mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor: add organization jit role api tests
This commit is contained in:
parent
3ea37c5275
commit
b25bca3aa2
13 changed files with 300 additions and 141 deletions
|
@ -16,7 +16,7 @@ const appendHeader = jest.fn((key: string, value: string) => {
|
|||
}
|
||||
});
|
||||
|
||||
const createContext = (query: Record<string, string>): WithPaginationContext<Context> => {
|
||||
const createContext = (query: Record<string, string>): WithPaginationContext<Context, false> => {
|
||||
const baseContext = createMockContext();
|
||||
const context = {
|
||||
...baseContext,
|
||||
|
|
|
@ -9,37 +9,58 @@ type Pagination = {
|
|||
offset: number;
|
||||
limit: number;
|
||||
totalCount?: number;
|
||||
disabled?: boolean;
|
||||
disabled?: false;
|
||||
};
|
||||
|
||||
export type WithPaginationContext<ContextT> = ContextT & {
|
||||
pagination: Pagination;
|
||||
type DisabledPagination = {
|
||||
offset: undefined;
|
||||
limit: undefined;
|
||||
totalCount: undefined;
|
||||
disabled: true;
|
||||
};
|
||||
|
||||
type PaginationConfig = {
|
||||
export type WithPaginationContext<ContextT, IsOptional extends boolean> = ContextT & {
|
||||
pagination: IsOptional extends true ? Pagination | DisabledPagination : Pagination;
|
||||
};
|
||||
|
||||
type PaginationConfig<IsOptional extends boolean> = {
|
||||
defaultPageSize?: number;
|
||||
maxPageSize?: number;
|
||||
isOptional?: boolean;
|
||||
isOptional?: IsOptional;
|
||||
};
|
||||
|
||||
export const isPaginationMiddleware = <Type extends IMiddleware>(
|
||||
function_: Type
|
||||
): function_ is WithPaginationContext<Type> => function_.name === 'paginationMiddleware';
|
||||
): function_ is WithPaginationContext<Type, true> => function_.name === 'paginationMiddleware';
|
||||
|
||||
export const fallbackDefaultPageSize = 20;
|
||||
export const pageNumberKey = 'page';
|
||||
export const pageSizeKey = 'page_size';
|
||||
|
||||
export default function koaPagination<StateT, ContextT, ResponseBodyT>({
|
||||
function koaPagination<StateT, ContextT, ResponseBodyT>(
|
||||
config?: PaginationConfig<false>
|
||||
): MiddlewareType<StateT, WithPaginationContext<ContextT, false>, ResponseBodyT>;
|
||||
function koaPagination<StateT, ContextT, ResponseBodyT>(
|
||||
config: PaginationConfig<true>
|
||||
): MiddlewareType<StateT, WithPaginationContext<ContextT, true>, ResponseBodyT>;
|
||||
function koaPagination<StateT, ContextT, ResponseBodyT>(
|
||||
config?: PaginationConfig<boolean>
|
||||
): MiddlewareType<StateT, WithPaginationContext<ContextT, boolean>, ResponseBodyT>;
|
||||
function koaPagination<StateT, ContextT, ResponseBodyT, IsOptional extends boolean>({
|
||||
defaultPageSize = fallbackDefaultPageSize,
|
||||
maxPageSize = 100,
|
||||
isOptional = false,
|
||||
}: PaginationConfig = {}): MiddlewareType<StateT, WithPaginationContext<ContextT>, ResponseBodyT> {
|
||||
isOptional,
|
||||
}: PaginationConfig<IsOptional> = {}): MiddlewareType<
|
||||
StateT,
|
||||
WithPaginationContext<ContextT, true>,
|
||||
ResponseBodyT
|
||||
> {
|
||||
// Name this anonymous function for the utility function `isPaginationMiddleware` to identify it
|
||||
const paginationMiddleware: MiddlewareType<
|
||||
StateT,
|
||||
WithPaginationContext<ContextT>,
|
||||
WithPaginationContext<ContextT, true>,
|
||||
ResponseBodyT
|
||||
// eslint-disable-next-line complexity -- maybe refactor me
|
||||
> = async (ctx, next) => {
|
||||
try {
|
||||
const {
|
||||
|
@ -56,7 +77,9 @@ export default function koaPagination<StateT, ContextT, ResponseBodyT>({
|
|||
? number().positive().max(maxPageSize).parse(Number(rawPageSize))
|
||||
: defaultPageSize;
|
||||
|
||||
ctx.pagination = { offset: (pageNumber - 1) * pageSize, limit: pageSize, disabled };
|
||||
ctx.pagination = disabled
|
||||
? { disabled, totalCount: undefined, offset: undefined, limit: undefined }
|
||||
: { disabled, totalCount: undefined, offset: (pageNumber - 1) * pageSize, limit: pageSize };
|
||||
} catch {
|
||||
throw new RequestError({ code: 'guard.invalid_pagination', status: 400 });
|
||||
}
|
||||
|
@ -94,3 +117,5 @@ export default function koaPagination<StateT, ContextT, ResponseBodyT>({
|
|||
|
||||
return paginationMiddleware;
|
||||
}
|
||||
|
||||
export default koaPagination;
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
}
|
||||
},
|
||||
"post": {
|
||||
"summary": "Add organization JIT role",
|
||||
"description": "Add a new organization role that will be assigned to users during just-in-time provisioning.",
|
||||
"summary": "Add organization JIT roles",
|
||||
"description": "Add new organization roles that will be assigned to users during just-in-time provisioning.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"organizationRoleId": {
|
||||
"description": "The organization role ID to add."
|
||||
"organizationRoleIds": {
|
||||
"description": "The organization role IDs to add."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,10 +33,10 @@
|
|||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "The organization role was added successfully."
|
||||
"description": "The organization roles were added successfully."
|
||||
},
|
||||
"422": {
|
||||
"description": "The organization role is already in use."
|
||||
"description": "The organization roles could not be added. Some of the organization roles may not exist."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -141,7 +141,7 @@ export default function organizationRoutes<T extends ManagementApiRouter>(
|
|||
|
||||
// MARK: Just-in-time provisioning
|
||||
emailDomainRoutes(router, organizations);
|
||||
router.addRelationRoutes(organizations.jit.roles, 'jit/roles');
|
||||
router.addRelationRoutes(organizations.jit.roles, 'jit/roles', { isPaginationOptional: true });
|
||||
|
||||
// MARK: Mount sub-routes
|
||||
organizationRoleRoutes(...args);
|
||||
|
|
|
@ -143,7 +143,10 @@ export default function singleSignOnConnectorsRoutes<T extends ManagementApiRout
|
|||
const [totalCount, connectors] = paginationDisabled
|
||||
? await getSsoConnectors()
|
||||
: await getSsoConnectors(limit, offset);
|
||||
ctx.pagination.totalCount = totalCount;
|
||||
|
||||
if (!paginationDisabled) {
|
||||
ctx.pagination.totalCount = totalCount;
|
||||
}
|
||||
|
||||
// Fetch provider details for each connector
|
||||
const connectorsWithProviderDetails = await Promise.all(
|
||||
|
|
|
@ -96,6 +96,11 @@ type RelationRoutesConfig = {
|
|||
/** Disable `GET /:id/[pathname]` route. */
|
||||
get: boolean;
|
||||
};
|
||||
/**
|
||||
* If the GET route's pagination is optional.
|
||||
* @default false
|
||||
*/
|
||||
isPaginationOptional?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -184,7 +189,7 @@ export default class SchemaRouter<
|
|||
GeneratedSchema<string, RelationCreateSchema, RelationSchema>
|
||||
>,
|
||||
pathname = tableToPathname(relationQueries.schemas[1].table),
|
||||
{ disabled, hookEvent }: Partial<RelationRoutesConfig> = {}
|
||||
{ disabled, hookEvent, isPaginationOptional }: Partial<RelationRoutesConfig> = {}
|
||||
) {
|
||||
const relationSchema = relationQueries.schemas[1];
|
||||
const relationSchemaId = camelCaseSchemaId(relationSchema);
|
||||
|
@ -205,7 +210,7 @@ export default class SchemaRouter<
|
|||
if (!disabled?.get) {
|
||||
this.get(
|
||||
`/:id/${pathname}`,
|
||||
koaPagination(),
|
||||
koaPagination({ isOptional: isPaginationOptional }),
|
||||
koaGuard({
|
||||
params: z.object({ id: z.string().min(1) }),
|
||||
response: relationSchema.guard.array(),
|
||||
|
@ -220,10 +225,12 @@ export default class SchemaRouter<
|
|||
const [totalCount, entities] = await relationQueries.getEntities(
|
||||
relationSchema,
|
||||
{ [columns.schemaId]: id },
|
||||
ctx.pagination
|
||||
ctx.pagination.disabled ? undefined : ctx.pagination
|
||||
);
|
||||
|
||||
ctx.pagination.totalCount = totalCount;
|
||||
if (!ctx.pagination.disabled) {
|
||||
ctx.pagination.totalCount = totalCount;
|
||||
}
|
||||
ctx.body = entities;
|
||||
return next();
|
||||
}
|
||||
|
|
55
packages/integration-tests/src/api/organization-jit.ts
Normal file
55
packages/integration-tests/src/api/organization-jit.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { type OrganizationRole, type OrganizationJitEmailDomain } from '@logto/schemas';
|
||||
|
||||
import { authedAdminApi } from './api.js';
|
||||
|
||||
export class OrganizationJitApi {
|
||||
constructor(public path: string) {}
|
||||
|
||||
async getEmailDomains(
|
||||
id: string,
|
||||
page?: number,
|
||||
pageSize?: number
|
||||
): Promise<OrganizationJitEmailDomain[]> {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (page) {
|
||||
searchParams.append('page', String(page));
|
||||
}
|
||||
|
||||
if (pageSize) {
|
||||
searchParams.append('page_size', String(pageSize));
|
||||
}
|
||||
|
||||
return authedAdminApi
|
||||
.get(`${this.path}/${id}/jit/email-domains`, { searchParams })
|
||||
.json<OrganizationJitEmailDomain[]>();
|
||||
}
|
||||
|
||||
async addEmailDomain(id: string, emailDomain: string): Promise<void> {
|
||||
await authedAdminApi.post(`${this.path}/${id}/jit/email-domains`, { json: { emailDomain } });
|
||||
}
|
||||
|
||||
async deleteEmailDomain(id: string, emailDomain: string): Promise<void> {
|
||||
await authedAdminApi.delete(`${this.path}/${id}/jit/email-domains/${emailDomain}`);
|
||||
}
|
||||
|
||||
async replaceEmailDomains(id: string, emailDomains: string[]): Promise<void> {
|
||||
await authedAdminApi.put(`${this.path}/${id}/jit/email-domains`, { json: { emailDomains } });
|
||||
}
|
||||
|
||||
async getRoles(id: string): Promise<OrganizationRole[]> {
|
||||
return authedAdminApi.get(`${this.path}/${id}/jit/roles`).json<OrganizationRole[]>();
|
||||
}
|
||||
|
||||
async addRole(id: string, organizationRoleIds: string[]): Promise<void> {
|
||||
await authedAdminApi.post(`${this.path}/${id}/jit/roles`, { json: { organizationRoleIds } });
|
||||
}
|
||||
|
||||
async deleteRole(id: string, organizationRoleId: string): Promise<void> {
|
||||
await authedAdminApi.delete(`${this.path}/${id}/jit/roles/${organizationRoleId}`);
|
||||
}
|
||||
|
||||
async replaceRoles(id: string, organizationRoleIds: string[]): Promise<void> {
|
||||
await authedAdminApi.put(`${this.path}/${id}/jit/roles`, { json: { organizationRoleIds } });
|
||||
}
|
||||
}
|
|
@ -5,12 +5,12 @@ import {
|
|||
type UserWithOrganizationRoles,
|
||||
type OrganizationWithFeatured,
|
||||
type OrganizationScope,
|
||||
type OrganizationJitEmailDomain,
|
||||
type CreateOrganization,
|
||||
} from '@logto/schemas';
|
||||
|
||||
import { authedAdminApi } from './api.js';
|
||||
import { ApiFactory } from './factory.js';
|
||||
import { OrganizationJitApi } from './organization-jit.js';
|
||||
|
||||
type Query = {
|
||||
q?: string;
|
||||
|
@ -19,6 +19,8 @@ type Query = {
|
|||
};
|
||||
|
||||
export class OrganizationApi extends ApiFactory<Organization, Omit<CreateOrganization, 'id'>> {
|
||||
jit = new OrganizationJitApi(this.path);
|
||||
|
||||
constructor() {
|
||||
super('organizations');
|
||||
}
|
||||
|
@ -77,36 +79,4 @@ export class OrganizationApi extends ApiFactory<Organization, Omit<CreateOrganiz
|
|||
.get(`${this.path}/${id}/users/${userId}/scopes`)
|
||||
.json<OrganizationScope[]>();
|
||||
}
|
||||
|
||||
async getEmailDomains(
|
||||
id: string,
|
||||
page?: number,
|
||||
pageSize?: number
|
||||
): Promise<OrganizationJitEmailDomain[]> {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (page) {
|
||||
searchParams.append('page', String(page));
|
||||
}
|
||||
|
||||
if (pageSize) {
|
||||
searchParams.append('page_size', String(pageSize));
|
||||
}
|
||||
|
||||
return authedAdminApi
|
||||
.get(`${this.path}/${id}/jit/email-domains`, { searchParams })
|
||||
.json<OrganizationJitEmailDomain[]>();
|
||||
}
|
||||
|
||||
async addEmailDomain(id: string, emailDomain: string): Promise<void> {
|
||||
await authedAdminApi.post(`${this.path}/${id}/jit/email-domains`, { json: { emailDomain } });
|
||||
}
|
||||
|
||||
async deleteEmailDomain(id: string, emailDomain: string): Promise<void> {
|
||||
await authedAdminApi.delete(`${this.path}/${id}/jit/email-domains/${emailDomain}`);
|
||||
}
|
||||
|
||||
async replaceEmailDomains(id: string, emailDomains: string[]): Promise<void> {
|
||||
await authedAdminApi.put(`${this.path}/${id}/jit/email-domains`, { json: { emailDomains } });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ describe('organization just-in-time provisioning', () => {
|
|||
const emailDomain = 'foo.com';
|
||||
await Promise.all(
|
||||
organizations.map(async (organization) =>
|
||||
organizationApi.addEmailDomain(organization.id, emailDomain)
|
||||
organizationApi.jit.addEmailDomain(organization.id, emailDomain)
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ describe('manual data hook tests', () => {
|
|||
it('should trigger `Organization.Membership.Updated` event when user is provisioned by Management API', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
const domain = 'example.com';
|
||||
await organizationApi.addEmailDomain(organization.id, domain);
|
||||
await organizationApi.jit.addEmailDomain(organization.id, domain);
|
||||
|
||||
await userApi.create({ primaryEmail: `${randomString()}@${domain}` });
|
||||
await assertOrganizationMembershipUpdated(organization.id);
|
||||
|
@ -142,7 +142,7 @@ describe('manual data hook tests', () => {
|
|||
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
const domain = 'example.com';
|
||||
await organizationApi.addEmailDomain(organization.id, domain);
|
||||
await organizationApi.jit.addEmailDomain(organization.id, domain);
|
||||
|
||||
await registerWithEmail(`${randomString()}@${domain}`);
|
||||
await assertOrganizationMembershipUpdated(organization.id);
|
||||
|
|
|
@ -48,7 +48,7 @@ describe('organization just-in-time provisioning', () => {
|
|||
const emailDomain = 'foo.com';
|
||||
await Promise.all(
|
||||
organizations.map(async (organization) =>
|
||||
organizationApi.addEmailDomain(organization.id, emailDomain)
|
||||
organizationApi.jit.addEmailDomain(organization.id, emailDomain)
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
import { generateStandardId } from '@logto/shared';
|
||||
|
||||
import { OrganizationApiTest } from '#src/helpers/organization.js';
|
||||
|
||||
const randomId = () => generateStandardId(6);
|
||||
|
||||
describe('organization email domains', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
|
||||
afterEach(async () => {
|
||||
await organizationApi.cleanUp();
|
||||
});
|
||||
|
||||
it('should add and delete email domains', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
const emailDomain = `${randomId()}.com`;
|
||||
|
||||
await organizationApi.addEmailDomain(organization.id, emailDomain);
|
||||
await expect(organizationApi.getEmailDomains(organization.id)).resolves.toMatchObject([
|
||||
{ emailDomain },
|
||||
]);
|
||||
|
||||
await organizationApi.deleteEmailDomain(organization.id, emailDomain);
|
||||
await expect(organizationApi.getEmailDomains(organization.id)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should have default pagination', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
|
||||
const emailDomains = Array.from({ length: 30 }, () => `${randomId()}.com`);
|
||||
|
||||
await organizationApi.replaceEmailDomains(organization.id, emailDomains);
|
||||
|
||||
const emailDomainsPage1 = await organizationApi.getEmailDomains(organization.id);
|
||||
const emailDomainsPage2 = await organizationApi.getEmailDomains(organization.id, 2);
|
||||
|
||||
expect(emailDomainsPage1).toHaveLength(20);
|
||||
expect(emailDomainsPage2).toHaveLength(10);
|
||||
expect(emailDomainsPage1.concat(emailDomainsPage2)).toEqual(
|
||||
expect.arrayContaining(
|
||||
emailDomains.map((emailDomain) => expect.objectContaining({ emailDomain }))
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 404 when deleting a non-existent email domain', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
const emailDomain = `${randomId()}.com`;
|
||||
|
||||
await expect(
|
||||
organizationApi.deleteEmailDomain(organization.id, emailDomain)
|
||||
).rejects.toMatchInlineSnapshot('[HTTPError: Request failed with status code 404 Not Found]');
|
||||
});
|
||||
|
||||
it('should return 400 when adding an email domain that already exists', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
const emailDomain = `${randomId()}.com`;
|
||||
|
||||
await organizationApi.addEmailDomain(organization.id, emailDomain);
|
||||
await expect(
|
||||
organizationApi.addEmailDomain(organization.id, emailDomain)
|
||||
).rejects.toMatchInlineSnapshot(
|
||||
'[HTTPError: Request failed with status code 422 Unprocessable Entity]'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to replace email domains', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
await organizationApi.addEmailDomain(organization.id, `${randomId()}.com`);
|
||||
|
||||
const emailDomains = [`${randomId()}.com`, `${randomId()}.com`];
|
||||
|
||||
await organizationApi.replaceEmailDomains(organization.id, emailDomains);
|
||||
await expect(organizationApi.getEmailDomains(organization.id)).resolves.toEqual(
|
||||
expect.arrayContaining(
|
||||
emailDomains.map((emailDomain) => expect.objectContaining({ emailDomain }))
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,179 @@
|
|||
import { generateStandardId } from '@logto/shared';
|
||||
|
||||
import { OrganizationApiTest } from '#src/helpers/organization.js';
|
||||
import { randomString } from '#src/utils.js';
|
||||
|
||||
const randomId = () => generateStandardId(6);
|
||||
|
||||
describe('organization just-in-time provisioning', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
|
||||
afterEach(async () => {
|
||||
await organizationApi.cleanUp();
|
||||
});
|
||||
|
||||
describe('email domains', () => {
|
||||
it('should add and delete email domains', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
const emailDomain = `${randomId()}.com`;
|
||||
|
||||
await organizationApi.jit.addEmailDomain(organization.id, emailDomain);
|
||||
await expect(organizationApi.jit.getEmailDomains(organization.id)).resolves.toMatchObject([
|
||||
{ emailDomain },
|
||||
]);
|
||||
|
||||
await organizationApi.jit.deleteEmailDomain(organization.id, emailDomain);
|
||||
await expect(organizationApi.jit.getEmailDomains(organization.id)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should have default pagination', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
|
||||
const emailDomains = Array.from({ length: 30 }, () => `${randomId()}.com`);
|
||||
|
||||
await organizationApi.jit.replaceEmailDomains(organization.id, emailDomains);
|
||||
|
||||
const emailDomainsPage1 = await organizationApi.jit.getEmailDomains(organization.id);
|
||||
const emailDomainsPage2 = await organizationApi.jit.getEmailDomains(organization.id, 2);
|
||||
|
||||
expect(emailDomainsPage1).toHaveLength(20);
|
||||
expect(emailDomainsPage2).toHaveLength(10);
|
||||
expect(emailDomainsPage1.concat(emailDomainsPage2)).toEqual(
|
||||
expect.arrayContaining(
|
||||
emailDomains.map((emailDomain) => expect.objectContaining({ emailDomain }))
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 404 when deleting a non-existent email domain', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
const emailDomain = `${randomId()}.com`;
|
||||
|
||||
await expect(
|
||||
organizationApi.jit.deleteEmailDomain(organization.id, emailDomain)
|
||||
).rejects.toMatchInlineSnapshot('[HTTPError: Request failed with status code 404 Not Found]');
|
||||
});
|
||||
|
||||
it('should return 422 when adding an email domain that already exists', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
const emailDomain = `${randomId()}.com`;
|
||||
|
||||
await organizationApi.jit.addEmailDomain(organization.id, emailDomain);
|
||||
await expect(
|
||||
organizationApi.jit.addEmailDomain(organization.id, emailDomain)
|
||||
).rejects.toMatchInlineSnapshot(
|
||||
'[HTTPError: Request failed with status code 422 Unprocessable Entity]'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to replace email domains', async () => {
|
||||
const organization = await organizationApi.create({ name: 'foo' });
|
||||
await organizationApi.jit.addEmailDomain(organization.id, `${randomId()}.com`);
|
||||
|
||||
const emailDomains = [`${randomId()}.com`, `${randomId()}.com`];
|
||||
|
||||
await organizationApi.jit.replaceEmailDomains(organization.id, emailDomains);
|
||||
await expect(organizationApi.jit.getEmailDomains(organization.id)).resolves.toEqual(
|
||||
expect.arrayContaining(
|
||||
emailDomains.map((emailDomain) => expect.objectContaining({ emailDomain }))
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('organization roles', () => {
|
||||
it('should add and delete organization roles', async () => {
|
||||
const organization = await organizationApi.create({ name: `jit-role:${randomString()}` });
|
||||
const { id: organizationRoleId } = await organizationApi.roleApi.create({
|
||||
name: `jit-role:${randomString()}`,
|
||||
});
|
||||
|
||||
await organizationApi.jit.addRole(organization.id, [organizationRoleId]);
|
||||
await expect(organizationApi.jit.getRoles(organization.id)).resolves.toMatchObject([
|
||||
{ id: organizationRoleId },
|
||||
]);
|
||||
|
||||
await organizationApi.jit.deleteRole(organization.id, organizationRoleId);
|
||||
await expect(organizationApi.jit.getRoles(organization.id)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should have no pagination', async () => {
|
||||
const organization = await organizationApi.create({ name: `jit-role:${randomString()}` });
|
||||
const organizationRoles = await Promise.all(
|
||||
Array.from({ length: 30 }, async () =>
|
||||
organizationApi.roleApi.create({
|
||||
name: `jit-role:${randomString()}`,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
await organizationApi.jit.replaceRoles(
|
||||
organization.id,
|
||||
organizationRoles.map(({ id }) => id)
|
||||
);
|
||||
|
||||
await expect(organizationApi.jit.getRoles(organization.id)).resolves.toEqual(
|
||||
expect.arrayContaining(organizationRoles.map(({ id }) => expect.objectContaining({ id })))
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 404 when deleting a non-existent organization role', async () => {
|
||||
const organization = await organizationApi.create({ name: `jit-role:${randomString()}` });
|
||||
const organizationRoleId = randomId();
|
||||
|
||||
await expect(
|
||||
organizationApi.jit.deleteRole(organization.id, organizationRoleId)
|
||||
).rejects.toMatchInlineSnapshot('[HTTPError: Request failed with status code 404 Not Found]');
|
||||
});
|
||||
|
||||
it('should return 422 when adding a non-existent organization role', async () => {
|
||||
const organization = await organizationApi.create({ name: `jit-role:${randomString()}` });
|
||||
const organizationRoleId = randomId();
|
||||
|
||||
await expect(
|
||||
organizationApi.jit.addRole(organization.id, [organizationRoleId])
|
||||
).rejects.toMatchInlineSnapshot(
|
||||
'[HTTPError: Request failed with status code 422 Unprocessable Entity]'
|
||||
);
|
||||
});
|
||||
|
||||
it('should do nothing when adding an organization role that already exists', async () => {
|
||||
const organization = await organizationApi.create({ name: `jit-role:${randomString()}` });
|
||||
const organizationRoles = await Promise.all([
|
||||
organizationApi.roleApi.create({
|
||||
name: `jit-role:${randomString()}`,
|
||||
}),
|
||||
organizationApi.roleApi.create({
|
||||
name: `jit-role:${randomString()}`,
|
||||
}),
|
||||
]);
|
||||
|
||||
await organizationApi.jit.addRole(organization.id, [organizationRoles[0].id]);
|
||||
await expect(
|
||||
organizationApi.jit.addRole(organization.id, [
|
||||
organizationRoles[0].id,
|
||||
organizationRoles[1].id,
|
||||
])
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should be able to replace organization roles', async () => {
|
||||
const organization = await organizationApi.create({ name: `jit-role:${randomString()}` });
|
||||
const organizationRoles = await Promise.all(
|
||||
Array.from({ length: 2 }, async () =>
|
||||
organizationApi.roleApi.create({
|
||||
name: `jit-role:${randomString()}`,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
await organizationApi.jit.replaceRoles(
|
||||
organization.id,
|
||||
organizationRoles.map(({ id }) => id)
|
||||
);
|
||||
await expect(organizationApi.jit.getRoles(organization.id)).resolves.toEqual(
|
||||
expect.arrayContaining(organizationRoles.map(({ id }) => expect.objectContaining({ id })))
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue