Thanks to visit codestin.com
Credit goes to github.com

Skip to content

refactor: consolidate API logic into CoderClient class #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 66 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
67cb4b3
wip: commit progress on UrlSync class/hook
Parkreiner Apr 26, 2024
e53d127
refactor: consolidate emoji-testing logic
Parkreiner Apr 26, 2024
f26184f
docs: update comments for clarity
Parkreiner Apr 26, 2024
b54324b
refactor: rename helpers to renderHelpers
Parkreiner Apr 26, 2024
7daf597
wip: finish initial implementation of UrlSync
Parkreiner Apr 26, 2024
86b5acc
chore: finish tests for UrlSync class
Parkreiner Apr 26, 2024
5d14d5a
chore: add mock DiscoveryApi helper
Parkreiner Apr 26, 2024
afd0203
chore: finish tests for useUrlSync
Parkreiner Apr 26, 2024
1e16c82
refactor: consolidate mock URL logic for useUrlSync
Parkreiner Apr 26, 2024
d786e34
fix: update test helper to use API list
Parkreiner Apr 26, 2024
063ecf2
fix: remove unneeded imports
Parkreiner Apr 26, 2024
1d1ab3c
fix: get tests for all current code passing
Parkreiner Apr 26, 2024
58566c8
fix: remove typo
Parkreiner Apr 26, 2024
cd5fef6
fix: update useUrlSync to expose underlying api
Parkreiner Apr 26, 2024
abfd949
refactor: increase data hiding for hook
Parkreiner Apr 26, 2024
26ae96d
fix: make useUrlSync tests less dependent on implementation details
Parkreiner Apr 26, 2024
5aabe86
refactor: remove reliance on baseUrl argument for fetch calls
Parkreiner Apr 26, 2024
425d50f
refactor: split Backstage error type into separate file
Parkreiner Apr 26, 2024
04f0f3e
refactor: clean up imports for api file
Parkreiner Apr 26, 2024
111df43
refactor: split main query options into separate file
Parkreiner Apr 26, 2024
1575f8e
consolidate how mock endpoints are defined
Parkreiner Apr 26, 2024
dc6e75c
fix: remove base URL from auth calls
Parkreiner Apr 26, 2024
fd8b3cb
refactor: consolidate almost all auth logic into CoderAuthProvider
Parkreiner Apr 26, 2024
5af006c
move api file into api directory
Parkreiner Apr 26, 2024
644e632
fix: revert prop that was changed for debugging
Parkreiner Apr 26, 2024
a667b48
fix: revert prop definition
Parkreiner Apr 26, 2024
cca343d
refactor: extract token-checking logic into middleware for server
Parkreiner Apr 26, 2024
aac5a0c
refactor: move shared auth key to queryOptions file
Parkreiner Apr 26, 2024
32fb344
docs: add reminder about arrow functions
Parkreiner Apr 26, 2024
170d451
wip: add initial versions of CoderClient code
Parkreiner Apr 28, 2024
9f548a3
wip: delete entire api.ts file
Parkreiner Apr 28, 2024
340399e
fix: remove temp api escape hatch for useUrlSync
Parkreiner Apr 28, 2024
5e6d812
chore: update syncToken logic to use temporary interceptors
Parkreiner Apr 28, 2024
b8affbd
refactor: update variable name for clarity
Parkreiner Apr 28, 2024
0a306f5
fix: prevent double-cancellation of timeout signals
Parkreiner Apr 28, 2024
24f1c29
fix: cleanup timeout logic
Parkreiner Apr 28, 2024
e2e2034
refactor: split pseudo-SDK into separate file
Parkreiner Apr 28, 2024
09b0c48
fix: resolve issue with conflicting interceptors
Parkreiner Apr 29, 2024
7b534cc
chore: improve cleanup logic
Parkreiner Apr 29, 2024
7dc8b24
fix: update majority of breaking tests
Parkreiner Apr 29, 2024
72edd92
fix: resolve all breaking tests
Parkreiner Apr 29, 2024
8d3ad60
fix: beef up CoderClient validation logic
Parkreiner Apr 29, 2024
af0cae8
chore: commit first passing test for CoderClient
Parkreiner Apr 29, 2024
fb10624
fix: update error-detection logic in test
Parkreiner Apr 29, 2024
239661d
wip: add all test stubs for CoderClient
Parkreiner Apr 29, 2024
cc4e4e7
chore: add test cases for syncToken's main return type
Parkreiner Apr 29, 2024
56bcbbd
chore: add more test cases
Parkreiner Apr 29, 2024
907f78e
fix: remove Object.freeze logic
Parkreiner Apr 29, 2024
94cd97a
refactor: consolidate mock API endpoints in one spot
Parkreiner Apr 29, 2024
648c4a4
wip: commit current test progress
Parkreiner Apr 29, 2024
08d38c6
refactor: rename mock API endpoint variable for clarity
Parkreiner Apr 29, 2024
c8cbba7
chore: finish test for aborting queued requests
Parkreiner Apr 29, 2024
9f1c63e
chore: finish initial versions of all CoderClient tests
Parkreiner Apr 29, 2024
c7fb74e
fix: delete helper that was never used
Parkreiner Apr 29, 2024
549e12e
fix: update getWorkspacesByRepo function signature to be more consist…
Parkreiner Apr 29, 2024
4a7fa14
docs: add comment reminder about arrow functions for CoderClient
Parkreiner Apr 29, 2024
9c1dc25
docs: add comment explaining use of interceptor logic
Parkreiner Apr 29, 2024
c91250d
fix: update return type of getWorkspacesByRepo function
Parkreiner Apr 29, 2024
e370197
fix: remove configApi from embedded class properties
Parkreiner Apr 30, 2024
9a359dc
fix: update query logic to remove any whitespace
Parkreiner Apr 30, 2024
4ed127b
Merge branch 'mes/api-v2-01' into mes/api-v2-02
Parkreiner Apr 30, 2024
bb05d8a
refactor: simplify interceptor removal logic
Parkreiner Apr 30, 2024
800bfc8
refactor: update how Backstage SDK is set up
Parkreiner Apr 30, 2024
5dad5fb
refactor: update dummy request for authenticating
Parkreiner Apr 30, 2024
fce7332
Merge branch 'main' into mes/api-v2-02
Parkreiner Apr 30, 2024
3c011c8
fix: add user parsing logic to CoderClient
Parkreiner Apr 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plugins/backstage-plugin-coder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
"@tanstack/react-query": "4.36.1",
"axios": "^1.6.8",
"use-sync-external-store": "^1.2.1",
"valibot": "^0.28.1"
},
Expand Down
215 changes: 215 additions & 0 deletions plugins/backstage-plugin-coder/src/api/CoderClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import {
CODER_AUTH_HEADER_KEY,
CoderClient,
disabledClientError,
} from './CoderClient';
import type { IdentityApi } from '@backstage/core-plugin-api';
import { UrlSync } from './UrlSync';
import { rest } from 'msw';
import { mockServerEndpoints, server, wrappedGet } from '../testHelpers/server';
import { CanceledError } from 'axios';
import { delay } from '../utils/time';
import { mockWorkspacesList } from '../testHelpers/mockCoderAppData';
import type { Workspace, WorkspacesResponse } from '../typesConstants';
import {
getMockConfigApi,
getMockDiscoveryApi,
getMockIdentityApi,
mockCoderAuthToken,
mockCoderWorkspacesConfig,
} from '../testHelpers/mockBackstageData';

