mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
Merge pull request #4739 from logto-io/gao-add-organization-user-api-tests
chore(test): add integration tests for new APIs
This commit is contained in:
commit
fb48db4185
10 changed files with 434 additions and 152 deletions
|
@ -50,7 +50,7 @@ function AddMembersToOrganization({ organization, isOpen, onClose }: Props) {
|
|||
await api.post(`api/organizations/${organization.id}/users/roles`, {
|
||||
json: {
|
||||
userIds: data.users.map(({ id }) => id),
|
||||
roleIds: data.scopes.map(({ value }) => value),
|
||||
organizationRoleIds: data.scopes.map(({ value }) => value),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -58,7 +58,10 @@ export const verifyBearerTokenFromRequest = async (
|
|||
const userId = request.headers['development-user-id']?.toString() ?? developmentUserId;
|
||||
|
||||
if ((!isProduction || isIntegrationTest) && userId) {
|
||||
consoleLog.warn(`Found dev user ID ${userId}, skip token validation.`);
|
||||
// This log is distracting in integration tests.
|
||||
if (!isIntegrationTest) {
|
||||
consoleLog.warn(`Found dev user ID ${userId}, skip token validation.`);
|
||||
}
|
||||
|
||||
return {
|
||||
sub: userId,
|
||||
|
|
|
@ -67,16 +67,16 @@ export default function organizationRoutes<T extends AuthedRouter>(...args: Rout
|
|||
params: z.object({ id: z.string().min(1) }),
|
||||
body: z.object({
|
||||
userIds: z.string().min(1).array().nonempty(),
|
||||
roleIds: z.string().min(1).array().nonempty(),
|
||||
organizationRoleIds: z.string().min(1).array().nonempty(),
|
||||
}),
|
||||
status: [201, 422],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id } = ctx.guard.params;
|
||||
const { userIds, roleIds } = ctx.guard.body;
|
||||
const { userIds, organizationRoleIds } = ctx.guard.body;
|
||||
|
||||
await organizations.relations.rolesUsers.insert(
|
||||
...roleIds.flatMap<[string, string, string]>((roleId) =>
|
||||
...organizationRoleIds.flatMap<[string, string, string]>((roleId) =>
|
||||
userIds.map<[string, string, string]>((userId) => [id, roleId, userId])
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
import { type Role, type Organization, type OrganizationWithRoles } from '@logto/schemas';
|
||||
import {
|
||||
type Role,
|
||||
type Organization,
|
||||
type OrganizationWithRoles,
|
||||
type UserWithOrganizationRoles,
|
||||
} from '@logto/schemas';
|
||||
|
||||
import { authedAdminApi } from './api.js';
|
||||
import { ApiFactory } from './factory.js';
|
||||
|
||||
type Query = {
|
||||
q?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
};
|
||||
|
||||
export class OrganizationApi extends ApiFactory<
|
||||
Organization,
|
||||
{ name: string; description?: string }
|
||||
|
@ -15,8 +26,12 @@ export class OrganizationApi extends ApiFactory<
|
|||
await authedAdminApi.post(`${this.path}/${id}/users`, { json: { userIds } });
|
||||
}
|
||||
|
||||
async getUsers(id: string): Promise<Organization[]> {
|
||||
return authedAdminApi.get(`${this.path}/${id}/users`).json<Organization[]>();
|
||||
async getUsers(
|
||||
id: string,
|
||||
query?: Query
|
||||
): Promise<[rows: UserWithOrganizationRoles[], totalCount: number]> {
|
||||
const got = await authedAdminApi.get(`${this.path}/${id}/users`, { searchParams: query });
|
||||
return [JSON.parse(got.body), Number(got.headers['total-number'] ?? 0)];
|
||||
}
|
||||
|
||||
async deleteUser(id: string, userId: string): Promise<void> {
|
||||
|
@ -29,6 +44,12 @@ export class OrganizationApi extends ApiFactory<
|
|||
});
|
||||
}
|
||||
|
||||
async addUsersRoles(id: string, userIds: string[], organizationRoleIds: string[]): Promise<void> {
|
||||
await authedAdminApi.post(`${this.path}/${id}/users/roles`, {
|
||||
json: { userIds, organizationRoleIds },
|
||||
});
|
||||
}
|
||||
|
||||
async getUserRoles(id: string, userId: string): Promise<Role[]> {
|
||||
return authedAdminApi.get(`${this.path}/${id}/users/${userId}/roles`).json<Role[]>();
|
||||
}
|
||||
|
|
|
@ -14,7 +14,11 @@ import { OrganizationApi } from '#src/api/organization.js';
|
|||
* delete them.
|
||||
*/
|
||||
export class OrganizationRoleApiTest extends OrganizationRoleApi {
|
||||
protected roles: OrganizationRole[] = [];
|
||||
#roles: OrganizationRole[] = [];
|
||||
|
||||
get roles(): OrganizationRole[] {
|
||||
return this.#roles;
|
||||
}
|
||||
|
||||
override async create(data: CreateOrganizationRolePostData): Promise<OrganizationRole> {
|
||||
const created = await super.create(data);
|
||||
|
@ -29,7 +33,7 @@ export class OrganizationRoleApiTest extends OrganizationRoleApi {
|
|||
async cleanUp(): Promise<void> {
|
||||
// Use `trySafe` to avoid error when role is deleted by other tests.
|
||||
await Promise.all(this.roles.map(async (role) => trySafe(this.delete(role.id))));
|
||||
this.roles = [];
|
||||
this.#roles = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +42,11 @@ export class OrganizationRoleApiTest extends OrganizationRoleApi {
|
|||
* delete them.
|
||||
*/
|
||||
export class OrganizationScopeApiTest extends OrganizationScopeApi {
|
||||
protected scopes: OrganizationScope[] = [];
|
||||
#scopes: OrganizationScope[] = [];
|
||||
|
||||
get scopes(): OrganizationScope[] {
|
||||
return this.#scopes;
|
||||
}
|
||||
|
||||
override async create(data: { name: string; description?: string }): Promise<OrganizationScope> {
|
||||
const created = await super.create(data);
|
||||
|
@ -53,7 +61,7 @@ export class OrganizationScopeApiTest extends OrganizationScopeApi {
|
|||
async cleanUp(): Promise<void> {
|
||||
// Use `trySafe` to avoid error when scope is deleted by other tests.
|
||||
await Promise.all(this.scopes.map(async (scope) => trySafe(this.delete(scope.id))));
|
||||
this.scopes = [];
|
||||
this.#scopes = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +77,11 @@ export class OrganizationApiTest extends OrganizationApi {
|
|||
roleApi = new OrganizationRoleApiTest();
|
||||
scopeApi = new OrganizationScopeApiTest();
|
||||
|
||||
protected organizations: Organization[] = [];
|
||||
#organizations: Organization[] = [];
|
||||
|
||||
get organizations(): Organization[] {
|
||||
return this.#organizations;
|
||||
}
|
||||
|
||||
override async create(data: { name: string; description?: string }): Promise<Organization> {
|
||||
const created = await super.create(data);
|
||||
|
@ -89,7 +101,7 @@ export class OrganizationApiTest extends OrganizationApi {
|
|||
// Use `trySafe` to avoid error when organization is deleted by other tests.
|
||||
this.organizations.map(async (organization) => trySafe(this.delete(organization.id)))
|
||||
);
|
||||
this.organizations = [];
|
||||
this.#organizations = [];
|
||||
}
|
||||
}
|
||||
/* eslint-enable @silverhand/fp/no-mutating-methods */
|
||||
|
|
|
@ -51,7 +51,11 @@ export const generateNewUser = async <T extends NewUserProfileOptions>(options:
|
|||
};
|
||||
|
||||
export class UserApiTest {
|
||||
protected users: User[] = [];
|
||||
#users: User[] = [];
|
||||
|
||||
get users(): User[] {
|
||||
return this.#users;
|
||||
}
|
||||
|
||||
async create(data: CreateUserPayload): Promise<User> {
|
||||
const user = await createUser(data);
|
||||
|
@ -67,6 +71,6 @@ export class UserApiTest {
|
|||
async cleanUp(): Promise<void> {
|
||||
// Use `trySafe` to avoid error when user is deleted by other tests.
|
||||
await Promise.all(this.users.map(async (user) => trySafe(deleteUser(user.id))));
|
||||
this.users = [];
|
||||
this.#users = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import type { IncomingHttpHeaders } from 'node:http';
|
||||
|
||||
import type { User } from '@logto/schemas';
|
||||
import type { Role, User } from '@logto/schemas';
|
||||
|
||||
import { authedAdminApi, deleteUser } from '#src/api/index.js';
|
||||
import { assignRolesToUser, authedAdminApi, createUser, deleteUser } from '#src/api/index.js';
|
||||
import { createRole, deleteRole } from '#src/api/role.js';
|
||||
import { createUserByAdmin, expectRejects } from '#src/helpers/index.js';
|
||||
import { OrganizationApiTest } from '#src/helpers/organization.js';
|
||||
import { UserApiTest } from '#src/helpers/user.js';
|
||||
|
||||
const getUsers = async <T>(
|
||||
init: string[][] | Record<string, string> | URLSearchParams
|
||||
|
@ -235,3 +238,134 @@ describe('admin console user search params', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('admin console user search params - excludeRoleId', () => {
|
||||
const users: User[] = [];
|
||||
const roles: Role[] = [];
|
||||
const userPrefix = `search_exclude_role_`;
|
||||
const rolePrefix = `role_`;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create users with different roles
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
||||
users.push(
|
||||
...(await Promise.all([
|
||||
createUser({ username: userPrefix + '1' }),
|
||||
createUser({ username: userPrefix + '2' }),
|
||||
createUser({ username: userPrefix + '3' }),
|
||||
]))
|
||||
);
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
||||
roles.push(
|
||||
...(await Promise.all([
|
||||
createRole({ name: rolePrefix + '1' }),
|
||||
createRole({ name: rolePrefix + '2' }),
|
||||
createRole({ name: rolePrefix + '3' }),
|
||||
]))
|
||||
);
|
||||
|
||||
// Assign roles to users
|
||||
await Promise.all([
|
||||
assignRolesToUser(users[0]!.id, [roles[0]!.id, roles[1]!.id]),
|
||||
assignRolesToUser(users[1]!.id, [roles[1]!.id, roles[2]!.id]),
|
||||
assignRolesToUser(users[2]!.id, [roles[2]!.id]),
|
||||
]);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await Promise.all(users.map(async ({ id }) => deleteUser(id)));
|
||||
await Promise.all(roles.map(async ({ id }) => deleteRole(id)));
|
||||
});
|
||||
|
||||
it('should be able to exclude users with a specific role (1)', async () => {
|
||||
const { headers, json } = await getUsers<User[]>([
|
||||
['search.username', userPrefix + '%'],
|
||||
['excludeRoleId', roles[0]!.id],
|
||||
]);
|
||||
|
||||
expect(headers['total-number']).toEqual('2');
|
||||
expect(json).toHaveLength(2);
|
||||
expect(json).toContainEqual(expect.objectContaining({ id: users[1]!.id }));
|
||||
expect(json).toContainEqual(expect.objectContaining({ id: users[2]!.id }));
|
||||
});
|
||||
|
||||
it('should be able to exclude users with a specific role (2)', async () => {
|
||||
const { headers, json } = await getUsers<User[]>([
|
||||
['search.username', userPrefix + '%'],
|
||||
['excludeRoleId', roles[1]!.id],
|
||||
]);
|
||||
|
||||
expect(headers['total-number']).toEqual('1');
|
||||
expect(json).toHaveLength(1);
|
||||
expect(json).toContainEqual(expect.objectContaining({ id: users[2]!.id }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('admin console user search params - excludeOrganizationId', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
const userApi = new UserApiTest();
|
||||
const organizationPrefix = `search_exclude_organization_`;
|
||||
const userPrefix = `search_exclude_organization_`;
|
||||
|
||||
beforeAll(async () => {
|
||||
await Promise.all([
|
||||
organizationApi.create({ name: organizationPrefix + '1' }),
|
||||
organizationApi.create({ name: organizationPrefix + '2' }),
|
||||
organizationApi.create({ name: organizationPrefix + '3' }),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
userApi.create({ username: userPrefix + '1' }),
|
||||
userApi.create({ username: userPrefix + '2' }),
|
||||
userApi.create({ username: userPrefix + '3' }),
|
||||
]);
|
||||
|
||||
const { organizations } = organizationApi;
|
||||
const { users } = userApi;
|
||||
|
||||
await Promise.all([
|
||||
organizationApi.addUsers(organizations[0]!.id, [users[0]!.id, users[1]!.id]),
|
||||
organizationApi.addUsers(organizations[1]!.id, [users[1]!.id, users[2]!.id]),
|
||||
organizationApi.addUsers(organizations[2]!.id, [users[2]!.id]),
|
||||
]);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await organizationApi.cleanUp();
|
||||
await userApi.cleanUp();
|
||||
});
|
||||
|
||||
it('should be able to exclude users with a specific organization (1)', async () => {
|
||||
const { headers, json } = await getUsers<User[]>([
|
||||
['search.username', userPrefix + '%'],
|
||||
['excludeOrganizationId', organizationApi.organizations[0]!.id],
|
||||
]);
|
||||
|
||||
expect(headers['total-number']).toEqual('1');
|
||||
expect(json).toHaveLength(1);
|
||||
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[2]!.id }));
|
||||
});
|
||||
|
||||
it('should be able to exclude users with a specific organization (2)', async () => {
|
||||
const { headers, json } = await getUsers<User[]>([
|
||||
['search.username', userPrefix + '%'],
|
||||
['excludeOrganizationId', organizationApi.organizations[1]!.id],
|
||||
]);
|
||||
|
||||
expect(headers['total-number']).toEqual('1');
|
||||
expect(json).toHaveLength(1);
|
||||
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[0]!.id }));
|
||||
});
|
||||
|
||||
it('should be able to exclude users with a specific organization (3)', async () => {
|
||||
const { headers, json } = await getUsers<User[]>([
|
||||
['search.username', userPrefix + '%'],
|
||||
['excludeOrganizationId', organizationApi.organizations[2]!.id],
|
||||
]);
|
||||
|
||||
expect(headers['total-number']).toEqual('2');
|
||||
expect(json).toHaveLength(2);
|
||||
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[0]!.id }));
|
||||
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[1]!.id }));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
import assert from 'node:assert';
|
||||
|
||||
import { HTTPError } from 'got';
|
||||
|
||||
import { OrganizationApiTest } from '#src/helpers/organization.js';
|
||||
import { UserApiTest } from '#src/helpers/user.js';
|
||||
import { generateTestName } from '#src/utils.js';
|
||||
|
||||
describe('organization user APIs', () => {
|
||||
describe('organization get users', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
const userApi = new UserApiTest();
|
||||
|
||||
beforeAll(async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const createdUsers = await Promise.all(
|
||||
Array.from({ length: 30 }).map(async () => userApi.create({ username: generateTestName() }))
|
||||
);
|
||||
await organizationApi.addUsers(
|
||||
organization.id,
|
||||
createdUsers.map((user) => user.id)
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await Promise.all([organizationApi.cleanUp(), userApi.cleanUp()]);
|
||||
});
|
||||
|
||||
it('should be able to get organization users with pagination', async () => {
|
||||
const organizationId = organizationApi.organizations[0]!.id;
|
||||
const [users1, total1] = await organizationApi.getUsers(organizationId, {
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
const [users2, total2] = await organizationApi.getUsers(organizationId, {
|
||||
page: 2,
|
||||
page_size: 10,
|
||||
});
|
||||
expect(users2.length).toBeGreaterThanOrEqual(10);
|
||||
expect(users2[0]?.id).not.toBeFalsy();
|
||||
expect(users2[0]?.id).toBe(users1[10]?.id);
|
||||
expect(total1).toBe(30);
|
||||
expect(total2).toBe(30);
|
||||
});
|
||||
|
||||
it('should be able to get organization users with search keyword', async () => {
|
||||
const organizationId = organizationApi.organizations[0]!.id;
|
||||
const username = generateTestName();
|
||||
const createdUser = await userApi.create({ username });
|
||||
|
||||
await organizationApi.addUsers(organizationId, [createdUser.id]);
|
||||
const [users] = await organizationApi.getUsers(organizationId, {
|
||||
q: username,
|
||||
});
|
||||
expect(users).toHaveLength(1);
|
||||
expect(users[0]).toMatchObject(createdUser);
|
||||
});
|
||||
|
||||
it('should be able to get organization users with their roles', async () => {
|
||||
const organizationId = organizationApi.organizations[0]!.id;
|
||||
const user = userApi.users[0]!;
|
||||
|
||||
const roles = await Promise.all([
|
||||
organizationApi.roleApi.create({ name: generateTestName() }),
|
||||
organizationApi.roleApi.create({ name: generateTestName() }),
|
||||
]);
|
||||
const roleIds = roles.map(({ id }) => id);
|
||||
await organizationApi.addUserRoles(organizationId, user.id, roleIds);
|
||||
|
||||
const [usersWithRoles] = await organizationApi.getUsers(organizationId, {
|
||||
q: user.username!,
|
||||
});
|
||||
expect(usersWithRoles).toHaveLength(1);
|
||||
expect(usersWithRoles[0]).toMatchObject(user);
|
||||
expect(usersWithRoles[0]!.organizationRoles).toHaveLength(2);
|
||||
expect(usersWithRoles[0]!.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: roles[0].id })
|
||||
);
|
||||
expect(usersWithRoles[0]!.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: roles[1].id })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('organization - user relations', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
const userApi = new UserApiTest();
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all([organizationApi.cleanUp(), userApi.cleanUp()]);
|
||||
});
|
||||
|
||||
it('should fail when try to add empty user list', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const response = await organizationApi
|
||||
.addUsers(organization.id, [])
|
||||
.catch((error: unknown) => error);
|
||||
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it('should fail when try to add user to an organization that does not exist', async () => {
|
||||
const response = await organizationApi.addUsers('0', ['0']).catch((error: unknown) => error);
|
||||
assert(response instanceof HTTPError);
|
||||
expect(response.response.statusCode).toBe(422);
|
||||
expect(JSON.parse(String(response.response.body))).toMatchObject(
|
||||
expect.objectContaining({ code: 'entity.relation_foreign_key_not_found' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to delete organization user', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const user = await userApi.create({ username: generateTestName() });
|
||||
|
||||
await organizationApi.addUsers(organization.id, [user.id]);
|
||||
await organizationApi.deleteUser(organization.id, user.id);
|
||||
const users = await organizationApi.getUsers(organization.id);
|
||||
expect(users).not.toContainEqual(user);
|
||||
});
|
||||
|
||||
it('should fail when try to delete user from an organization that does not exist', async () => {
|
||||
const response = await organizationApi.deleteUser('0', '0').catch((error: unknown) => error);
|
||||
assert(response instanceof HTTPError);
|
||||
expect(response.response.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('organization - user - organization role relation routes', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
const { roleApi } = organizationApi;
|
||||
const userApi = new UserApiTest();
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all([organizationApi.cleanUp(), userApi.cleanUp()]);
|
||||
});
|
||||
|
||||
it("should be able to add and get user's organization roles", async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const user = await userApi.create({ username: generateTestName() });
|
||||
const [role1, role2] = await Promise.all([
|
||||
roleApi.create({ name: generateTestName() }),
|
||||
roleApi.create({ name: generateTestName() }),
|
||||
]);
|
||||
|
||||
const response = await organizationApi
|
||||
.addUserRoles(organization.id, user.id, [role1.id, role2.id])
|
||||
.catch((error: unknown) => error);
|
||||
|
||||
assert(response instanceof HTTPError);
|
||||
expect(response.response.statusCode).toBe(422);
|
||||
expect(JSON.parse(String(response.response.body))).toMatchObject(
|
||||
expect.objectContaining({ code: 'organization.require_membership' })
|
||||
);
|
||||
|
||||
await organizationApi.addUsers(organization.id, [user.id]);
|
||||
await organizationApi.addUserRoles(organization.id, user.id, [role1.id, role2.id]);
|
||||
const roles = await organizationApi.getUserRoles(organization.id, user.id);
|
||||
expect(roles).toContainEqual(expect.objectContaining({ id: role1.id }));
|
||||
expect(roles).toContainEqual(expect.objectContaining({ id: role2.id }));
|
||||
});
|
||||
|
||||
it('should be able to get all organizations with roles for a user', async () => {
|
||||
const [organization1, organization2] = await Promise.all([
|
||||
organizationApi.create({ name: 'test' }),
|
||||
organizationApi.create({ name: 'test' }),
|
||||
]);
|
||||
const user = await userApi.create({ username: generateTestName() });
|
||||
const [role1, role2] = await Promise.all([
|
||||
roleApi.create({ name: generateTestName() }),
|
||||
roleApi.create({ name: generateTestName() }),
|
||||
]);
|
||||
|
||||
await organizationApi.addUsers(organization1.id, [user.id]);
|
||||
await organizationApi.addUserRoles(organization1.id, user.id, [role1.id]);
|
||||
await organizationApi.addUsers(organization2.id, [user.id]);
|
||||
await organizationApi.addUserRoles(organization2.id, user.id, [role1.id, role2.id]);
|
||||
|
||||
const organizations = await organizationApi.getUserOrganizations(user.id);
|
||||
|
||||
// Check organization 1 and ensure it only has role 1
|
||||
const organization1WithRoles = organizations.find((org) => org.id === organization1.id);
|
||||
assert(organization1WithRoles);
|
||||
expect(organization1WithRoles.id).toBe(organization1.id);
|
||||
expect(organization1WithRoles.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: role1.id })
|
||||
);
|
||||
expect(organization1WithRoles.organizationRoles).not.toContainEqual(
|
||||
expect.objectContaining({ id: role2.id })
|
||||
);
|
||||
|
||||
// Check organization 2 and ensure it has both role 1 and role 2
|
||||
const organization2WithRoles = organizations.find((org) => org.id === organization2.id);
|
||||
assert(organization2WithRoles);
|
||||
expect(organization2WithRoles.id).toBe(organization2.id);
|
||||
expect(organization2WithRoles.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: role1.id })
|
||||
);
|
||||
expect(organization2WithRoles.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: role2.id })
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to assign multiple roles to multiple users', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const [user1, user2] = await Promise.all([
|
||||
userApi.create({ username: generateTestName() }),
|
||||
userApi.create({ username: generateTestName() }),
|
||||
]);
|
||||
const [role1, role2] = await Promise.all([
|
||||
roleApi.create({ name: generateTestName() }),
|
||||
roleApi.create({ name: generateTestName() }),
|
||||
]);
|
||||
|
||||
await organizationApi.addUsers(organization.id, [user1.id, user2.id]);
|
||||
await organizationApi.addUsersRoles(
|
||||
organization.id,
|
||||
[user1.id, user2.id],
|
||||
[role1.id, role2.id]
|
||||
);
|
||||
|
||||
const [user1Roles, user2Roles] = await Promise.all([
|
||||
organizationApi.getUserRoles(organization.id, user1.id),
|
||||
organizationApi.getUserRoles(organization.id, user2.id),
|
||||
]);
|
||||
|
||||
expect(user1Roles).toContainEqual(expect.objectContaining({ id: role1.id }));
|
||||
expect(user1Roles).toContainEqual(expect.objectContaining({ id: role2.id }));
|
||||
expect(user2Roles).toContainEqual(expect.objectContaining({ id: role1.id }));
|
||||
expect(user2Roles).toContainEqual(expect.objectContaining({ id: role2.id }));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,10 +1,7 @@
|
|||
import assert from 'node:assert';
|
||||
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { HTTPError } from 'got';
|
||||
|
||||
import { OrganizationApiTest } from '#src/helpers/organization.js';
|
||||
import { UserApiTest } from '#src/helpers/user.js';
|
||||
|
||||
const randomId = () => generateStandardId(4);
|
||||
|
||||
|
@ -90,135 +87,4 @@ describe('organization APIs', () => {
|
|||
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('organization - user relations', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
const userApi = new UserApiTest();
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all([organizationApi.cleanUp(), userApi.cleanUp()]);
|
||||
});
|
||||
|
||||
it('should be able to add and get organization users', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const [user1, user2] = await Promise.all([
|
||||
userApi.create({ username: 'test' + randomId() }),
|
||||
userApi.create({ username: 'test' + randomId() }),
|
||||
]);
|
||||
|
||||
await organizationApi.addUsers(organization.id, [user1.id, user2.id]);
|
||||
const users = await organizationApi.getUsers(organization.id);
|
||||
expect(users).toContainEqual(expect.objectContaining({ id: user1.id }));
|
||||
expect(users).toContainEqual(expect.objectContaining({ id: user2.id }));
|
||||
});
|
||||
|
||||
it('should fail when try to add empty user list', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const response = await organizationApi
|
||||
.addUsers(organization.id, [])
|
||||
.catch((error: unknown) => error);
|
||||
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it('should fail when try to add user to an organization that does not exist', async () => {
|
||||
const response = await organizationApi.addUsers('0', ['0']).catch((error: unknown) => error);
|
||||
assert(response instanceof HTTPError);
|
||||
expect(response.response.statusCode).toBe(422);
|
||||
expect(JSON.parse(String(response.response.body))).toMatchObject(
|
||||
expect.objectContaining({ code: 'entity.relation_foreign_key_not_found' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to delete organization user', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const user = await userApi.create({ username: 'test' + randomId() });
|
||||
|
||||
await organizationApi.addUsers(organization.id, [user.id]);
|
||||
await organizationApi.deleteUser(organization.id, user.id);
|
||||
const users = await organizationApi.getUsers(organization.id);
|
||||
expect(users).not.toContainEqual(user);
|
||||
});
|
||||
|
||||
it('should fail when try to delete user from an organization that does not exist', async () => {
|
||||
const response = await organizationApi.deleteUser('0', '0').catch((error: unknown) => error);
|
||||
assert(response instanceof HTTPError);
|
||||
expect(response.response.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('organization - user - organization role relation routes', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
const { roleApi } = organizationApi;
|
||||
const userApi = new UserApiTest();
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all([organizationApi.cleanUp(), userApi.cleanUp()]);
|
||||
});
|
||||
|
||||
it("should be able to add and get user's organization roles", async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const user = await userApi.create({ username: 'test' + randomId() });
|
||||
const [role1, role2] = await Promise.all([
|
||||
roleApi.create({ name: 'test' + randomId() }),
|
||||
roleApi.create({ name: 'test' + randomId() }),
|
||||
]);
|
||||
|
||||
const response = await organizationApi
|
||||
.addUserRoles(organization.id, user.id, [role1.id, role2.id])
|
||||
.catch((error: unknown) => error);
|
||||
|
||||
assert(response instanceof HTTPError);
|
||||
expect(response.response.statusCode).toBe(422);
|
||||
expect(JSON.parse(String(response.response.body))).toMatchObject(
|
||||
expect.objectContaining({ code: 'organization.require_membership' })
|
||||
);
|
||||
|
||||
await organizationApi.addUsers(organization.id, [user.id]);
|
||||
await organizationApi.addUserRoles(organization.id, user.id, [role1.id, role2.id]);
|
||||
const roles = await organizationApi.getUserRoles(organization.id, user.id);
|
||||
expect(roles).toContainEqual(expect.objectContaining({ id: role1.id }));
|
||||
expect(roles).toContainEqual(expect.objectContaining({ id: role2.id }));
|
||||
});
|
||||
|
||||
it('should be able to get all organizations with roles for a user', async () => {
|
||||
const [organization1, organization2] = await Promise.all([
|
||||
organizationApi.create({ name: 'test' }),
|
||||
organizationApi.create({ name: 'test' }),
|
||||
]);
|
||||
const user = await userApi.create({ username: 'test' + randomId() });
|
||||
const [role1, role2] = await Promise.all([
|
||||
roleApi.create({ name: 'test' + randomId() }),
|
||||
roleApi.create({ name: 'test' + randomId() }),
|
||||
]);
|
||||
|
||||
await organizationApi.addUsers(organization1.id, [user.id]);
|
||||
await organizationApi.addUserRoles(organization1.id, user.id, [role1.id]);
|
||||
await organizationApi.addUsers(organization2.id, [user.id]);
|
||||
await organizationApi.addUserRoles(organization2.id, user.id, [role1.id, role2.id]);
|
||||
|
||||
const organizations = await organizationApi.getUserOrganizations(user.id);
|
||||
|
||||
// Check organization 1 and ensure it only has role 1
|
||||
const organization1WithRoles = organizations.find((org) => org.id === organization1.id);
|
||||
assert(organization1WithRoles);
|
||||
expect(organization1WithRoles.id).toBe(organization1.id);
|
||||
expect(organization1WithRoles.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: role1.id })
|
||||
);
|
||||
expect(organization1WithRoles.organizationRoles).not.toContainEqual(
|
||||
expect.objectContaining({ id: role2.id })
|
||||
);
|
||||
|
||||
// Check organization 2 and ensure it has both role 1 and role 2
|
||||
const organization2WithRoles = organizations.find((org) => org.id === organization2.id);
|
||||
assert(organization2WithRoles);
|
||||
expect(organization2WithRoles.id).toBe(organization2.id);
|
||||
expect(organization2WithRoles.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: role1.id })
|
||||
);
|
||||
expect(organization2WithRoles.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: role2.id })
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import crypto from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
import { type Page } from 'puppeteer';
|
||||
|
||||
|
@ -105,3 +106,13 @@ export const cls = <C extends string>(className: C) => `[class*=_${className}]`
|
|||
* @see {@link cls}
|
||||
*/
|
||||
export const dcls = <C extends string>(className: C) => `div${cls(className)}` as const;
|
||||
|
||||
/**
|
||||
* Generate a random test name that starts with `test_` and followed by 4 random characters.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* generateTestName() // => 'test_abc1'
|
||||
* ```
|
||||
*/
|
||||
export const generateTestName = () => `test_${generateStandardId(4)}`;
|
||||
|
|
Loading…
Add table
Reference in a new issue