mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor: fix tests
This commit is contained in:
parent
ab4867d310
commit
afb1091603
15 changed files with 82 additions and 67 deletions
|
@ -43,6 +43,9 @@ type UserContext = {
|
|||
user: User;
|
||||
};
|
||||
|
||||
/**
|
||||
* A map of data hook event to its context type for better type hinting.
|
||||
*/
|
||||
export type DataHookContextMap = {
|
||||
'Organization.Membership.Updated': { organizationId: string };
|
||||
'User.Created': UserContext;
|
||||
|
|
|
@ -198,10 +198,7 @@ describe('triggerDataHooks()', () => {
|
|||
const hookData = { path: '/test', method: 'POST', data: { success: true } };
|
||||
|
||||
const hooksManager = new DataHookContextManager(metadata);
|
||||
hooksManager.appendContext({
|
||||
event: 'Role.Created',
|
||||
...hookData,
|
||||
});
|
||||
hooksManager.appendContext('Role.Created', hookData);
|
||||
|
||||
await triggerDataHooks(new ConsoleLog(), hooksManager);
|
||||
|
||||
|
@ -257,8 +254,7 @@ describe('triggerDataHooks()', () => {
|
|||
|
||||
const hooksManager = new DataHookContextManager(metadata);
|
||||
|
||||
hooksManager.appendContext({
|
||||
event: 'Role.Created',
|
||||
hooksManager.appendContext('Role.Created', {
|
||||
data: { id: 'user_id', username: 'user' },
|
||||
});
|
||||
|
||||
|
|
|
@ -70,6 +70,14 @@ const converBindMfaToMfaVerification = (bindMfa: BindMfa): MfaVerification => {
|
|||
};
|
||||
};
|
||||
|
||||
export type InsertUserResult = [
|
||||
User,
|
||||
{
|
||||
/** The organization IDs that the user has been provisioned into. */
|
||||
organizationIds: readonly string[];
|
||||
},
|
||||
];
|
||||
|
||||
export type UserLibrary = ReturnType<typeof createUserLibrary>;
|
||||
|
||||
export const createUserLibrary = (queries: Queries) => {
|
||||
|
@ -107,14 +115,6 @@ export const createUserLibrary = (queries: Queries) => {
|
|||
{ retries, factor: 0 } // No need for exponential backoff
|
||||
);
|
||||
|
||||
type InsertUserResult = [
|
||||
User,
|
||||
{
|
||||
/** The organization IDs that the user has been provisioned into. */
|
||||
organizationsIds: readonly string[];
|
||||
},
|
||||
];
|
||||
|
||||
const insertUser = async (
|
||||
data: OmitAutoSetFields<CreateUser>,
|
||||
additionalRoleNames: string[]
|
||||
|
@ -166,7 +166,7 @@ export const createUserLibrary = (queries: Queries) => {
|
|||
return [];
|
||||
};
|
||||
|
||||
return [user, { organizationsIds: await provisionOrganizations() }];
|
||||
return [user, { organizationIds: await provisionOrganizations() }];
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { removeUndefinedKeys } from '@silverhand/essentials';
|
|||
|
||||
import { mockUser, mockUserResponse } from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { type InsertUserResult } from '#src/libraries/user.js';
|
||||
import { koaManagementApiHooks } from '#src/middleware/koa-management-api-hooks.js';
|
||||
import type Libraries from '#src/tenants/Libraries.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
|
@ -81,10 +82,13 @@ const signOutUser = jest.fn();
|
|||
const usersLibraries = {
|
||||
generateUserId: jest.fn(async () => 'fooId'),
|
||||
insertUser: jest.fn(
|
||||
async (user: CreateUser): Promise<User> => ({
|
||||
...mockUser,
|
||||
...removeUndefinedKeys(user), // No undefined values will be returned from database
|
||||
})
|
||||
async (user: CreateUser): Promise<InsertUserResult> => [
|
||||
{
|
||||
...mockUser,
|
||||
...removeUndefinedKeys(user), // No undefined values will be returned from database
|
||||
},
|
||||
{ organizationIds: [] },
|
||||
]
|
||||
),
|
||||
verifyUserPassword,
|
||||
signOutUser,
|
||||
|
|
|
@ -200,7 +200,7 @@ export default function adminUserBasicsRoutes<T extends ManagementApiRouter>(
|
|||
|
||||
const id = await generateUserId();
|
||||
|
||||
const [user, { organizationsIds }] = await insertUser(
|
||||
const [user, { organizationIds }] = await insertUser(
|
||||
{
|
||||
id,
|
||||
primaryEmail,
|
||||
|
@ -221,7 +221,7 @@ export default function adminUserBasicsRoutes<T extends ManagementApiRouter>(
|
|||
[]
|
||||
);
|
||||
|
||||
for (const organizationId of organizationsIds) {
|
||||
for (const organizationId of organizationIds) {
|
||||
ctx.appendDataHookContext('Organization.Membership.Updated', {
|
||||
...buildManagementApiContext(ctx),
|
||||
organizationId,
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
mockUserTotpMfaVerification,
|
||||
mockUserWithMfaVerifications,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import { type InsertUserResult } from '#src/libraries/user.js';
|
||||
import type Libraries from '#src/tenants/Libraries.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import { MockTenant, type Partial2 } from '#src/test-utils/tenant.js';
|
||||
|
@ -49,10 +50,13 @@ await mockEsmWithActual('../interaction/utils/backup-code-validation.js', () =>
|
|||
const usersLibraries = {
|
||||
generateUserId: jest.fn(async () => 'fooId'),
|
||||
insertUser: jest.fn(
|
||||
async (user: CreateUser): Promise<User> => ({
|
||||
...mockUser,
|
||||
...removeUndefinedKeys(user), // No undefined values will be returned from database
|
||||
})
|
||||
async (user: CreateUser): Promise<InsertUserResult> => [
|
||||
{
|
||||
...mockUser,
|
||||
...removeUndefinedKeys(user), // No undefined values will be returned from database
|
||||
},
|
||||
{ organizationIds: [] },
|
||||
]
|
||||
),
|
||||
} satisfies Partial<Libraries['users']>;
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import type { CreateUser, Role, User } from '@logto/schemas';
|
||||
import { userInfoSelectFields, RoleType } from '@logto/schemas';
|
||||
import { pickDefault } from '@logto/shared/esm';
|
||||
import { pick } from '@silverhand/essentials';
|
||||
import { pick, removeUndefinedKeys } from '@silverhand/essentials';
|
||||
|
||||
import { mockUser, mockUserList, mockUserListResponse } from '#src/__mocks__/index.js';
|
||||
import { type InsertUserResult } from '#src/libraries/user.js';
|
||||
import type Libraries from '#src/tenants/Libraries.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import { MockTenant, type Partial2 } from '#src/test-utils/tenant.js';
|
||||
|
@ -52,10 +53,13 @@ const mockedQueries = {
|
|||
const usersLibraries = {
|
||||
generateUserId: jest.fn(async () => 'fooId'),
|
||||
insertUser: jest.fn(
|
||||
async (user: CreateUser): Promise<User> => ({
|
||||
...mockUser,
|
||||
...user,
|
||||
})
|
||||
async (user: CreateUser): Promise<InsertUserResult> => [
|
||||
{
|
||||
...mockUser,
|
||||
...removeUndefinedKeys(user), // No undefined values will be returned from database
|
||||
},
|
||||
{ organizationIds: [] },
|
||||
]
|
||||
),
|
||||
} satisfies Partial<Libraries['users']>;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ConnectorType, type CreateUser, type User } from '@logto/schemas';
|
||||
import { pickDefault } from '@logto/shared/esm';
|
||||
import { removeUndefinedKeys } from '@silverhand/essentials';
|
||||
|
||||
import {
|
||||
mockConnector0,
|
||||
|
@ -9,6 +10,7 @@ import {
|
|||
} from '#src/__mocks__/index.js';
|
||||
import { mockUser } from '#src/__mocks__/user.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { type InsertUserResult } from '#src/libraries/user.js';
|
||||
import type Libraries from '#src/tenants/Libraries.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import { MockTenant, type Partial2 } from '#src/test-utils/tenant.js';
|
||||
|
@ -47,10 +49,13 @@ const mockedQueries = {
|
|||
const usersLibraries = {
|
||||
generateUserId: jest.fn(async () => 'fooId'),
|
||||
insertUser: jest.fn(
|
||||
async (user: CreateUser): Promise<User> => ({
|
||||
...mockUser,
|
||||
...user,
|
||||
})
|
||||
async (user: CreateUser): Promise<InsertUserResult> => [
|
||||
{
|
||||
...mockUser,
|
||||
...removeUndefinedKeys(user), // No undefined values will be returned from database
|
||||
},
|
||||
{ organizationIds: [] },
|
||||
]
|
||||
),
|
||||
} satisfies Partial<Libraries['users']>;
|
||||
|
||||
|
|
|
@ -52,7 +52,10 @@ const userQueries = {
|
|||
|
||||
const { hasActiveUsers, updateUserById } = userQueries;
|
||||
|
||||
const userLibraries = { generateUserId: jest.fn().mockResolvedValue('uid'), insertUser: jest.fn() };
|
||||
const userLibraries = {
|
||||
generateUserId: jest.fn().mockResolvedValue('uid'),
|
||||
insertUser: jest.fn().mockResolvedValue([{}, { organizationIds: [] }]),
|
||||
};
|
||||
const { generateUserId, insertUser } = userLibraries;
|
||||
|
||||
const submitInteraction = await pickDefault(import('./submit-interaction.js'));
|
||||
|
@ -73,7 +76,7 @@ describe('submit action', () => {
|
|||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
interactionDetails: { params: {} } as Awaited<ReturnType<Provider['interactionDetails']>>,
|
||||
assignInteractionHookResult: jest.fn(),
|
||||
assignDataHookContext: jest.fn(),
|
||||
appendDataHookContext: jest.fn(),
|
||||
};
|
||||
const profile = {
|
||||
username: 'username',
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
import { createMockUtils, pickDefault } from '@logto/shared/esm';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
import { type InsertUserResult } from '#src/libraries/user.js';
|
||||
import { createMockLogContext } from '#src/test-utils/koa-audit-log.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
|
||||
|
@ -62,7 +63,9 @@ const { hasActiveUsers, updateUserById, hasUserWithEmail, hasUserWithPhone } = u
|
|||
|
||||
const userLibraries = {
|
||||
generateUserId: jest.fn().mockResolvedValue('uid'),
|
||||
insertUser: jest.fn(async (user: CreateUser) => user as User),
|
||||
insertUser: jest.fn(
|
||||
async (user: CreateUser): Promise<InsertUserResult> => [user as User, { organizationIds: [] }]
|
||||
),
|
||||
};
|
||||
const { generateUserId, insertUser } = userLibraries;
|
||||
|
||||
|
@ -84,7 +87,7 @@ describe('submit action', () => {
|
|||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
interactionDetails: { params: {} } as Awaited<ReturnType<Provider['interactionDetails']>>,
|
||||
assignInteractionHookResult: jest.fn(),
|
||||
assignDataHookContext: jest.fn(),
|
||||
appendDataHookContext: jest.fn(),
|
||||
};
|
||||
const profile = {
|
||||
username: 'username',
|
||||
|
@ -153,8 +156,7 @@ describe('submit action', () => {
|
|||
login: { accountId: 'uid' },
|
||||
});
|
||||
|
||||
expect(ctx.assignDataHookContext).toBeCalledWith({
|
||||
event: 'User.Created',
|
||||
expect(ctx.appendDataHookContext).toBeCalledWith('User.Created', {
|
||||
user: {
|
||||
id: 'uid',
|
||||
...upsertProfile,
|
||||
|
@ -188,8 +190,7 @@ describe('submit action', () => {
|
|||
login: { accountId: 'pending-account-id' },
|
||||
});
|
||||
|
||||
expect(ctx.assignDataHookContext).toBeCalledWith({
|
||||
event: 'User.Created',
|
||||
expect(ctx.appendDataHookContext).toBeCalledWith('User.Created', {
|
||||
user: {
|
||||
id: 'pending-account-id',
|
||||
...upsertProfile,
|
||||
|
@ -336,7 +337,7 @@ describe('submit action', () => {
|
|||
expect(assignInteractionResults).toBeCalledWith(ctx, tenant.provider, {
|
||||
login: { accountId: 'foo' },
|
||||
});
|
||||
expect(ctx.assignDataHookContext).not.toBeCalled();
|
||||
expect(ctx.appendDataHookContext).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('sign-in with new profile', async () => {
|
||||
|
@ -371,8 +372,7 @@ describe('submit action', () => {
|
|||
expect(assignInteractionResults).toBeCalledWith(ctx, tenant.provider, {
|
||||
login: { accountId: 'foo' },
|
||||
});
|
||||
expect(ctx.assignDataHookContext).toBeCalledWith({
|
||||
event: 'User.Data.Updated',
|
||||
expect(ctx.appendDataHookContext).toBeCalledWith('User.Data.Updated', {
|
||||
user: updateProfile,
|
||||
});
|
||||
});
|
||||
|
@ -432,8 +432,7 @@ describe('submit action', () => {
|
|||
expect(assignInteractionResults).toBeCalledWith(ctx, tenant.provider, {
|
||||
login: { accountId: 'foo' },
|
||||
});
|
||||
expect(ctx.assignDataHookContext).toBeCalledWith({
|
||||
event: 'User.Data.Updated',
|
||||
expect(ctx.appendDataHookContext).toBeCalledWith('User.Data.Updated', {
|
||||
user: {
|
||||
primaryEmail: 'email',
|
||||
name: userInfo.name,
|
||||
|
@ -458,8 +457,7 @@ describe('submit action', () => {
|
|||
passwordEncryptionMethod: 'plain',
|
||||
});
|
||||
expect(assignInteractionResults).not.toBeCalled();
|
||||
expect(ctx.assignDataHookContext).toBeCalledWith({
|
||||
event: 'User.Data.Updated',
|
||||
expect(ctx.appendDataHookContext).toBeCalledWith('User.Data.Updated', {
|
||||
user: {
|
||||
passwordEncrypted: 'passwordEncrypted',
|
||||
passwordEncryptionMethod: 'plain',
|
||||
|
|
|
@ -135,7 +135,7 @@ async function handleSubmitRegister(
|
|||
(invitation) => invitation.status === OrganizationInvitationStatus.Pending
|
||||
);
|
||||
|
||||
const [user, { organizationsIds }] = await insertUser(
|
||||
const [user, { organizationIds }] = await insertUser(
|
||||
{
|
||||
id,
|
||||
...userProfile,
|
||||
|
@ -190,7 +190,7 @@ async function handleSubmitRegister(
|
|||
ctx.assignInteractionHookResult({ userId: id });
|
||||
ctx.appendDataHookContext('User.Created', { user });
|
||||
|
||||
for (const organizationId of organizationsIds) {
|
||||
for (const organizationId of organizationIds) {
|
||||
ctx.appendDataHookContext('Organization.Membership.Updated', {
|
||||
organizationId,
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import { MockTenant } from '#src/test-utils/tenant.js';
|
|||
import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
|
||||
|
||||
import { type WithInteractionDetailsContext } from '../middleware/koa-interaction-details.js';
|
||||
import { type WithInteractionHooksContext } from '../middleware/koa-interaction-hooks.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm } = createMockUtils(jest);
|
||||
|
@ -25,7 +26,7 @@ const updateUserSsoIdentityMock = jest.fn();
|
|||
const insertUserSsoIdentityMock = jest.fn();
|
||||
const updateUserMock = jest.fn();
|
||||
const findUserByEmailMock = jest.fn();
|
||||
const insertUserMock = jest.fn();
|
||||
const insertUserMock = jest.fn().mockResolvedValue([{ id: 'foo' }, { organizationIds: [] }]);
|
||||
const generateUserIdMock = jest.fn().mockResolvedValue('foo');
|
||||
const getAvailableSsoConnectorsMock = jest.fn();
|
||||
|
||||
|
@ -59,12 +60,14 @@ const {
|
|||
} = await import('./single-sign-on.js');
|
||||
|
||||
describe('Single sign on util methods tests', () => {
|
||||
const mockContext: WithLogContext & WithInteractionDetailsContext = {
|
||||
const mockContext = {
|
||||
...createContextWithRouteParameters(),
|
||||
...createMockLogContext(),
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
interactionDetails: { jti: 'foo' } as Awaited<ReturnType<Provider['interactionDetails']>>,
|
||||
};
|
||||
assignInteractionHookResult: jest.fn(),
|
||||
appendDataHookContext: jest.fn(),
|
||||
} satisfies WithInteractionHooksContext<WithLogContext<WithInteractionDetailsContext>>;
|
||||
|
||||
const mockProvider = createMockProvider();
|
||||
|
||||
|
@ -288,7 +291,7 @@ describe('Single sign on util methods tests', () => {
|
|||
|
||||
describe('registerWithSsoAuthentication tests', () => {
|
||||
it('should register if no related user account found', async () => {
|
||||
insertUserMock.mockResolvedValueOnce({ id: 'foo' });
|
||||
insertUserMock.mockResolvedValueOnce([{ id: 'foo' }, { organizationIds: [] }]);
|
||||
|
||||
const { id } = await registerWithSsoAuthentication(mockContext, tenant, {
|
||||
connectorId: wellConfiguredSsoConnector.id,
|
||||
|
|
|
@ -310,7 +310,7 @@ export const registerWithSsoAuthentication = async (
|
|||
};
|
||||
|
||||
// Insert new user
|
||||
const [user, { organizationsIds }] = await usersLibrary.insertUser(
|
||||
const [user, { organizationIds }] = await usersLibrary.insertUser(
|
||||
{
|
||||
id: await usersLibrary.generateUserId(),
|
||||
...syncingProfile,
|
||||
|
@ -318,7 +318,7 @@ export const registerWithSsoAuthentication = async (
|
|||
},
|
||||
[]
|
||||
);
|
||||
for (const organizationId of organizationsIds) {
|
||||
for (const organizationId of organizationIds) {
|
||||
ctx.appendDataHookContext('Organization.Membership.Updated', {
|
||||
organizationId,
|
||||
});
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
/**
|
||||
* @fileoverview
|
||||
* Tests for manual-triggered data hook events.
|
||||
*/
|
||||
|
||||
import { SignInIdentifier, hookEvents, userInfoSelectFields } from '@logto/schemas';
|
||||
import { pick } from '@silverhand/essentials';
|
||||
|
||||
|
@ -10,6 +5,7 @@ import { deleteUser } from '#src/api/admin-user.js';
|
|||
import { createResource, deleteResource } from '#src/api/resource.js';
|
||||
import { createRole } from '#src/api/role.js';
|
||||
import { createScope } from '#src/api/scope.js';
|
||||
import { setEmailConnector, setSmsConnector } from '#src/helpers/connector.js';
|
||||
import { WebHookApiTest } from '#src/helpers/hook.js';
|
||||
import { registerWithEmail } from '#src/helpers/interactions.js';
|
||||
import { OrganizationApiTest } from '#src/helpers/organization.js';
|
||||
|
@ -133,7 +129,11 @@ describe('manual data hook tests', () => {
|
|||
await assertOrganizationMembershipUpdated(organization.id);
|
||||
});
|
||||
|
||||
// TODO: Add user deletion test case
|
||||
|
||||
it('should trigger `Organization.Membership.Updated` event when user is provisioned by experience', async () => {
|
||||
await setEmailConnector();
|
||||
await setSmsConnector();
|
||||
await enableAllVerificationCodeSignInMethods({
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: false,
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
/**
|
||||
* @fileoverview
|
||||
* Tests for automatic data hook events triggered by the Management API.
|
||||
*/
|
||||
|
||||
import {
|
||||
RoleType,
|
||||
hookEventGuard,
|
||||
|
|
Loading…
Reference in a new issue