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

Skip to content

Commit ba43e8a

Browse files
committed
wip: commit progress on tests
1 parent 15a4e22 commit ba43e8a

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/**
2+
* @file Ideally all the files in CoderProvider could be treated as
3+
* implementation details, and we could have a single test file for all the
4+
* pieces joined together.
5+
*
6+
* But because the auth is complicated, it helps to have tests just for it
7+
*/
8+
import React, { type ReactNode } from 'react';
9+
import { render, screen, waitFor } from '@testing-library/react';
10+
import {
11+
BACKSTAGE_APP_ROOT_ID,
12+
CoderAuthProvider,
13+
TOKEN_STORAGE_KEY,
14+
useEndUserCoderAuth,
15+
useInternalCoderAuth,
16+
} from './CoderAuthProvider';
17+
import { CoderClient, coderClientApiRef } from '../../api/CoderClient';
18+
import {
19+
getMockConfigApi,
20+
getMockDiscoveryApi,
21+
getMockIdentityApi,
22+
mockCoderAuthToken,
23+
} from '../../testHelpers/mockBackstageData';
24+
import { UrlSync } from '../../api/UrlSync';
25+
import { TestApiProvider } from '@backstage/test-utils';
26+
import { QueryClientProvider } from '@tanstack/react-query';
27+
import { getMockQueryClient } from '../../testHelpers/setup';
28+
29+
afterEach(() => {
30+
jest.clearAllMocks();
31+
});
32+
33+
function renderAuthProvider(children: ReactNode) {
34+
const urlSync = new UrlSync({
35+
apis: {
36+
configApi: getMockConfigApi(),
37+
discoveryApi: getMockDiscoveryApi(),
38+
},
39+
});
40+
41+
const identityApi = getMockIdentityApi();
42+
43+
// Can't use initialToken property, because then the Auth provider won't be
44+
// aware of it. When testing for UI authentication, we need to feed the token
45+
// from localStorage to the provider, which then feeds it to the client while
46+
// keeping track of the React state changes
47+
const coderClient = new CoderClient({
48+
apis: { urlSync, identityApi },
49+
});
50+
51+
const mockAppRoot = document.createElement('div');
52+
mockAppRoot.id = BACKSTAGE_APP_ROOT_ID;
53+
document.body.append(mockAppRoot);
54+
55+
const queryClient = getMockQueryClient();
56+
const renderResult = render(
57+
<TestApiProvider apis={[[coderClientApiRef, coderClient]]}>
58+
<QueryClientProvider client={queryClient}>
59+
<CoderAuthProvider>{children}</CoderAuthProvider>
60+
</QueryClientProvider>
61+
</TestApiProvider>,
62+
{
63+
baseElement: mockAppRoot,
64+
},
65+
);
66+
67+
return { ...renderResult, coderClient };
68+
}
69+
70+
describe(`${CoderAuthProvider.name}`, () => {
71+
/**
72+
* @todo Figure out what general auth state logic could benefit from tests
73+
* and put them in a separate describe block
74+
*/
75+
describe('Fallback auth input', () => {
76+
const fallbackTriggerMatcher = /Authenticate with Coder/;
77+
78+
function MockTrackedComponent() {
79+
const auth = useInternalCoderAuth();
80+
return <p>Authenticated? {auth.isAuthenticated ? 'Yes!' : 'No...'}</p>;
81+
}
82+
83+
function MockEndUserComponent() {
84+
const auth = useEndUserCoderAuth();
85+
return <p>Authenticated? {auth.isAuthenticated ? 'Yes!' : 'No...'}</p>;
86+
}
87+
88+
async function waitForNAuthenticatedComponents(n: number) {
89+
await waitFor(() => {
90+
const authenticatedComponents = screen.getAllByText(/Yes!/);
91+
expect(authenticatedComponents.length).toBe(n);
92+
});
93+
}
94+
95+
it('Will never display the auth fallback if the user is already authenticated', async () => {
96+
const originalGetItem = global.Storage.prototype.getItem;
97+
jest
98+
.spyOn(global.Storage.prototype, 'getItem')
99+
.mockImplementation(key => {
100+
if (key === TOKEN_STORAGE_KEY) {
101+
return mockCoderAuthToken;
102+
}
103+
104+
return originalGetItem(key);
105+
});
106+
107+
renderAuthProvider(
108+
<>
109+
<MockTrackedComponent />
110+
<MockEndUserComponent />
111+
</>,
112+
);
113+
114+
await waitForNAuthenticatedComponents(2);
115+
const authFallbackTrigger = screen.queryByRole('button', {
116+
name: fallbackTriggerMatcher,
117+
});
118+
119+
expect(authFallbackTrigger).not.toBeInTheDocument();
120+
});
121+
122+
it('Will display an auth fallback input when there are no Coder components to be tracked and does not consider users of', async () => {
123+
renderAuthProvider(<></>);
124+
const authFallbackTrigger = await screen.findByRole('button', {
125+
name: fallbackTriggerMatcher,
126+
});
127+
128+
expect(authFallbackTrigger).toBeInTheDocument();
129+
});
130+
131+
it('Will never display the auth fallback if there are components being tracked', () => {
132+
renderAuthProvider(<MockTrackedComponent />);
133+
const authFallbackTrigger = screen.queryByRole('button', {
134+
name: fallbackTriggerMatcher,
135+
});
136+
137+
expect(authFallbackTrigger).not.toBeInTheDocument();
138+
});
139+
140+
it(`Does not consider users of ${useEndUserCoderAuth.name} when deciding whether to show fallback auth UI`, async () => {
141+
renderAuthProvider(<MockEndUserComponent />);
142+
const authFallbackTrigger = await screen.findByRole('button', {
143+
name: fallbackTriggerMatcher,
144+
});
145+
146+
expect(authFallbackTrigger).toBeInTheDocument();
147+
});
148+
149+
it.skip('Lets the user go through a full authentication flow via the fallback auth UI', () => {
150+
expect.hasAssertions();
151+
});
152+
});
153+
});

plugins/backstage-plugin-coder/src/components/CoderProvider/CoderAuthProvider.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ import { coderClientApiRef } from '../../api/CoderClient';
2727
import { CoderLogo } from '../CoderLogo';
2828
import { CoderAuthFormDialog } from '../CoderAuthFormDialog';
2929

30-
const BACKSTAGE_APP_ROOT_ID = '#root';
30+
export const TOKEN_STORAGE_KEY = 'coder-backstage-plugin/token';
31+
export const BACKSTAGE_APP_ROOT_ID = '#root';
3132
const FALLBACK_UI_OVERRIDE_CLASS_NAME = 'backstage-root-override';
32-
const TOKEN_STORAGE_KEY = 'coder-backstage-plugin/token';
3333

3434
// Handles auth edge case where a previously-valid token can't be verified. Not
3535
// immediately removing token to provide better UX in case someone's internet

0 commit comments

Comments
 (0)