import {createHmac} from 'node:crypto' import {describe, expect, test} from 'bun:test' import { AdminAddUserToGroupCommand, AdminConfirmSignUpCommand, AdminDisableUserCommand, AdminListGroupsForUserCommand, AdminRemoveUserFromGroupCommand, AdminUserGlobalSignOutCommand, CreateGroupCommand, DeleteGroupCommand, DescribeUserPoolClientCommand, InitiateAuthCommand, ListGroupsCommand, AdminCreateUserCommand, AdminDeleteUserCommand, AdminGetUserCommand, AdminSetUserPasswordCommand, CreateUserPoolClientCommand, CreateUserPoolCommand, DeleteUserPoolCommand, DescribeUserPoolCommand, ListUserPoolClientsCommand, ListUserPoolsCommand, ListUsersCommand, UpdateUserPoolClientCommand, type CognitoIdentityProviderClient, } from '@aws-sdk/client-cognito-identity-provider' import {AwsUserPoolsAdapter} from './AwsUserPoolsAdapter' import {ConflictError, ValidationError} from '../cloud-spi/errors' type SendResult = Record function stubCognito(handler: (command: object) => SendResult | Promise) { const sent: object[] = [] const client = { async send(command: object) { sent.push(command) return handler(command) }, } as unknown as CognitoIdentityProviderClient return {client, sent} } function awsError(name: string): Error { return Object.assign(new Error(name), {name, $metadata: {httpStatusCode: 400}}) } const POOL_ID = 'us-east-1_8e65d229a' const CREATED = new Date('2026-10-23T16:01:22.000Z') const pool = { Id: POOL_ID, Name: 'orders-users', Arn: `arn:aws:cognito-idp:us-east-1:000000000000:userpool/${POOL_ID}`, Status: 'Enabled', CreationDate: CREATED, UsernameAttributes: ['email'], EstimatedNumberOfUsers: 1, MfaConfiguration: 'OFF', DeletionProtection: 'INACTIVE', Policies: {PasswordPolicy: {MinimumLength: 8, RequireUppercase: true, RequireLowercase: true, RequireNumbers: true, RequireSymbols: true}}, } const alice = { Username: 'aa36ecc1-ed8a-43b5-84e9-988398c17436', Attributes: [ {Name: 'sub', Value: 'aa36ecc1-ed8a-43b5-84e9-988398c17436'}, {Name: 'email', Value: 'alice@example.com'}, {Name: 'email_verified', Value: 'true'}, ], UserCreateDate: CREATED, UserLastModifiedDate: CREATED, Enabled: true, UserStatus: 'CONFIRMED', } function runtimeStub() { return stubCognito((command) => { if (command instanceof CreateUserPoolCommand) return {UserPool: pool} if (command instanceof DescribeUserPoolCommand) return {UserPool: pool} if (command instanceof ListUserPoolClientsCommand) return {UserPoolClients: [{ClientId: 'a8e5c7d38d3b4a1a97c9d822de', ClientName: 'orders-users-client', UserPoolId: POOL_ID}]} if (command instanceof AdminCreateUserCommand) return {User: {...alice, UserStatus: 'FORCE_CHANGE_PASSWORD'}} if (command instanceof AdminGetUserCommand) return {...alice, UserAttributes: alice.Attributes} return {} }) } describe('AwsUserPoolsAdapter', () => { test('lists every page of user pools and filters by name or id', async () => { const {client, sent} = stubCognito((command) => { if (!(command instanceof ListUserPoolsCommand)) return {} return command.input.NextToken ? {UserPools: [{Id: 'us-east-1_second', Name: 'billing', Status: 'Enabled', CreationDate: CREATED}]} : {UserPools: [{Id: POOL_ID, Name: 'orders-users', Status: 'Enabled', CreationDate: CREATED}], NextToken: 'page-2'} }) const adapter = new AwsUserPoolsAdapter(client) expect((await adapter.list()).map((resource) => resource.id)).toEqual([POOL_ID, 'us-east-1_second']) expect(sent.filter((command) => command instanceof ListUserPoolsCommand)).toHaveLength(2) expect((await adapter.list({search: 'BILL'})).map((resource) => resource.name)).toEqual(['billing']) expect((await adapter.list())[0]).toMatchObject({type: 'user-pool', region: 'us-east-1', status: 'Enabled', createdAt: CREATED.toISOString()}) }) test('lists pools with the details the inspector shows', async () => { const {client} = stubCognito((command) => { if (command instanceof ListUserPoolsCommand) return {UserPools: [{Id: POOL_ID, Name: 'orders-users', Status: 'Enabled', CreationDate: CREATED}]} if (command instanceof DescribeUserPoolCommand) return {UserPool: pool} if (command instanceof ListUserPoolClientsCommand) return {UserPoolClients: [{ClientId: 'a8e5c7d38d3b4a1a97c9d822de', ClientName: 'orders-users-client'}]} return {} }) const [listed] = await new AwsUserPoolsAdapter(client).list() expect(listed.metadata).toMatchObject({signInWith: 'email', appClients: [{clientId: 'a8e5c7d38d3b4a1a97c9d822de', clientName: 'orders-users-client'}]}) }) test('describes a pool with its sign-in method, password policy and app clients', async () => { const resource = await new AwsUserPoolsAdapter(runtimeStub().client).get(POOL_ID) expect(resource?.metadata).toEqual({ arn: pool.Arn, signInWith: 'email', passwordPolicy: '8+ characters with uppercase, lowercase, number, symbol', estimatedNumberOfUsers: 1, userCount: 0, mfaConfiguration: 'OFF', deletionProtection: 'INACTIVE', appClients: [{clientId: 'a8e5c7d38d3b4a1a97c9d822de', clientName: 'orders-users-client'}], lambdaTriggers: [], }) }) test('lists the pool\'s Lambda triggers in sign-in order, with the trigger version when set', async () => { const arn = (name: string) => `arn:aws:lambda:us-east-1:000000000000:function:${name}` const {client} = stubCognito((command) => { if (command instanceof DescribeUserPoolCommand) return {UserPool: {...pool, LambdaConfig: { PreTokenGeneration: arn('claims'), PreTokenGenerationConfig: {LambdaArn: arn('claims'), LambdaVersion: 'V2_0'}, PreSignUp: `${arn('signup')}:live`, CustomEmailSender: {LambdaArn: arn('mailer'), LambdaVersion: 'V1_0'}, KMSKeyID: 'arn:aws:kms:us-east-1:000000000000:key/1', }}} return {} }) const resource = await new AwsUserPoolsAdapter(client).get(POOL_ID) expect(resource?.metadata.lambdaTriggers).toEqual([ {trigger: 'PreSignUp', label: 'Pre sign-up', functionArn: `${arn('signup')}:live`, functionName: 'signup', version: null}, {trigger: 'PreTokenGeneration', label: 'Pre token generation', functionArn: arn('claims'), functionName: 'claims', version: 'V2_0'}, {trigger: 'CustomEmailSender', label: 'Custom email sender', functionArn: arn('mailer'), functionName: 'mailer', version: 'V1_0'}, ]) }) test('counts users itself, since the runtime leaves EstimatedNumberOfUsers at 0', async () => { const {client} = stubCognito((command) => { if (command instanceof DescribeUserPoolCommand) return {UserPool: {...pool, EstimatedNumberOfUsers: 0}} if (command instanceof ListUsersCommand) return command.input.PaginationToken ? {Users: [alice]} : {Users: [alice, alice], PaginationToken: 'page-2'} return {} }) const resource = await new AwsUserPoolsAdapter(client).get(POOL_ID) expect(resource?.metadata.userCount).toBe(3) expect(resource?.metadata.estimatedNumberOfUsers).toBe(0) }) test('summarises a relaxed password policy without character rules', async () => { const {client} = stubCognito((command) => { if (command instanceof DescribeUserPoolCommand) return {UserPool: {...pool, Policies: {PasswordPolicy: {MinimumLength: 8, RequireUppercase: false, RequireLowercase: false, RequireNumbers: false, RequireSymbols: false}}}} return {} }) expect((await new AwsUserPoolsAdapter(client).get(POOL_ID))?.metadata.passwordPolicy).toBe('8+ characters') }) test('returns null for a pool the runtime does not know', async () => { const {client} = stubCognito(() => { throw awsError('ResourceNotFoundException') }) expect(await new AwsUserPoolsAdapter(client).get('us-east-1_missing')).toBeNull() }) test('creates an email pool with a public app client by default', async () => { const {client, sent} = runtimeStub() await new AwsUserPoolsAdapter(client).create({values: {name: 'orders-users'}}) const createPool = sent.find((command) => command instanceof CreateUserPoolCommand) as CreateUserPoolCommand expect(createPool.input).toEqual({PoolName: 'orders-users', UsernameAttributes: ['email'], AutoVerifiedAttributes: ['email']}) const createClient = sent.find((command) => command instanceof CreateUserPoolClientCommand) as CreateUserPoolClientCommand expect(createClient.input).toEqual({ UserPoolId: POOL_ID, ClientName: 'orders-users-client', GenerateSecret: false, ExplicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_USER_SRP_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'], }) }) test('creates a username pool with a relaxed password policy and no app client', async () => { const {client, sent} = runtimeStub() await new AwsUserPoolsAdapter(client).create({values: {name: 'testers', signInWith: 'username', passwordPolicy: 'relaxed', createAppClient: 'no'}}) const createPool = sent.find((command) => command instanceof CreateUserPoolCommand) as CreateUserPoolCommand expect(createPool.input).toEqual({ PoolName: 'testers', Policies: {PasswordPolicy: {MinimumLength: 8, RequireUppercase: false, RequireLowercase: false, RequireNumbers: false, RequireSymbols: false}}, }) expect(sent.some((command) => command instanceof CreateUserPoolClientCommand)).toBe(false) }) test('removes the new pool when its app client cannot be created', async () => { const {client, sent} = stubCognito((command) => { if (command instanceof CreateUserPoolCommand) return {UserPool: pool} if (command instanceof CreateUserPoolClientCommand) throw awsError('InvalidParameterException') return {} }) await expect(new AwsUserPoolsAdapter(client).create({values: {name: 'orders-users'}})).rejects.toThrow('InvalidParameterException') expect((sent.at(-1) as DeleteUserPoolCommand).input).toEqual({UserPoolId: POOL_ID}) }) test('rejects invalid create input before calling the runtime', async () => { const {client, sent} = runtimeStub() const adapter = new AwsUserPoolsAdapter(client) await expect(adapter.create({values: {}})).rejects.toBeInstanceOf(ValidationError) await expect(adapter.create({values: {name: 'bad/name'}})).rejects.toBeInstanceOf(ValidationError) await expect(adapter.create({values: {name: 'ok', signInWith: 'phone'}})).rejects.toBeInstanceOf(ValidationError) expect(sent).toHaveLength(0) }) test('deletes a pool by id', async () => { const {client, sent} = runtimeStub() await new AwsUserPoolsAdapter(client).delete(POOL_ID) expect(sent[0]).toBeInstanceOf(DeleteUserPoolCommand) expect((sent[0] as DeleteUserPoolCommand).input).toEqual({UserPoolId: POOL_ID}) }) }) describe('AwsUserPoolsAdapter users', () => { test('lists users as items keyed by username with their email and status', async () => { const {client, sent} = stubCognito(() => ({Users: [alice], PaginationToken: 'next-page'})) const page = await new AwsUserPoolsAdapter(client).items.listItems(POOL_ID, {limit: 500, cursor: 'this-page'}) expect((sent[0] as ListUsersCommand).input).toEqual({UserPoolId: POOL_ID, Limit: 60, PaginationToken: 'this-page'}) expect(page.nextCursor).toBe('next-page') expect(page.items).toEqual([{ id: alice.Username, collectionId: null, timestamp: CREATED.toISOString(), body: { username: alice.Username, email: 'alice@example.com', emailVerified: true, status: 'CONFIRMED', enabled: true, lastModifiedAt: CREATED.toISOString(), attributes: {sub: alice.Username, email: 'alice@example.com', email_verified: 'true'}, }, metadata: {}, }]) }) test('creates a user whose password is permanent so it can sign in at once', async () => { const {client, sent} = runtimeStub() const user = await new AwsUserPoolsAdapter(client).items.putItem!(POOL_ID, {email: 'alice@example.com', password: ' Passw0rd! '}) expect((sent[0] as AdminCreateUserCommand).input).toEqual({ UserPoolId: POOL_ID, Username: 'alice@example.com', UserAttributes: [{Name: 'email', Value: 'alice@example.com'}, {Name: 'email_verified', Value: 'true'}], TemporaryPassword: ' Passw0rd! ', MessageAction: 'SUPPRESS', }) expect((sent[1] as AdminSetUserPasswordCommand).input).toEqual({UserPoolId: POOL_ID, Username: alice.Username, Password: ' Passw0rd! ', Permanent: true}) expect(user.body.status).toBe('CONFIRMED') }) test('leaves the password temporary when asked', async () => { const {client, sent} = runtimeStub() await new AwsUserPoolsAdapter(client).items.putItem!(POOL_ID, {username: 'bob', password: 'Passw0rd!', permanent: false}) expect((sent[0] as AdminCreateUserCommand).input.Username).toBe('bob') expect((sent[0] as AdminCreateUserCommand).input.UserAttributes).toEqual([]) expect(sent.some((command) => command instanceof AdminSetUserPasswordCommand)).toBe(false) }) test('rejects a user without a name, password or valid email', async () => { const {client, sent} = runtimeStub() const users = new AwsUserPoolsAdapter(client).items await expect(users.putItem!(POOL_ID, {password: 'Passw0rd!'})).rejects.toThrow('Provide a username or an email address') await expect(users.putItem!(POOL_ID, {email: 'alice@example.com'})).rejects.toThrow('password is required') await expect(users.putItem!(POOL_ID, {email: 'not-an-email', password: 'Passw0rd!'})).rejects.toThrow('email must be a valid email address') await expect(users.putItem!(POOL_ID, {username: 'bob', password: 'Passw0rd!', permanent: 'yes'})).rejects.toThrow('permanent must be true or false') expect(sent).toHaveLength(0) }) test('returns null for a missing user and deletes by username', async () => { const missing = stubCognito(() => { throw awsError('UserNotFoundException') }) expect(await new AwsUserPoolsAdapter(missing.client).items.getItem!(POOL_ID, 'nobody')).toBeNull() const {client, sent} = runtimeStub() await new AwsUserPoolsAdapter(client).items.deleteItem!(POOL_ID, alice.Username) expect((sent[0] as AdminDeleteUserCommand).input).toEqual({UserPoolId: POOL_ID, Username: alice.Username}) }) }) function jwt(claims: Record): string { return ['{"alg":"RS256"}', JSON.stringify(claims), 'sig'].map((part) => Buffer.from(part).toString('base64url')).join('.') } describe('AwsUserPoolsAdapter user actions and groups', () => { test('reads a user with the groups it belongs to', async () => { const {client, sent} = stubCognito((command) => { if (command instanceof AdminGetUserCommand) return {Username: alice.Username, UserAttributes: alice.Attributes, UserStatus: 'CONFIRMED', Enabled: true, UserCreateDate: CREATED} if (command instanceof AdminListGroupsForUserCommand) return {Groups: [{GroupName: 'admins'}]} return {} }) const user = await new AwsUserPoolsAdapter(client).items.getItem!(POOL_ID, alice.Username) expect(user?.body.groups).toEqual(['admins']) expect((sent[1] as AdminListGroupsForUserCommand).input).toMatchObject({UserPoolId: POOL_ID, Username: alice.Username}) }) test('runs each user action as its Admin call', async () => { const {client, sent} = stubCognito(() => ({})) const adapter = new AwsUserPoolsAdapter(client) await adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'disable'}) await adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'confirm'}) await adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'signOut'}) await adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'setPassword', password: ' Passw0rd! '}) expect(sent[0]).toBeInstanceOf(AdminDisableUserCommand) expect(sent[1]).toBeInstanceOf(AdminConfirmSignUpCommand) expect(sent[2]).toBeInstanceOf(AdminUserGlobalSignOutCommand) expect((sent[3] as AdminSetUserPasswordCommand).input).toEqual({UserPoolId: POOL_ID, Username: 'bob', Password: ' Passw0rd! ', Permanent: true}) }) test('rejects unknown actions and reports a refused one as a conflict, never a 401', async () => { const {client} = stubCognito(() => { throw awsError('NotAuthorizedException') }) const adapter = new AwsUserPoolsAdapter(client) await expect(adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'explode'} as never)).rejects.toBeInstanceOf(ValidationError) await expect(adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'setPassword'} as never)).rejects.toBeInstanceOf(ValidationError) await expect(adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'confirm'})).rejects.toBeInstanceOf(ConflictError) }) test('sets a user\'s groups by adding and removing only the difference', async () => { const {client, sent} = stubCognito((command) => command instanceof AdminListGroupsForUserCommand ? {Groups: [{GroupName: 'staff'}, {GroupName: 'beta'}]} : {}) await new AwsUserPoolsAdapter(client).setUserPoolUserGroups(POOL_ID, 'bob', ['staff', 'admins']) const changes = sent.slice(1) expect(changes).toHaveLength(2) expect(changes[0]).toBeInstanceOf(AdminAddUserToGroupCommand) expect((changes[0] as AdminAddUserToGroupCommand).input.GroupName).toBe('admins') expect(changes[1]).toBeInstanceOf(AdminRemoveUserFromGroupCommand) expect((changes[1] as AdminRemoveUserFromGroupCommand).input.GroupName).toBe('beta') }) test('lists, creates and deletes groups', async () => { const {client, sent} = stubCognito((command) => { if (command instanceof ListGroupsCommand) return {Groups: [{GroupName: 'admins', Description: 'Full access', Precedence: 0, CreationDate: CREATED}]} if (command instanceof CreateGroupCommand) return {Group: {GroupName: command.input.GroupName, Precedence: command.input.Precedence, CreationDate: CREATED}} return {} }) const adapter = new AwsUserPoolsAdapter(client) expect(await adapter.listUserPoolGroups(POOL_ID)).toEqual([{name: 'admins', description: 'Full access', precedence: 0, createdAt: CREATED.toISOString()}]) expect(await adapter.createUserPoolGroup(POOL_ID, {name: 'editors', precedence: 5})).toMatchObject({name: 'editors', precedence: 5}) await adapter.deleteUserPoolGroup(POOL_ID, 'editors') expect((sent[1] as CreateGroupCommand).input).toEqual({UserPoolId: POOL_ID, GroupName: 'editors', Precedence: 5}) expect(sent[2]).toBeInstanceOf(DeleteGroupCommand) }) test('validates a group before creating it', async () => { const {client, sent} = stubCognito(() => ({})) const adapter = new AwsUserPoolsAdapter(client) await expect(adapter.createUserPoolGroup(POOL_ID, {name: 'has space'})).rejects.toBeInstanceOf(ValidationError) await expect(adapter.createUserPoolGroup(POOL_ID, {name: 'ok', precedence: -1})).rejects.toBeInstanceOf(ValidationError) expect(sent).toHaveLength(0) }) test('test sign-in returns decoded claims, adding SECRET_HASH for a client with a secret', async () => { const {client, sent} = stubCognito((command) => { if (command instanceof DescribeUserPoolClientCommand) return {UserPoolClient: {ClientId: 'client-1', ClientSecret: 'shh'}} if (command instanceof InitiateAuthCommand) return {AuthenticationResult: {ExpiresIn: 3600, IdToken: jwt({sub: 's-1', email: 'alice@example.com', token_use: 'id'}), AccessToken: jwt({sub: 's-1', token_use: 'access'})}} return {} }) const result = await new AwsUserPoolsAdapter(client).testUserPoolSignIn(POOL_ID, {clientId: 'client-1', username: 'alice@example.com', password: 'Passw0rd!'}) expect(result).toEqual({outcome: 'signed-in', expiresIn: 3600, idToken: {sub: 's-1', email: 'alice@example.com', token_use: 'id'}, accessToken: {sub: 's-1', token_use: 'access'}}) const auth = (sent[1] as InitiateAuthCommand).input expect(auth.AuthFlow).toBe('USER_PASSWORD_AUTH') expect(auth.AuthParameters?.SECRET_HASH).toBe(createHmac('sha256', 'shh').update('alice@example.comclient-1').digest('base64')) }) test('test sign-in reports a wrong password or a challenge as its outcome', async () => { const refusing = stubCognito((command) => { if (command instanceof InitiateAuthCommand) throw Object.assign(awsError('NotAuthorizedException'), {message: 'Incorrect username or password.'}) return {UserPoolClient: {ClientId: 'client-1'}} }) expect(await new AwsUserPoolsAdapter(refusing.client).testUserPoolSignIn(POOL_ID, {clientId: 'client-1', username: 'a', password: 'x'})) .toEqual({outcome: 'rejected', code: 'NotAuthorizedException', message: 'Incorrect username or password.'}) expect((refusing.sent[1] as InitiateAuthCommand).input.AuthParameters?.SECRET_HASH).toBeUndefined() const challenging = stubCognito((command) => command instanceof InitiateAuthCommand ? {ChallengeName: 'NEW_PASSWORD_REQUIRED'} : {UserPoolClient: {}}) expect(await new AwsUserPoolsAdapter(challenging.client).testUserPoolSignIn(POOL_ID, {clientId: 'c', username: 'a', password: 'x'})) .toEqual({outcome: 'challenge', challengeName: 'NEW_PASSWORD_REQUIRED'}) }) }) describe('app client OAuth settings', () => { const CLIENT_ID = '40808d60e188483d876bbbf646' const stored = { UserPoolId: POOL_ID, ClientId: CLIENT_ID, ClientName: 'notes-demo-web', ExplicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'], PreventUserExistenceErrors: 'ENABLED', RefreshTokenValidity: 30, EnableTokenRevocation: true, SupportedIdentityProviders: ['COGNITO'], } function clientStub() { return stubCognito((command) => { if (command instanceof DescribeUserPoolClientCommand) return {UserPoolClient: stored} if (command instanceof UpdateUserPoolClientCommand) return {UserPoolClient: {...stored, ...command.input}} return {} }) } test('reads a client with no OAuth set up', async () => { const settings = await new AwsUserPoolsAdapter(clientStub().client).describeUserPoolClient(POOL_ID, CLIENT_ID) expect(settings).toEqual({ clientId: CLIENT_ID, clientName: 'notes-demo-web', hasSecret: false, explicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'], callbackUrls: [], logoutUrls: [], oauthFlows: [], oauthScopes: [], identityProviders: ['COGNITO'], }) }) test('turns on the code flow and sends the client\'s other settings back unchanged', async () => { const {client, sent} = clientStub() const settings = await new AwsUserPoolsAdapter(client).updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, { callbackUrls: ['http://localhost:4600/api/auth/callback', ' http://localhost:4600/api/auth/callback '], logoutUrls: ['http://localhost:4600/'], oauthFlows: ['code'], oauthScopes: ['openid', 'email'], identityProviders: ['COGNITO'], }) const update = sent.find((command) => command instanceof UpdateUserPoolClientCommand) as UpdateUserPoolClientCommand expect(update.input).toMatchObject({ ClientName: 'notes-demo-web', ExplicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'], PreventUserExistenceErrors: 'ENABLED', RefreshTokenValidity: 30, EnableTokenRevocation: true, CallbackURLs: ['http://localhost:4600/api/auth/callback'], LogoutURLs: ['http://localhost:4600/'], AllowedOAuthFlows: ['code'], AllowedOAuthScopes: ['openid', 'email'], AllowedOAuthFlowsUserPoolClient: true, SupportedIdentityProviders: ['COGNITO'], }) expect(settings.oauthFlows).toEqual(['code']) }) test('applies Cognito\'s rules before calling it', async () => { const {client, sent} = clientStub() const adapter = new AwsUserPoolsAdapter(client) const valid = {callbackUrls: ['https://app.example/callback'], logoutUrls: [], oauthFlows: ['code' as const], oauthScopes: ['openid'], identityProviders: ['COGNITO']} await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, callbackUrls: ['http://app.example/callback']})).rejects.toThrow(/https/) await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, callbackUrls: ['https://app.example/#done']})).rejects.toThrow(/fragment/) await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, callbackUrls: []})).rejects.toThrow(/callback URL/) await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, oauthScopes: []})).rejects.toThrow(/scope/) await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, oauthFlows: ['code', 'client_credentials']})).rejects.toThrow(ValidationError) await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, oauthFlows: ['client_credentials']})).rejects.toThrow(/secret/) expect(sent.some((command) => command instanceof UpdateUserPoolClientCommand)).toBe(false) }) })