200 lines
10 KiB
TypeScript
200 lines
10 KiB
TypeScript
import {QueryClient, QueryClientProvider} from '@tanstack/react-query'
|
|
import {render, screen, within} from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import {beforeEach, expect, test, vi} from 'vitest'
|
|
import {CognitoUsersPanel} from './CognitoUsersPanel'
|
|
import type {CloudResource} from '@/types/resource'
|
|
|
|
const api = vi.hoisted(() => ({
|
|
deleteFlatItem: vi.fn(),
|
|
getCloudResource: vi.fn(),
|
|
listFlatItems: vi.fn(),
|
|
listUserPoolGroups: vi.fn(),
|
|
putFlatItem: vi.fn(),
|
|
getFlatItem: vi.fn(),
|
|
updateUserPoolUser: vi.fn(),
|
|
setUserPoolUserGroups: vi.fn(),
|
|
createUserPoolGroup: vi.fn(),
|
|
deleteUserPoolGroup: vi.fn(),
|
|
testUserPoolSignIn: vi.fn(),
|
|
describeUserPoolClient: vi.fn(),
|
|
updateUserPoolClientOAuth: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/api/cloudProxyClient', () => api)
|
|
vi.mock('@/lib/accountStore', () => ({useAccountId: () => '000000000000'}))
|
|
|
|
const summary: CloudResource = {
|
|
id: 'us-east-1_8e65d229a', name: 'orders-users', cloud: 'aws', service: 'userpools', type: 'user-pool',
|
|
region: 'us-east-1', createdAt: null, metadata: {},
|
|
}
|
|
|
|
function pool(signInWith: 'email' | 'username'): CloudResource {
|
|
return {...summary, metadata: {signInWith, appClients: [{clientId: 'a8e5c7d38d3b4a1a97c9d822de', clientName: 'orders-users-client'}]}}
|
|
}
|
|
|
|
const alice = {
|
|
id: 'aa36ecc1-ed8a-43b5-84e9-988398c17436', collectionId: null, timestamp: '2026-10-23T16:01:22.000Z', metadata: {},
|
|
body: {username: 'aa36ecc1-ed8a-43b5-84e9-988398c17436', email: 'alice@example.com', status: 'CONFIRMED', enabled: true},
|
|
}
|
|
|
|
function renderPanel() {
|
|
const client = new QueryClient({defaultOptions: {queries: {retry: false}}})
|
|
return render(<QueryClientProvider client={client}><CognitoUsersPanel resource={summary} runtimeReachable/></QueryClientProvider>)
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
api.listFlatItems.mockResolvedValue({items: [alice], nextCursor: null})
|
|
api.putFlatItem.mockResolvedValue(alice)
|
|
api.deleteFlatItem.mockResolvedValue(undefined)
|
|
api.listUserPoolGroups.mockResolvedValue([{name: 'admins', description: 'Full access', precedence: 0, createdAt: null}])
|
|
api.getFlatItem.mockResolvedValue({...alice, body: {...alice.body, emailVerified: true, groups: [], attributes: {sub: alice.id, email: 'alice@example.com'}}})
|
|
api.updateUserPoolUser.mockResolvedValue(undefined)
|
|
api.setUserPoolUserGroups.mockResolvedValue(undefined)
|
|
api.createUserPoolGroup.mockResolvedValue({name: 'editors', description: null, precedence: null, createdAt: null})
|
|
})
|
|
|
|
test('shows the app client id and the pool users', async () => {
|
|
api.getCloudResource.mockResolvedValue(pool('email'))
|
|
renderPanel()
|
|
|
|
expect(await screen.findByText('a8e5c7d38d3b4a1a97c9d822de')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', {name: 'Copy Client ID'})).toBeInTheDocument()
|
|
expect(await screen.findByText('alice@example.com')).toBeInTheDocument()
|
|
expect(screen.getByText('CONFIRMED')).toBeInTheDocument()
|
|
expect(api.getCloudResource).toHaveBeenCalledWith('aws', 'userpools', summary.id, expect.anything())
|
|
})
|
|
|
|
test('lists the pool\'s Lambda triggers with links to the function and its logs', async () => {
|
|
api.getCloudResource.mockResolvedValue({...pool('email'), metadata: {...pool('email').metadata, lambdaTriggers: [
|
|
{trigger: 'PreTokenGeneration', label: 'Pre token generation', functionArn: 'arn:aws:lambda:us-east-1:000000000000:function:notes-demo-pre-token', functionName: 'notes-demo-pre-token', version: null},
|
|
]}})
|
|
renderPanel()
|
|
|
|
expect(await screen.findByText('Pre token generation')).toBeInTheDocument()
|
|
expect(screen.getByRole('link', {name: 'Open function notes-demo-pre-token'})).toHaveAttribute('href', '/cloud-explorer/aws/serverless?resource=notes-demo-pre-token')
|
|
expect(screen.getByRole('link', {name: 'Open logs for notes-demo-pre-token'})).toHaveAttribute('href', '/cloud-explorer/aws/logs?resource=%2Faws%2Flambda%2Fnotes-demo-pre-token')
|
|
})
|
|
|
|
test('says when a pool has no Lambda triggers', async () => {
|
|
api.getCloudResource.mockResolvedValue(pool('email'))
|
|
renderPanel()
|
|
|
|
expect(await screen.findByText('This pool runs no Lambda triggers.')).toBeInTheDocument()
|
|
})
|
|
|
|
test('turns on hosted sign-in for an app client', async () => {
|
|
const user = userEvent.setup()
|
|
const settings = {clientId: 'a8e5c7d38d3b4a1a97c9d822de', clientName: 'orders-users-client', hasSecret: false, explicitAuthFlows: ['ALLOW_REFRESH_TOKEN_AUTH'],
|
|
callbackUrls: [], logoutUrls: [], oauthFlows: [], oauthScopes: [], identityProviders: ['COGNITO']}
|
|
api.getCloudResource.mockResolvedValue(pool('email'))
|
|
api.describeUserPoolClient.mockResolvedValue(settings)
|
|
api.updateUserPoolClientOAuth.mockImplementation(async (_cloud, _pool, _client, input) => ({...settings, ...input}))
|
|
renderPanel()
|
|
|
|
await user.click(await screen.findByRole('button', {name: 'Manage app client orders-users-client'}))
|
|
const form = await screen.findByRole('form', {name: 'Hosted sign-in settings'})
|
|
expect(within(form).getByRole('checkbox', {name: /Client credentials/})).toBeDisabled()
|
|
await user.click(within(form).getByRole('checkbox', {name: /Authorization code/}))
|
|
await user.click(within(form).getByRole('checkbox', {name: 'openid'}))
|
|
await user.click(within(form).getByRole('checkbox', {name: 'email'}))
|
|
await user.type(within(form).getByLabelText(/Callback URLs/), 'http://localhost:4600/api/auth/callback')
|
|
await user.type(within(form).getByLabelText(/Sign-out URLs/), 'http://localhost:4600/')
|
|
await user.click(within(form).getByRole('button', {name: 'Save settings'}))
|
|
|
|
expect(api.updateUserPoolClientOAuth).toHaveBeenCalledWith('aws', summary.id, 'a8e5c7d38d3b4a1a97c9d822de', {
|
|
callbackUrls: ['http://localhost:4600/api/auth/callback'], logoutUrls: ['http://localhost:4600/'],
|
|
oauthFlows: ['code'], oauthScopes: ['openid', 'email'], identityProviders: ['COGNITO'],
|
|
})
|
|
expect(await screen.findByText(/can now send users to the hosted login page/)).toBeInTheDocument()
|
|
})
|
|
|
|
test('creates an email user who can sign in immediately', async () => {
|
|
const user = userEvent.setup()
|
|
api.getCloudResource.mockResolvedValue(pool('email'))
|
|
renderPanel()
|
|
|
|
await screen.findByText('a8e5c7d38d3b4a1a97c9d822de')
|
|
const form = screen.getByRole('form', {name: 'Create user'})
|
|
await user.type(within(form).getByLabelText('Email'), 'bob@example.com')
|
|
await user.type(within(form).getByLabelText('Password'), 'Passw0rd!')
|
|
await user.click(within(form).getByRole('button', {name: 'Create user'}))
|
|
|
|
expect(api.putFlatItem).toHaveBeenCalledWith('aws', 'userpools', summary.id, {email: 'bob@example.com', password: 'Passw0rd!', permanent: true})
|
|
})
|
|
|
|
test('creates a username user with a temporary password when asked', async () => {
|
|
const user = userEvent.setup()
|
|
api.getCloudResource.mockResolvedValue(pool('username'))
|
|
renderPanel()
|
|
|
|
await screen.findByText('a8e5c7d38d3b4a1a97c9d822de')
|
|
const form = screen.getByRole('form', {name: 'Create user'})
|
|
await user.type(within(form).getByLabelText('Username'), 'bob')
|
|
await user.type(within(form).getByLabelText('Password'), 'Passw0rd!')
|
|
await user.click(within(form).getByLabelText(/User can sign in now/))
|
|
await user.click(within(form).getByRole('button', {name: 'Create user'}))
|
|
|
|
expect(api.putFlatItem).toHaveBeenCalledWith('aws', 'userpools', summary.id, {username: 'bob', password: 'Passw0rd!', permanent: false})
|
|
})
|
|
|
|
test('deletes a user by its stored username', async () => {
|
|
const user = userEvent.setup()
|
|
api.getCloudResource.mockResolvedValue(pool('email'))
|
|
renderPanel()
|
|
|
|
await user.click(await screen.findByRole('button', {name: 'Delete user alice@example.com'}))
|
|
|
|
expect(api.deleteFlatItem).toHaveBeenCalledWith('aws', 'userpools', summary.id, alice.id)
|
|
})
|
|
|
|
test('manages a user: disables it and puts it in a group', async () => {
|
|
const user = userEvent.setup()
|
|
api.getCloudResource.mockResolvedValue(pool('email'))
|
|
renderPanel()
|
|
|
|
await user.click(await screen.findByRole('button', {name: 'Manage user alice@example.com'}))
|
|
await user.click(await screen.findByRole('button', {name: 'Disable'}))
|
|
expect(api.updateUserPoolUser).toHaveBeenCalledWith('aws', summary.id, alice.id, {action: 'disable'})
|
|
expect(await screen.findByText(/User disabled/)).toBeInTheDocument()
|
|
|
|
const groupBox = screen.getByRole('checkbox', {name: 'admins'})
|
|
await user.click(groupBox)
|
|
await user.click(screen.getByRole('button', {name: 'Save groups'}))
|
|
expect(api.setUserPoolUserGroups).toHaveBeenCalledWith('aws', summary.id, alice.id, ['admins'])
|
|
})
|
|
|
|
test('creates a group with its precedence', async () => {
|
|
const user = userEvent.setup()
|
|
api.getCloudResource.mockResolvedValue(pool('email'))
|
|
renderPanel()
|
|
|
|
await user.type(await screen.findByLabelText('Group name'), 'editors')
|
|
await user.type(screen.getByLabelText(/^Precedence/), '5')
|
|
await user.click(screen.getByRole('button', {name: 'Create group'}))
|
|
|
|
expect(api.createUserPoolGroup).toHaveBeenCalledWith('aws', summary.id, {name: 'editors', precedence: 5})
|
|
})
|
|
|
|
test('test sign-in shows the decoded claims, or why Cognito refused', async () => {
|
|
const user = userEvent.setup()
|
|
api.getCloudResource.mockResolvedValue(pool('email'))
|
|
api.testUserPoolSignIn
|
|
.mockResolvedValueOnce({outcome: 'rejected', code: 'NotAuthorizedException', message: 'Incorrect username or password.'})
|
|
.mockResolvedValueOnce({outcome: 'signed-in', expiresIn: 3600, idToken: {sub: alice.id, email: 'alice@example.com', 'cognito:groups': ['admins']}, accessToken: {sub: alice.id, client_id: 'a8e5c7d38d3b4a1a97c9d822de'}})
|
|
renderPanel()
|
|
|
|
await screen.findByText('a8e5c7d38d3b4a1a97c9d822de')
|
|
const form = screen.getByRole('form', {name: 'Test sign-in'})
|
|
await user.type(within(form).getByLabelText('Email'), 'alice@example.com')
|
|
await user.type(within(form).getByLabelText('Password'), 'wrong')
|
|
await user.click(within(form).getByRole('button', {name: 'Try sign-in'}))
|
|
expect(await screen.findByText('Incorrect username or password.')).toBeInTheDocument()
|
|
|
|
await user.click(within(form).getByRole('button', {name: 'Try sign-in'}))
|
|
expect(await screen.findByText(/valid for 60 minutes/)).toBeInTheDocument()
|
|
expect(screen.getByText('cognito:groups', {selector: 'th code'})).toBeInTheDocument()
|
|
expect(api.testUserPoolSignIn).toHaveBeenLastCalledWith('aws', summary.id, {clientId: 'a8e5c7d38d3b4a1a97c9d822de', username: 'alice@example.com', password: 'wrong'})
|
|
})
|