-
Notifications
You must be signed in to change notification settings - Fork 7
chore(Coder plugin): add tests for auth fallback UI #129
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
Open
buenos-nachos
wants to merge
65
commits into
main
Choose a base branch
from
mes/auth-fallback-tests
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
65 commits
Select commit
Hold shift + click to select a range
9db4173
wip: commit progress on fallback UI
buenos-nachos 50d9008
chore: move dep to peer dependencies
buenos-nachos c57d4c9
wip: commit more progress
buenos-nachos e567260
wip: more progress
buenos-nachos 61ee528
refactor: consolidate card logic
buenos-nachos 9b16cc2
fix: update component tracking hooks
buenos-nachos 344593d
fix: add a11y landmark to auth fallback
buenos-nachos 2041f17
wip: commit more style progress
buenos-nachos 43cfd52
wip: commit more progress
buenos-nachos 560d009
wip: more progress
buenos-nachos 98faa12
wip: cleanup current approach
buenos-nachos a73b1b1
wip: commit progress on observer approach
buenos-nachos 0011dd2
wip: fix infinite loop for mutation logic
buenos-nachos b8bc1ef
fix: prevent padding patches from firing too often
buenos-nachos 2112995
fix: improve scoping of style overrides
buenos-nachos d04274c
chore: finish intial version of fallback stylling
buenos-nachos ae85984
fix: tidy up types
buenos-nachos 6e18204
wip: create initial version of dialog form
buenos-nachos f097f9b
wip: commit progress on modal
buenos-nachos e1a70dd
chore: finish styling for modal wrapper
buenos-nachos c4101f6
fix: update padding for FormDialog
buenos-nachos f8bb852
wip: start extracting out auth form
buenos-nachos 98c96af
fix: add missing barrel export file
buenos-nachos a404ddb
fix: make sure that auth form isn't dismissed early
buenos-nachos af2f383
fix: update auth imports
buenos-nachos 5b04628
fix: update spacing for auth modal
buenos-nachos 8ae3d14
refactor: clean up auth provider for clarity
buenos-nachos 1b1f8c4
docs: rewrite comment for clarity
buenos-nachos 1ea39e0
fix: improve granularity between official Coder components and user c…
buenos-nachos 9236ca4
fix: update all internal consumers of useCoderAuth
buenos-nachos b90ebd0
wip: commit initial version of useCoderQuery helper hook
buenos-nachos 12a7b3e
refactor: rename hooks to avoid confusion
buenos-nachos c146788
fix: update exports for plugin
buenos-nachos e7e7755
docs: fill in incomplete sentence
buenos-nachos 8493080
wip: commit initial version of useMutation wrapper
buenos-nachos 8087e19
refactor: extract retry factor into global constant
buenos-nachos 20fa328
merge
buenos-nachos ca257ae
fix: add explicit return type to useCoderMutation
buenos-nachos a92ec05
wip: start extracting auth logic into better reusable components
buenos-nachos 028c6b7
fix: update card to have better styling for body
buenos-nachos aea9345
wip: commit progress on style refactoring
buenos-nachos ceb65e1
fix: update vertical padding for card wrapper
buenos-nachos 8d1343f
chore: delete CoderAuthWrapper component
buenos-nachos 91e0702
fix: update styling for auth fallback
buenos-nachos b644eec
chore: shrink size of PR
buenos-nachos b127195
fix: update imports
buenos-nachos 7118896
docs: add comment about description setup
buenos-nachos b364e0c
fix: remove risk of runtime render errors in auth form
buenos-nachos 39f9a06
fix: update imports
buenos-nachos 1148eae
fix: update font sizes to use relative units
buenos-nachos 5aca4f7
fix: update peer dependencies for react-dom
buenos-nachos b04482e
refactor: clean up auth revalidation logic
buenos-nachos 063006c
wip: start updating tests for new code changes
buenos-nachos 1702755
fix: adding missing test case for auth card
buenos-nachos 1d928bb
wip: commit progress on auth form test updates
buenos-nachos 34f027f
fix: removal vetigal properties
buenos-nachos e0a4760
fix: get all CoderAuthForm tests passing
buenos-nachos 15a4e22
fix: update import for auth hook in test
buenos-nachos ba43e8a
wip: commit progress on tests
buenos-nachos 06c128a
wip: commit more test progress
buenos-nachos fc9f83d
wip: commit progress on fixing test isolation
buenos-nachos 44801af
chore: finish all tests for auth fallback logic
buenos-nachos 26c65ed
refactor: clean up setup code and revise comments
buenos-nachos 719a9a6
Merge branch 'main' into mes/auth-fallback-tests
buenos-nachos c553dc3
fix: remove risks of race conditions in tests
buenos-nachos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
193 changes: 193 additions & 0 deletions
193
plugins/backstage-plugin-coder/src/components/CoderProvider/CoderAuthProvider.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| /** | ||
| * @file Ideally all the files in CoderProvider could be treated as | ||
| * implementation details, and we could have a single test file for all the | ||
| * pieces joined together. | ||
| * | ||
| * But because the auth is so complicated, it helps to have tests just for it. | ||
| * | ||
| * --- | ||
| * @todo 2024-05-23 - Right now, there is a conflict when you try to call | ||
| * Backstage's wrapInTestApp and also try to mock out localStorage. They | ||
| * interact in such a way that when you call your mock's getItem method, it | ||
| * immediately throws an error. Didn't want to get rid of wrapInTestApp, because | ||
| * then that would require removing official Backstage components. wrapInTestApp | ||
| * sets up a lot of things behind the scenes like React Router that these | ||
| * components rely on. | ||
| * | ||
| * Figured out a way to write the tests that didn't involve extra mocking, but | ||
| * it's not as airtight as it could be. Definitely worth opening an issue with | ||
| * the Backstage repo upstream. | ||
| */ | ||
| import React, { type ReactNode } from 'react'; | ||
| import { render, screen, waitFor, within } from '@testing-library/react'; | ||
| import { | ||
| BACKSTAGE_APP_ROOT_ID, | ||
| CoderAuthProvider, | ||
| TOKEN_STORAGE_KEY, | ||
| useEndUserCoderAuth, | ||
| useInternalCoderAuth, | ||
| } from './CoderAuthProvider'; | ||
| import { CoderClient, coderClientApiRef } from '../../api/CoderClient'; | ||
| import { | ||
| getMockConfigApi, | ||
| getMockDiscoveryApi, | ||
| getMockIdentityApi, | ||
| mockAppConfig, | ||
| mockCoderAuthToken, | ||
| } from '../../testHelpers/mockBackstageData'; | ||
| import { UrlSync } from '../../api/UrlSync'; | ||
| import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils'; | ||
| import { QueryClientProvider } from '@tanstack/react-query'; | ||
| import { getMockQueryClient } from '../../testHelpers/setup'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import { CoderAppConfigProvider } from './CoderAppConfigProvider'; | ||
|
|
||
| afterEach(() => { | ||
| jest.restoreAllMocks(); | ||
|
|
||
| const appRootNodes = document.querySelectorAll(BACKSTAGE_APP_ROOT_ID); | ||
| appRootNodes.forEach(node => node.remove()); | ||
| window.localStorage.removeItem(TOKEN_STORAGE_KEY); | ||
| }); | ||
|
|
||
| function renderAuthProvider(children?: ReactNode) { | ||
| const urlSync = new UrlSync({ | ||
| apis: { | ||
| configApi: getMockConfigApi(), | ||
| discoveryApi: getMockDiscoveryApi(), | ||
| }, | ||
| }); | ||
|
|
||
| const queryClient = getMockQueryClient(); | ||
| const identityApi = getMockIdentityApi(); | ||
|
|
||
| // Can't use initialToken property, because then the Auth provider won't be | ||
| // aware of it. When testing for UI authentication, we need to feed the token | ||
| // from localStorage to the provider, which then feeds it to the client while | ||
| // keeping track of the React state changes | ||
| const coderClient = new CoderClient({ | ||
| apis: { urlSync, identityApi }, | ||
| }); | ||
|
|
||
| const mockAppRoot = document.createElement('div'); | ||
| mockAppRoot.id = BACKSTAGE_APP_ROOT_ID; | ||
| document.body.append(mockAppRoot); | ||
|
|
||
| return render( | ||
| <TestApiProvider apis={[[coderClientApiRef, coderClient]]}> | ||
| <QueryClientProvider client={queryClient}> | ||
| <CoderAppConfigProvider appConfig={mockAppConfig}> | ||
| <CoderAuthProvider>{children}</CoderAuthProvider> | ||
| </CoderAppConfigProvider> | ||
| </QueryClientProvider> | ||
| </TestApiProvider>, | ||
| { | ||
| baseElement: mockAppRoot, | ||
| wrapper: ({ children }) => wrapInTestApp(children), | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| describe(`${CoderAuthProvider.name}`, () => { | ||
| /** | ||
| * @todo Figure out what general auth state logic could benefit from tests | ||
| * and put them in a separate describe block | ||
| */ | ||
| describe('Fallback auth input', () => { | ||
| const fallbackTriggerMatcher = /Authenticate with Coder/; | ||
|
|
||
| function MockTrackedComponent() { | ||
| const auth = useInternalCoderAuth(); | ||
| return <p>Authenticated? {auth.isAuthenticated ? 'Yes!' : 'No...'}</p>; | ||
| } | ||
|
|
||
| function MockEndUserComponent() { | ||
| const auth = useEndUserCoderAuth(); | ||
| return <p>Authenticated? {auth.isAuthenticated ? 'Yes!' : 'No...'}</p>; | ||
| } | ||
|
|
||
| it('Will never display the auth fallback if the user is already authenticated', async () => { | ||
| /** | ||
| * Not 100% sure on why this works. We load the token in before rendering, | ||
| * so that we can bring the token into the UI on the initial render | ||
| * | ||
| * But as part of that rendering, wrapInTestApp eventually replaces | ||
| * localStorage with a mock. So the initial state is getting carried over | ||
| * to the mock? Or maybe it's not really a full mock and is just a spy? | ||
| */ | ||
| window.localStorage.setItem(TOKEN_STORAGE_KEY, mockCoderAuthToken); | ||
|
|
||
| renderAuthProvider( | ||
| <> | ||
| <MockTrackedComponent /> | ||
| <MockEndUserComponent /> | ||
| </>, | ||
| ); | ||
|
|
||
| await waitFor(() => { | ||
| const authenticatedComponents = screen.getAllByText(/Yes!/); | ||
| expect(authenticatedComponents).toHaveLength(2); | ||
| }); | ||
|
|
||
| const authFallbackTrigger = screen.queryByRole('button', { | ||
| name: fallbackTriggerMatcher, | ||
| }); | ||
|
|
||
| expect(authFallbackTrigger).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('Will display an auth fallback input when there are no components on screen that use Coder plugin logic', async () => { | ||
| renderAuthProvider(); | ||
| const authFallbackTrigger = await screen.findByRole('button', { | ||
| name: fallbackTriggerMatcher, | ||
| }); | ||
|
|
||
| expect(authFallbackTrigger).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('Will never display the auth fallback if there are tracked Coder components that let you submit auth info in other ways', async () => { | ||
| renderAuthProvider(<MockTrackedComponent />); | ||
|
|
||
| const authFallbackTrigger = screen.queryByRole('button', { | ||
| name: fallbackTriggerMatcher, | ||
| }); | ||
|
|
||
| await screen.findByText(/No\.\.\./); | ||
| expect(authFallbackTrigger).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it(`Does not consider users of ${useEndUserCoderAuth.name} when deciding whether to show fallback auth UI`, async () => { | ||
| renderAuthProvider(<MockEndUserComponent />); | ||
| const authFallbackTrigger = await screen.findByRole('button', { | ||
| name: fallbackTriggerMatcher, | ||
| }); | ||
|
|
||
| expect(authFallbackTrigger).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('Lets the user go through a full authentication flow via the fallback auth UI', async () => { | ||
| renderAuthProvider(); | ||
| const user = userEvent.setup(); | ||
|
|
||
| const authFallbackTrigger = await screen.findByRole('button', { | ||
| name: fallbackTriggerMatcher, | ||
| }); | ||
|
|
||
| await user.click(authFallbackTrigger); | ||
| const authForm = await screen.findByRole('form', { | ||
| name: /Authenticate with Coder/, | ||
| }); | ||
|
|
||
| const tokenInput = await within(authForm).findByLabelText(/Auth token/); | ||
| await user.type(tokenInput, mockCoderAuthToken); | ||
|
|
||
| const submitButton = await within(authForm).findByRole('button', { | ||
| name: /Authenticate/, | ||
| }); | ||
|
|
||
| await user.click(submitButton); | ||
| expect(authForm).not.toBeInTheDocument(); | ||
| expect(authFallbackTrigger).not.toBeInTheDocument(); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just decided to tuck this in here to keep the layout effect logic more self-contained