Skip to content

Commit 1305931

Browse files
committed
chore: Auto select based on latency
1 parent 159538c commit 1305931

File tree

4 files changed

+297
-67
lines changed

4 files changed

+297
-67
lines changed

site/src/contexts/ProxyContext.test.tsx

+201-28
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,52 @@ import {
1010
saveUserSelectedProxy,
1111
useProxy,
1212
} from "./ProxyContext"
13-
import * as ProxyContextModule from "./ProxyContext"
13+
import * as ProxyLatency from "./useProxyLatency"
1414
import {
1515
renderWithAuth,
1616
waitForLoaderToBeRemoved,
1717
} from "testHelpers/renderHelpers"
1818
import { screen } from "@testing-library/react"
1919
import { server } from "testHelpers/server"
20-
import "testHelpers/localstorage"
2120
import { rest } from "msw"
2221
import { Region } from "api/typesGenerated"
22+
import "testHelpers/localstorage"
23+
import userEvent from "@testing-library/user-event"
24+
25+
// Mock useProxyLatency to use a hard-coded latency. 'jest.mock' must be called
26+
// here and not inside a unit test.
27+
jest.mock("contexts/useProxyLatency", () => ({
28+
useProxyLatency: () => {
29+
return hardCodedLatencies
30+
},
31+
}))
32+
33+
let hardCodedLatencies: Record<string, ProxyLatency.ProxyLatencyReport> = {}
34+
35+
const fakeLatency = (ms: number): ProxyLatency.ProxyLatencyReport => {
36+
return {
37+
latencyMS: ms,
38+
accurate: true,
39+
at: new Date(),
40+
}
41+
}
2342

