mirror of https://github.com/coder/coder.git
387 lines
11 KiB
TypeScript
387 lines
11 KiB
TypeScript
import "testHelpers/localStorage";
|
|
import { screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { http, HttpResponse } from "msw";
|
|
import type { Region } from "api/typesGenerated";
|
|
import {
|
|
MockPrimaryWorkspaceProxy,
|
|
MockWorkspaceProxies,
|
|
MockHealthyWildWorkspaceProxy,
|
|
MockUnhealthyWildWorkspaceProxy,
|
|
} from "testHelpers/entities";
|
|
import {
|
|
renderWithAuth,
|
|
waitForLoaderToBeRemoved,
|
|
} from "testHelpers/renderHelpers";
|
|
import { server } from "testHelpers/server";
|
|
import {
|
|
getPreferredProxy,
|
|
ProxyProvider,
|
|
saveUserSelectedProxy,
|
|
useProxy,
|
|
} from "./ProxyContext";
|
|
import type * as ProxyLatency from "./useProxyLatency";
|
|
|
|
// Mock useProxyLatency to use a hard-coded latency. 'jest.mock' must be called
|
|
// here and not inside a unit test.
|
|
jest.mock("contexts/useProxyLatency", () => ({
|
|
useProxyLatency: () => {
|
|
return { proxyLatencies: hardCodedLatencies, refetch: jest.fn() };
|
|
},
|
|
}));
|
|
|
|
let hardCodedLatencies: Record<string, ProxyLatency.ProxyLatencyReport> = {};
|
|
|
|
// fakeLatency is a helper function to make a Latency report from just a number.
|
|
const fakeLatency = (ms: number): ProxyLatency.ProxyLatencyReport => {
|
|
return {
|
|
latencyMS: ms,
|
|
accurate: true,
|
|
at: new Date(),
|
|
};
|
|
};
|
|
|
|
describe("ProxyContextGetURLs", () => {
|
|
it.each([
|
|
["empty", [], {}, undefined, "", ""],
|
|
// Primary has no path app URL. Uses relative links
|
|
[
|
|
"primary",
|
|
[MockPrimaryWorkspaceProxy],
|
|
{},
|
|
MockPrimaryWorkspaceProxy,
|
|
"",
|
|
MockPrimaryWorkspaceProxy.wildcard_hostname,
|
|
],
|
|
[
|
|
"regions selected",
|
|
MockWorkspaceProxies,
|
|
{},
|
|
MockHealthyWildWorkspaceProxy,
|
|
MockHealthyWildWorkspaceProxy.path_app_url,
|
|
MockHealthyWildWorkspaceProxy.wildcard_hostname,
|
|
],
|
|
// Primary is the default if none selected
|
|
[
|
|
"no selected",
|
|
[MockPrimaryWorkspaceProxy],
|
|
{},
|
|
undefined,
|
|
"",
|
|
MockPrimaryWorkspaceProxy.wildcard_hostname,
|
|
],
|
|
[
|
|
"regions no select primary default",
|
|
MockWorkspaceProxies,
|
|
{},
|
|
undefined,
|
|
"",
|
|
MockPrimaryWorkspaceProxy.wildcard_hostname,
|
|
],
|
|
// Primary is the default if the selected is unhealthy
|
|
[
|
|
"unhealthy selection",
|
|
MockWorkspaceProxies,
|
|
{},
|
|
MockUnhealthyWildWorkspaceProxy,
|
|
"",
|
|
MockPrimaryWorkspaceProxy.wildcard_hostname,
|
|
],
|
|
// This should never happen, when there is no primary
|
|
["no primary", [MockHealthyWildWorkspaceProxy], {}, undefined, "", ""],
|
|
// Latency behavior
|
|
[
|
|
"best latency",
|
|
MockWorkspaceProxies,
|
|
{
|
|
[MockPrimaryWorkspaceProxy.id]: fakeLatency(100),
|
|
[MockHealthyWildWorkspaceProxy.id]: fakeLatency(50),
|
|
// This should be ignored because it's unhealthy
|
|
[MockUnhealthyWildWorkspaceProxy.id]: fakeLatency(25),
|
|
// This should be ignored because it is not in the list.
|
|
["not a proxy"]: fakeLatency(10),
|
|
},
|
|
undefined,
|
|
MockHealthyWildWorkspaceProxy.path_app_url,
|
|
MockHealthyWildWorkspaceProxy.wildcard_hostname,
|
|
],
|
|
])(
|
|
`%p`,
|
|
(
|
|
_,
|
|
regions,
|
|
latencies,
|
|
selected,
|
|
preferredPathAppURL,
|
|
preferredWildcardHostname,
|
|
) => {
|
|
const preferred = getPreferredProxy(regions, selected, latencies);
|
|
expect(preferred.preferredPathAppURL).toBe(preferredPathAppURL);
|
|
expect(preferred.preferredWildcardHostname).toBe(
|
|
preferredWildcardHostname,
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
const TestingComponent = () => {
|
|
return renderWithAuth(
|
|
<ProxyProvider>
|
|
<TestingScreen />
|
|
</ProxyProvider>,
|
|
{
|
|
route: `/proxies`,
|
|
path: "/proxies",
|
|
},
|
|
);
|
|
};
|
|
|
|
// TestingScreen just mounts some components that we can check in the unit test.
|
|
const TestingScreen = () => {
|
|
const { proxy, userProxy, isFetched, isLoading, clearProxy, setProxy } =
|
|
useProxy();
|
|
return (
|
|
<>
|
|
<div data-testid="isFetched" title={isFetched.toString()}></div>
|
|
<div data-testid="isLoading" title={isLoading.toString()}></div>
|
|
<div
|
|
data-testid="preferredProxy"
|
|
title={proxy.proxy && proxy.proxy.id}
|
|
></div>
|
|
<div data-testid="userProxy" title={userProxy && userProxy.id}></div>
|
|
<button data-testid="clearProxy" onClick={clearProxy}></button>
|
|
<div data-testid="userSelectProxyData"></div>
|
|
<button
|
|
data-testid="userSelectProxy"
|
|
onClick={() => {
|
|
const data = screen.getByTestId("userSelectProxyData");
|
|
if (data.innerText) {
|
|
setProxy(JSON.parse(data.innerText));
|
|
}
|
|
}}
|
|
></button>
|
|
</>
|
|
);
|
|
};
|
|
|
|
interface ProxyContextSelectionTest {
|
|
// Regions is the list of regions to return via the "api" response.
|
|
regions: Region[];
|
|
// storageProxy should be the proxy stored in local storage before the
|
|
// component is mounted and context is loaded. This simulates opening a
|
|
// new window with a selection saved from before.
|
|
storageProxy: Region | undefined;
|
|
// latencies is the hard coded latencies to return. If empty, no latencies
|
|
// are returned.
|
|
latencies?: Record<string, ProxyLatency.ProxyLatencyReport>;
|
|
// afterLoad are actions to take after loading the component, but before
|
|
// assertions. This is useful for simulating user actions.
|
|
afterLoad?: () => Promise<void>;
|
|
|
|
// Assert these values.
|
|
// expProxyID is the proxyID returned to be used.
|
|
expProxyID: string;
|
|
// expUserProxyID is the user's stored selection.
|
|
expUserProxyID?: string;
|
|
}
|
|
|
|
describe("ProxyContextSelection", () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
});
|
|
|
|
// A way to simulate a user clearing the proxy selection.
|
|
const clearProxyAction = async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
const clearProxyButton = screen.getByTestId("clearProxy");
|
|
await user.click(clearProxyButton);
|
|
};
|
|
|
|
const userSelectProxy = (proxy: Region): (() => Promise<void>) => {
|
|
return async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
const selectData = screen.getByTestId("userSelectProxyData");
|
|
selectData.innerText = JSON.stringify(proxy);
|
|
|
|
const selectProxyButton = screen.getByTestId("userSelectProxy");
|
|
await user.click(selectProxyButton);
|
|
};
|
|
};
|
|
|
|
it.each([
|
|
// Not latency behavior
|
|
[
|
|
"empty",
|
|
{
|
|
expProxyID: "",
|
|
regions: [],
|
|
storageProxy: undefined,
|
|
latencies: {},
|
|
},
|
|
],
|
|
[
|
|
"regions_no_selection",
|
|
{
|
|
expProxyID: MockPrimaryWorkspaceProxy.id,
|
|
regions: MockWorkspaceProxies,
|
|
storageProxy: undefined,
|
|
},
|
|
],
|
|
[
|
|
"regions_selected_unhealthy",
|
|
{
|
|
expProxyID: MockPrimaryWorkspaceProxy.id,
|
|
regions: MockWorkspaceProxies,
|
|
storageProxy: MockUnhealthyWildWorkspaceProxy,
|
|
expUserProxyID: MockUnhealthyWildWorkspaceProxy.id,
|
|
},
|
|
],
|
|
[
|
|
"regions_selected_healthy",
|
|
{
|
|
expProxyID: MockHealthyWildWorkspaceProxy.id,
|
|
regions: MockWorkspaceProxies,
|
|
storageProxy: MockHealthyWildWorkspaceProxy,
|
|
expUserProxyID: MockHealthyWildWorkspaceProxy.id,
|
|
},
|
|
],
|
|
[
|
|
"regions_selected_clear",
|
|
{
|
|
expProxyID: MockPrimaryWorkspaceProxy.id,
|
|
regions: MockWorkspaceProxies,
|
|
storageProxy: MockHealthyWildWorkspaceProxy,
|
|
afterLoad: clearProxyAction,
|
|
expUserProxyID: undefined,
|
|
},
|
|
],
|
|
[
|
|
"regions_make_selection",
|
|
{
|
|
expProxyID: MockHealthyWildWorkspaceProxy.id,
|
|
regions: MockWorkspaceProxies,
|
|
afterLoad: userSelectProxy(MockHealthyWildWorkspaceProxy),
|
|
expUserProxyID: MockHealthyWildWorkspaceProxy.id,
|
|
},
|
|
],
|
|
// Latency behavior is disabled, so the primary should be selected.
|
|
[
|
|
"regions_default_low_latency",
|
|
{
|
|
expProxyID: MockPrimaryWorkspaceProxy.id,
|
|
regions: MockWorkspaceProxies,
|
|
storageProxy: undefined,
|
|
latencies: {
|
|
[MockPrimaryWorkspaceProxy.id]: fakeLatency(100),
|
|
[MockHealthyWildWorkspaceProxy.id]: fakeLatency(50),
|
|
// This is a trick. It's unhealthy so should be ignored.
|
|
[MockUnhealthyWildWorkspaceProxy.id]: fakeLatency(25),
|
|
},
|
|
},
|
|
],
|
|
[
|
|
// User intentionally selected a high latency proxy.
|
|
"regions_select_high_latency",
|
|
{
|
|
expProxyID: MockHealthyWildWorkspaceProxy.id,
|
|
regions: MockWorkspaceProxies,
|
|
storageProxy: undefined,
|
|
afterLoad: userSelectProxy(MockHealthyWildWorkspaceProxy),
|
|
expUserProxyID: MockHealthyWildWorkspaceProxy.id,
|
|
latencies: {
|
|
[MockHealthyWildWorkspaceProxy.id]: fakeLatency(500),
|
|
[MockPrimaryWorkspaceProxy.id]: fakeLatency(100),
|
|
// This is a trick. It's unhealthy so should be ignored.
|
|
[MockUnhealthyWildWorkspaceProxy.id]: fakeLatency(25),
|
|
},
|
|
},
|
|
],
|
|
[
|
|
// Low latency proxy is selected, but it is unhealthy
|
|
"regions_select_unhealthy_low_latency",
|
|
{
|
|
expProxyID: MockPrimaryWorkspaceProxy.id,
|
|
regions: MockWorkspaceProxies,
|
|
storageProxy: MockUnhealthyWildWorkspaceProxy,
|
|
expUserProxyID: MockUnhealthyWildWorkspaceProxy.id,
|
|
latencies: {
|
|
[MockHealthyWildWorkspaceProxy.id]: fakeLatency(500),
|
|
[MockPrimaryWorkspaceProxy.id]: fakeLatency(100),
|
|
// This is a trick. It's unhealthy so should be ignored.
|
|
[MockUnhealthyWildWorkspaceProxy.id]: fakeLatency(25),
|
|
},
|
|
},
|
|
],
|
|
[
|
|
// Excess proxies we do not have are low latency.
|
|
// This will probably never happen in production.
|
|
"unknown_regions_low_latency",
|
|
{
|
|
// Default to primary since we have unknowns
|
|
expProxyID: MockPrimaryWorkspaceProxy.id,
|
|
regions: MockWorkspaceProxies,
|
|
storageProxy: MockUnhealthyWildWorkspaceProxy,
|
|
expUserProxyID: MockUnhealthyWildWorkspaceProxy.id,
|
|
latencies: {
|
|
["some"]: fakeLatency(500),
|
|
["random"]: fakeLatency(100),
|
|
["ids"]: fakeLatency(25),
|
|
},
|
|
},
|
|
],
|
|
] as [string, ProxyContextSelectionTest][])(
|
|
`%s`,
|
|
async (
|
|
_,
|
|
{
|
|
expUserProxyID,
|
|
expProxyID: expSelectedProxyID,
|
|
regions,
|
|
storageProxy,
|
|
latencies = {},
|
|
afterLoad,
|
|
},
|
|
) => {
|
|
// Mock the latencies
|
|
hardCodedLatencies = latencies;
|
|
|
|
// Initial selection if present
|
|
if (storageProxy) {
|
|
saveUserSelectedProxy(storageProxy);
|
|
}
|
|
|
|
// Mock the API response
|
|
server.use(
|
|
http.get("/api/v2/regions", () =>
|
|
HttpResponse.json({
|
|
regions,
|
|
}),
|
|
),
|
|
http.get("/api/v2/workspaceproxies", () =>
|
|
HttpResponse.json({ regions }),
|
|
),
|
|
);
|
|
|
|
TestingComponent();
|
|
await waitForLoaderToBeRemoved();
|
|
|
|
if (afterLoad) {
|
|
await afterLoad();
|
|
}
|
|
|
|
await screen.findByTestId("isFetched").then((x) => {
|
|
expect(x.title).toBe("true");
|
|
});
|
|
await screen.findByTestId("isLoading").then((x) => {
|
|
expect(x.title).toBe("false");
|
|
});
|
|
await screen.findByTestId("preferredProxy").then((x) => {
|
|
expect(x.title).toBe(expSelectedProxyID);
|
|
});
|
|
await screen.findByTestId("userProxy").then((x) => {
|
|
expect(x.title).toBe(expUserProxyID || "");
|
|
});
|
|
},
|
|
);
|
|
});
|