@@ -10,31 +10,52 @@ import {
1010saveUserSelectedProxy ,
1111useProxy ,
1212} from "./ProxyContext"
13- import * as ProxyContextModule from "./ProxyContext "
13+ import * as ProxyLatency from "./useProxyLatency "
1414import {
1515renderWithAuth ,
1616waitForLoaderToBeRemoved ,
1717} from "testHelpers/renderHelpers"
1818import { screen } from "@testing-library/react"
1919import { server } from "testHelpers/server"
20- import "testHelpers/localstorage"
2120import { rest } from "msw"
2221import { 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
2443describe ( "ProxyContextGetURLs" , ( ) => {
2544it . each ( [
26- [ "empty" , [ ] , undefined , "" , "" ] ,
45+ [ "empty" , [ ] , { } , undefined , "" , "" ] ,
2746// Primary has no path app URL. Uses relative links
2847[
2948"primary" ,
3049[ MockPrimaryWorkspaceProxy ] ,
50+ { } ,
3151MockPrimaryWorkspaceProxy ,
3252"" ,
3353MockPrimaryWorkspaceProxy . wildcard_hostname ,
3454] ,
3555[
3656"regions selected" ,
3757MockWorkspaceProxies ,
58+ { } ,
3859MockHealthyWildWorkspaceProxy ,
3960MockHealthyWildWorkspaceProxy . path_app_url ,
4061MockHealthyWildWorkspaceProxy . wildcard_hostname ,
@@ -43,13 +64,15 @@ describe("ProxyContextGetURLs", () => {
4364[
4465"no selected" ,
4566[ MockPrimaryWorkspaceProxy ] ,
67+ { } ,
4668undefined ,
4769"" ,
4870MockPrimaryWorkspaceProxy . wildcard_hostname ,
4971] ,
5072[
5173"regions no select primary default" ,
5274MockWorkspaceProxies ,
75+ { } ,
5376undefined ,
5477"" ,
5578MockPrimaryWorkspaceProxy . wildcard_hostname ,
@@ -58,16 +81,40 @@ describe("ProxyContextGetURLs", () => {
5881[
5982"unhealthy selection" ,
6083MockWorkspaceProxies ,
84+ { } ,
6185MockUnhealthyWildWorkspaceProxy ,
6286"" ,
6387MockPrimaryWorkspaceProxy . 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 )
71118expect ( preferred . preferredPathAppURL ) . toBe ( preferredPathAppURL )
72119expect ( preferred . preferredWildcardHostname ) . toBe (
73120preferredWildcardHostname ,
@@ -76,11 +123,6 @@ describe("ProxyContextGetURLs", () => {
76123)
77124} )
78125
79- // interface ProxySelectTest {
80- // name: string
81- // actions: ()
82- // }
83-
84126const TestingComponent = ( ) => {
85127return renderWithAuth (
86128< ProxyProvider >
@@ -95,7 +137,8 @@ const TestingComponent = () => {
95137
96138// TestingScreen just mounts some components that we can check in the unit test.
97139const TestingScreen = ( ) => {
98- const { proxy, isFetched, isLoading} = useProxy ( )
140+ const { proxy, userProxy, isFetched, isLoading, clearProxy, setProxy} =
141+ useProxy ( )
99142return (
100143< >
101144< div data-testid = "isFetched" title = { isFetched . toString ( ) } > </ div >
@@ -104,49 +147,182 @@ const TestingScreen = () => {
104147data-testid = "preferredProxy"
105148title = { 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
111166interface ProxyContextSelectionTest {
112- expSelectedProxyID :string
113167regions :Region [ ]
114168storageProxy :Region | undefined
169+ latencies ?:Record < string , ProxyLatency . ProxyLatencyReport >
170+ afterLoad ?:( user :typeof userEvent ) => Promise < void >
171+
172+ expProxyID :string
173+ expUserProxyID ?:string
115174}
116175
117176describe ( "ProxyContextSelection" , ( ) => {
118177beforeEach ( ( ) => {
178+ // Object.defineProperty(window, "localStorage", { value: localStorageMock })
119179window . 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+
122200it . each ( [
201+ // Not latency behavior
123202[
124203"empty" ,
125204{
126- expSelectedProxyID :"" ,
205+ expProxyID :"" ,
127206regions :[ ] ,
128207storageProxy :undefined ,
208+ latencies :{ } ,
129209} ,
130210] ,
131211[
132212"regions_no_selection" ,
133213{
134- expSelectedProxyID :MockPrimaryWorkspaceProxy . id ,
214+ expProxyID :MockPrimaryWorkspaceProxy . id ,
135215regions :MockWorkspaceProxies ,
136216storageProxy :undefined ,
137217} ,
138218] ,
139219[
140220"regions_selected_unhealthy" ,
141221{
142- expSelectedProxyID :MockPrimaryWorkspaceProxy . id ,
222+ expProxyID :MockPrimaryWorkspaceProxy . id ,
143223regions :MockWorkspaceProxies ,
144224storageProxy :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
151327if ( storageProxy ) {
152328saveUserSelectedProxy ( storageProxy )
@@ -167,6 +343,11 @@ describe("ProxyContextSelection", () => {
167343TestingComponent ( )
168344await waitForLoaderToBeRemoved ( )
169345
346+ const user = userEvent . setup ( )
347+ if ( afterLoad ) {
348+ await afterLoad ( user )
349+ }
350+
170351await screen . findByTestId ( "isFetched" ) . then ( ( x ) => {
171352expect ( x . title ) . toBe ( "true" )
172353} )
@@ -176,17 +357,9 @@ describe("ProxyContextSelection", () => {
176357await screen . findByTestId ( "preferredProxy" ) . then ( ( x ) => {
177358expect ( 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} )