2443
describe("ProxyContextGetURLs", () => {
2544
it.each([
26-
["empty", [], undefined, "", ""],
45+
["empty", [], {}, undefined, "", ""],
2746
// Primary has no path app URL. Uses relative links
2847
[
2948
"primary",
3049
[MockPrimaryWorkspaceProxy],
50+
{},
3151
MockPrimaryWorkspaceProxy,
3252
"",
3353
MockPrimaryWorkspaceProxy.wildcard_hostname,
3454
],
3555
[
3656
"regions selected",
3757
MockWorkspaceProxies,
58+
{},
3859
MockHealthyWildWorkspaceProxy,
3960
MockHealthyWildWorkspaceProxy.path_app_url,
4061
MockHealthyWildWorkspaceProxy.wildcard_hostname,
@@ -43,13 +64,15 @@ describe("ProxyContextGetURLs", () => {
4364
[
4465
"no selected",
4566
[MockPrimaryWorkspaceProxy],
67+
{},
4668
undefined,
4769
"",
4870
MockPrimaryWorkspaceProxy.wildcard_hostname,
4971
],
5072
[
5173
"regions no select primary default",
5274
MockWorkspaceProxies,
75+
{},
5376
undefined,
5477
"",
5578
MockPrimaryWorkspaceProxy.wildcard_hostname,
@@ -58,16 +81,40 @@ describe("ProxyContextGetURLs", () => {
5881
[
5982
"unhealthy selection",
6083
MockWorkspaceProxies,
84+
{},
6185
MockUnhealthyWildWorkspaceProxy,
6286
"",
6387
MockPrimaryWorkspaceProxy.wildcard_hostname,
6488
],
6589
// This should never happen, when there is no primary
66-
["no primary", [MockHealthyWildWorkspaceProxy], undefined, "", ""],
90+
["no primary", [MockHealthyWildWorkspaceProxy], {}, undefined, "", ""],
91+
// Latency behavior
92+
[
93+
"best latency",
94+
MockWorkspaceProxies,
95+
{
96+
[MockPrimaryWorkspaceProxy.id]: fakeLatency(100),
97+
[MockHealthyWildWorkspaceProxy.id]: fakeLatency(50),
98+
// This should be ignored because it's unhealthy
99+
[MockUnhealthyWildWorkspaceProxy.id]: fakeLatency(25),
100+
// This should be ignored because it is not in the list.
101+
["not a proxy"]: fakeLatency(10),
102+
},
103+
undefined,
104+
MockHealthyWildWorkspaceProxy.path_app_url,
105+
MockHealthyWildWorkspaceProxy.wildcard_hostname,
106+
],
67107
])(
68108
`%p`,
69-
(_, regions, selected, preferredPathAppURL, preferredWildcardHostname) => {
70-
const preferred = getPreferredProxy(regions, selected)
109+
(
110+
_,
111+
regions,
112+
latencies,
113+
selected,
114+
preferredPathAppURL,
115+
preferredWildcardHostname,
116+
) => {
117+
const preferred = getPreferredProxy(regions, selected, latencies)
71118
expect(preferred.preferredPathAppURL).toBe(preferredPathAppURL)
72119
expect(preferred.preferredWildcardHostname).toBe(
73120
preferredWildcardHostname,
@@ -76,11 +123,6 @@ describe("ProxyContextGetURLs", () => {
76123
)
77124
})
78125

79-
// interface ProxySelectTest {
80-
// name: string
81-
// actions: ()
82-
// }
83-
84126
const TestingComponent = () => {
85127
return renderWithAuth(
86128
<ProxyProvider>
@@ -95,7 +137,8 @@ const TestingComponent = () => {
95137

96138
// TestingScreen just mounts some components that we can check in the unit test.
97139
const TestingScreen = () => {
98-
const { proxy, isFetched, isLoading } = useProxy()
140+
const { proxy, userProxy, isFetched, isLoading, clearProxy, setProxy } =
141+
useProxy()
99142
return (
100143
<>
101144
<div data-testid="isFetched" title={isFetched.toString()}></div>
@@ -104,49 +147,182 @@ const TestingScreen = () => {
104147
data-testid="preferredProxy"
105148
title={proxy.selectedProxy && proxy.selectedProxy.id}
106149
></div>
150+
<div data-testid="userProxy" title={userProxy && userProxy.id}></div>
151+
<button data-testid="clearProxy" onClick={clearProxy}></button>
152+
<div data-testid="userSelectProxyData"></div>
153+
<button
154+
data-testid="userSelectProxy"
155+
onClick={() => {
156+
const data = screen.getByTestId("userSelectProxyData")
157+
if (data.innerText) {
158+
setProxy(JSON.parse(data.innerText))
159+
}
160+
}}
161+
></button>
107162
</>
108163
)
109164
}
110165

111166
interface ProxyContextSelectionTest {
112-
expSelectedProxyID: string
113167
regions: Region[]
114168
storageProxy: Region | undefined
169+
latencies?: Record<string, ProxyLatency.ProxyLatencyReport>
170+
afterLoad?: (user: typeof userEvent) => Promise<void>
171+
172+
expProxyID: string
173+
expUserProxyID?: string
115174
}
116175

117176
describe("ProxyContextSelection", () => {
118177
beforeEach(() => {
178+
// Object.defineProperty(window, "localStorage", { value: localStorageMock })
119179
window.localStorage.clear()
120180
})
121181

182+
// A way to simulate a user clearing the proxy selection.
183+
const clearProxyAction = async (user: typeof userEvent): Promise<void> => {
184+
const clearProxyButton = screen.getByTestId("clearProxy")
185+
await user.click(clearProxyButton)
186+
}
187+
188+
const userSelectProxy = (
189+
proxy: Region,
190+
): ((user: typeof userEvent) => Promise<void>) => {
191+
return async (user): Promise<void> => {
192+
const selectData = screen.getByTestId("userSelectProxyData")
193+
selectData.innerText = JSON.stringify(proxy)
194+
195+
const selectProxyButton = screen.getByTestId("userSelectProxy")
196+
await user.click(selectProxyButton)
197+
}
198+
}
199+
122200
it.each([
201+
// Not latency behavior
123202
[
124203
"empty",
125204
{
126-
expSelectedProxyID: "",
205+
expProxyID: "",
127206
regions: [],
128207
storageProxy: undefined,
208+
latencies: {},
129209
},
130210
],
131211
[
132212
"regions_no_selection",
133213
{
134-
expSelectedProxyID: MockPrimaryWorkspaceProxy.id,
214+
expProxyID: MockPrimaryWorkspaceProxy.id,
135215
regions: MockWorkspaceProxies,
136216
storageProxy: undefined,
137217
},
138218
],
139219
[
140220
"regions_selected_unhealthy",
141221
{
142-
expSelectedProxyID: MockPrimaryWorkspaceProxy.id,
222+
expProxyID: MockPrimaryWorkspaceProxy.id,
143223
regions: MockWorkspaceProxies,
144224
storageProxy: MockUnhealthyWildWorkspaceProxy,
225+
expUserProxyID: MockUnhealthyWildWorkspaceProxy.id,
226+
},
227+
],
228+
[
229+
"regions_selected_healthy",
230+
{
231+
expProxyID: MockHealthyWildWorkspaceProxy.id,
232+
regions: MockWorkspaceProxies,
233+
storageProxy: MockHealthyWildWorkspaceProxy,
234+
expUserProxyID: MockHealthyWildWorkspaceProxy.id,
235+
},
236+
],
237+
[
238+
"regions_selected_clear",
239+
{
240+
expProxyID: MockPrimaryWorkspaceProxy.id,
241+
regions: MockWorkspaceProxies,
242+
storageProxy: MockHealthyWildWorkspaceProxy,
243+
afterLoad: clearProxyAction,
244+
expUserProxyID: undefined,
245+
},
246+
],
247+
[
248+
"regions_make_selection",
249+
{
250+
expProxyID: MockHealthyWildWorkspaceProxy.id,
251+
regions: MockWorkspaceProxies,
252+
afterLoad: userSelectProxy(MockHealthyWildWorkspaceProxy),
253+
expUserProxyID: MockHealthyWildWorkspaceProxy.id,
254+
},
255+
],
256+
// Latency behavior
257+
[
258+
"regions_default_low_latency",
259+
{
260+
expProxyID: MockHealthyWildWorkspaceProxy.id,
261+
regions: MockWorkspaceProxies,
262+
storageProxy: undefined,
263+
latencies: {
264+
[MockPrimaryWorkspaceProxy.id]: fakeLatency(100),
265+
[MockHealthyWildWorkspaceProxy.id]: fakeLatency(50),
266+
// This is a trick. It's unhealthy so should be ignored.
267+
[MockUnhealthyWildWorkspaceProxy.id]: fakeLatency(25),
268+
},
269+
},
270+
],
271+
[
272+
// User intentionally selected a high latency proxy.
273+
"regions_select_high_latency",
274+
{
275+
expProxyID: MockHealthyWildWorkspaceProxy.id,
276+
regions: MockWorkspaceProxies,
277+
storageProxy: undefined,
278+
afterLoad: userSelectProxy(MockHealthyWildWorkspaceProxy),
279+
expUserProxyID: MockHealthyWildWorkspaceProxy.id,
280+
latencies: {
281+
[MockHealthyWildWorkspaceProxy.id]: fakeLatency(500),
282+
[MockPrimaryWorkspaceProxy.id]: fakeLatency(100),
283+
// This is a trick. It's unhealthy so should be ignored.
284+
[MockUnhealthyWildWorkspaceProxy.id]: fakeLatency(25),
285+
},
286+
},
287+
],
288+
[
289+
// Low latency proxy is selected, but it is unhealthy
290+
"regions_select_unhealthy_low_latency",
291+
{
292+
expProxyID: MockPrimaryWorkspaceProxy.id,
293+
regions: MockWorkspaceProxies,
294+
storageProxy: MockUnhealthyWildWorkspaceProxy,
295+
expUserProxyID: MockUnhealthyWildWorkspaceProxy.id,
296+
latencies: {
297+
[MockHealthyWildWorkspaceProxy.id]: fakeLatency(500),
298+
[MockPrimaryWorkspaceProxy.id]: fakeLatency(100),
299+
// This is a trick. It's unhealthy so should be ignored.
300+
[MockUnhealthyWildWorkspaceProxy.id]: fakeLatency(25),
301+
},
145302
},
146303
],
147304
] as [string, ProxyContextSelectionTest][])(
148305
`%s`,
149-
async (_, { expSelectedProxyID, regions, storageProxy }) => {
306+
async (
307+
_,
308+
{
309+
expUserProxyID,
310+
expProxyID: expSelectedProxyID,
311+
regions,
312+
storageProxy,
313+
latencies = {},
314+
afterLoad,
315+
},
316+
) => {
317+
// Mock the latencies
318+
hardCodedLatencies = latencies
319+
320+
// jest.mock("contexts/useProxyLatency", () => ({
321+
// useProxyLatency: () => {
322+
// return latencies
323+
// },
324+
// }))
325+
150326
// Initial selection if present
151327
if (storageProxy) {
152328
saveUserSelectedProxy(storageProxy)
@@ -167,6 +343,11 @@ describe("ProxyContextSelection", () => {
167343
TestingComponent()
168344
await waitForLoaderToBeRemoved()
169345

346+
const user = userEvent.setup()
347+
if (afterLoad) {
348+
await afterLoad(user)
349+
}
350+
170351
await screen.findByTestId("isFetched").then((x) => {
171352
expect(x.title).toBe("true")
172353
})
@@ -176,17 +357,9 @@ describe("ProxyContextSelection", () => {
176357
await screen.findByTestId("preferredProxy").then((x) => {
177358
expect(x.title).toBe(expSelectedProxyID)
178359
})
179-
180-
// const { proxy, proxies, isFetched, isLoading, proxyLatencies } = useProxy()
181-
// expect(isLoading).toBe(false)
182-
// expect(isFetched).toBe(true)
183-
184-
// expect(x).toBe(2)
185-
// const preferred = getPreferredProxy(regions, selected)
186-
// expect(preferred.preferredPathAppURL).toBe(preferredPathAppURL)
187-
// expect(preferred.preferredWildcardHostname).toBe(
188-
// preferredWildcardHostname,
189-
// )
360+
await screen.findByTestId("userProxy").then((x) => {
361+
expect(x.title).toBe(expUserProxyID || "")
362+
})
190363
},
191364
)
192365
})

0 commit comments

Comments
 (0)