type ConstructorApis = Readonly<{
identityApi: IdentityApi;
urlSync: UrlSync;
}>;

function getConstructorApis(): ConstructorApis {
const configApi = getMockConfigApi();
const discoveryApi = getMockDiscoveryApi();
const urlSync = new UrlSync({
apis: { configApi, discoveryApi },
});

const identityApi = getMockIdentityApi();
return { urlSync, identityApi };
}

describe(`${CoderClient.name}`, () => {
describe('syncToken functionality', () => {
it('Will load the provided token into the client if it is valid', async () => {
const client = new CoderClient({ apis: getConstructorApis() });

const syncResult = await client.syncToken(mockCoderAuthToken);
expect(syncResult).toBe(true);

let serverToken: string | null = null;
server.use(
rest.get(mockServerEndpoints.authenticatedUser, (req, res, ctx) => {
serverToken = req.headers.get(CODER_AUTH_HEADER_KEY);
return res(ctx.status(200));
}),
);

await client.sdk.getAuthenticatedUser();
expect(serverToken).toBe(mockCoderAuthToken);
});

it('Will NOT load the provided token into the client if it is invalid', async () => {
const client = new CoderClient({ apis: getConstructorApis() });

const syncResult = await client.syncToken('Definitely not valid');
expect(syncResult).toBe(false);

let serverToken: string | null = null;
server.use(
rest.get(mockServerEndpoints.authenticatedUser, (req, res, ctx) => {
serverToken = req.headers.get(CODER_AUTH_HEADER_KEY);
return res(ctx.status(200));
}),
);

await client.sdk.getAuthenticatedUser();
expect(serverToken).toBe(null);
});

it('Will propagate any other error types to the caller', async () => {
const client = new CoderClient({
// Setting the timeout to 0 will make requests instantly fail from the
// next microtask queue tick
requestTimeoutMs: 0,
apis: getConstructorApis(),
});

server.use(
rest.get(mockServerEndpoints.authenticatedUser, async (_, res, ctx) => {
// MSW is so fast that sometimes it can respond before a forced
// timeout; have to introduce artificial delay (that shouldn't matter
// as long as the abort logic goes through properly)
await delay(2_000);
return res(ctx.status(200));
}),
);

await expect(() => {
return client.syncToken(mockCoderAuthToken);
}).rejects.toThrow(CanceledError);
});
});

describe('cleanupClient functionality', () => {
it('Will prevent any new SDK requests from going through', async () => {
const client = new CoderClient({ apis: getConstructorApis() });
client.cleanupClient();

// Request should fail, even though token is valid
await expect(() => {
return client.syncToken(mockCoderAuthToken);
}).rejects.toThrow(disabledClientError);

await expect(() => {
return client.sdk.getWorkspaces({
q: 'owner:me',
limit: 0,
});
}).rejects.toThrow(disabledClientError);
});

it('Will abort any pending requests', async () => {
const client = new CoderClient({
initialToken: mockCoderAuthToken,
apis: getConstructorApis(),
});

// Sanity check to ensure that request can still go through normally
const workspacesPromise1 = client.sdk.getWorkspaces({
q: 'owner:me',
limit: 0,
});

await expect(workspacesPromise1).resolves.toEqual<WorkspacesResponse>({
workspaces: mockWorkspacesList,
count: mockWorkspacesList.length,
});

const workspacesPromise2 = client.sdk.getWorkspaces({
q: 'owner:me',
limit: 0,
});
client.cleanupClient();
await expect(() => workspacesPromise2).rejects.toThrow();
});
});

// Eventually the Coder SDK is going to get too big to test every single
// function. Focus tests on the functionality specifically being patched in
// for Backstage
describe('Coder SDK', () => {
it('Will remap all workspace icon URLs to use the proxy URL if necessary', async () => {
const apis = getConstructorApis();
const client = new CoderClient({
apis,
initialToken: mockCoderAuthToken,
});

server.use(
wrappedGet(mockServerEndpoints.workspaces, (_, res, ctx) => {
const withRelativePaths = mockWorkspacesList.map<Workspace>(ws => {
return {
...ws,
template_icon: '/emojis/blueberry.svg',
};
});

return res(
ctx.status(200),
ctx.json<WorkspacesResponse>({
workspaces: withRelativePaths,
count: withRelativePaths.length,
}),
);
}),
);

const { workspaces } = await client.sdk.getWorkspaces({
q: 'owner:me',
limit: 0,
});

const { urlSync } = apis;
const apiEndpoint = await urlSync.getApiEndpoint();

const allWorkspacesAreRemapped = !workspaces.some(ws =>
ws.template_icon.startsWith(apiEndpoint),
);

expect(allWorkspacesAreRemapped).toBe(true);
});

it('Lets the user search for workspaces by repo URL', async () => {
const client = new CoderClient({
initialToken: mockCoderAuthToken,
apis: getConstructorApis(),
});

const { workspaces } = await client.sdk.getWorkspacesByRepo(
{ q: 'owner:me' },
mockCoderWorkspacesConfig,
);

const buildParameterGroups = await Promise.all(
workspaces.map(ws =>
client.sdk.getWorkspaceBuildParameters(ws.latest_build.id),
),
);

for (const paramGroup of buildParameterGroups) {
const atLeastOneParamMatchesForGroup = paramGroup.some(param => {
return param.value === mockCoderWorkspacesConfig.repoUrl;
});

expect(atLeastOneParamMatchesForGroup).toBe(true);
}
});
});
});
Loading