0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

chore: add response guard and integration tests for application APIs (#3771)

This commit is contained in:
Charles Zhao 2023-05-06 12:05:50 +08:00 committed by GitHub
parent d2e6e1fd5b
commit 0e46ddacca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 47 additions and 16 deletions

View file

@ -169,6 +169,7 @@ describe('application route', () => {
'http://127.0.0.1', 'http://127.0.0.1',
'http://localhost:3002', 'http://localhost:3002',
], ],
postLogoutRedirectUris: [],
}, },
}) })
).resolves.toHaveProperty('status', 200); ).resolves.toHaveProperty('status', 200);
@ -179,6 +180,7 @@ describe('application route', () => {
applicationRequest.patch('/applications/foo').send({ applicationRequest.patch('/applications/foo').send({
oidcClientMetadata: { oidcClientMetadata: {
redirectUris: ['www.example.com', 'com.example://callback'], redirectUris: ['www.example.com', 'com.example://callback'],
postLogoutRedirectUris: [],
}, },
}) })
).resolves.toHaveProperty('status', 400); ).resolves.toHaveProperty('status', 400);
@ -194,6 +196,7 @@ describe('application route', () => {
'com.example://callback', 'com.example://callback',
'io.logto://Abc123', 'io.logto://Abc123',
], ],
postLogoutRedirectUris: [],
}, },
}) })
).resolves.toHaveProperty('status', 200); ).resolves.toHaveProperty('status', 200);
@ -205,6 +208,7 @@ describe('application route', () => {
type: ApplicationType.Native, type: ApplicationType.Native,
oidcClientMetadata: { oidcClientMetadata: {
redirectUris: ['https://www.example.com', 'com.example/callback'], redirectUris: ['https://www.example.com', 'com.example/callback'],
postLogoutRedirectUris: [],
}, },
}) })
).resolves.toHaveProperty('status', 400); ).resolves.toHaveProperty('status', 400);

View file

@ -35,20 +35,25 @@ export default function applicationRoutes<T extends AuthedRouter>(
queries.applicationsRoles; queries.applicationsRoles;
const { findRoleByRoleName } = queries.roles; const { findRoleByRoleName } = queries.roles;
router.get('/applications', koaPagination(), async (ctx, next) => { router.get(
const { limit, offset } = ctx.pagination; '/applications',
koaPagination(),
koaGuard({ response: z.array(Applications.guard), status: 200 }),
async (ctx, next) => {
const { limit, offset } = ctx.pagination;
const [{ count }, applications] = await Promise.all([ const [{ count }, applications] = await Promise.all([
findTotalNumberOfApplications(), findTotalNumberOfApplications(),
findAllApplications(limit, offset), findAllApplications(limit, offset),
]); ]);
// Return totalCount to pagination middleware // Return totalCount to pagination middleware
ctx.pagination.totalCount = count; ctx.pagination.totalCount = count;
ctx.body = applications; ctx.body = applications;
return next(); return next();
}); }
);
router.post( router.post(
'/applications', '/applications',
@ -57,6 +62,8 @@ export default function applicationRoutes<T extends AuthedRouter>(
.omit({ id: true, createdAt: true }) .omit({ id: true, createdAt: true })
.partial() .partial()
.merge(Applications.createGuard.pick({ name: true, type: true })), .merge(Applications.createGuard.pick({ name: true, type: true })),
response: Applications.guard,
status: [200, 422],
}), }),
async (ctx, next) => { async (ctx, next) => {
const { oidcClientMetadata, ...rest } = ctx.guard.body; const { oidcClientMetadata, ...rest } = ctx.guard.body;
@ -77,6 +84,7 @@ export default function applicationRoutes<T extends AuthedRouter>(
koaGuard({ koaGuard({
params: object({ id: string().min(1) }), params: object({ id: string().min(1) }),
response: Applications.guard.merge(z.object({ isAdmin: z.boolean() })), response: Applications.guard.merge(z.object({ isAdmin: z.boolean() })),
status: [200, 404],
}), }),
async (ctx, next) => { async (ctx, next) => {
const { const {
@ -114,6 +122,8 @@ export default function applicationRoutes<T extends AuthedRouter>(
isAdmin: boolean().optional(), isAdmin: boolean().optional(),
}) })
), ),
response: Applications.guard,
status: [200, 404, 500],
}), }),
async (ctx, next) => { async (ctx, next) => {
const { const {
@ -135,7 +145,11 @@ export default function applicationRoutes<T extends AuthedRouter>(
assertThat( assertThat(
internalAdminRole, internalAdminRole,
new RequestError('entity.not_exists', { name: InternalRole.Admin }) new RequestError({
code: 'entity.not_exists',
status: 500,
data: { name: InternalRole.Admin },
})
); );
if (isAdmin && !usedToBeAdmin) { if (isAdmin && !usedToBeAdmin) {
@ -155,7 +169,11 @@ export default function applicationRoutes<T extends AuthedRouter>(
router.delete( router.delete(
'/applications/:id', '/applications/:id',
koaGuard({ params: object({ id: string().min(1) }) }), koaGuard({
params: object({ id: string().min(1) }),
response: z.undefined(),
status: [204, 404],
}),
async (ctx, next) => { async (ctx, next) => {
const { id } = ctx.guard.params; const { id } = ctx.guard.params;
// Note: will need delete cascade when application is joint with other tables // Note: will need delete cascade when application is joint with other tables

View file

@ -17,6 +17,8 @@ export const createApplication = async (name: string, type: ApplicationType) =>
}) })
.json<Application>(); .json<Application>();
export const getApplications = async () => authedAdminApi.get('applications').json<Application[]>();
export const getApplication = async (applicationId: string) => export const getApplication = async (applicationId: string) =>
authedAdminApi.get(`applications/${applicationId}`).json<Application>(); authedAdminApi.get(`applications/${applicationId}`).json<Application>();

View file

@ -6,13 +6,13 @@ import {
getApplication, getApplication,
updateApplication, updateApplication,
deleteApplication, deleteApplication,
getApplications,
} from '#src/api/index.js'; } from '#src/api/index.js';
describe('admin console application', () => { describe('admin console application', () => {
it('should create application successfully', async () => { it('should create application successfully', async () => {
const applicationName = 'test-create-app'; const applicationName = 'test-create-app';
const applicationType = ApplicationType.SPA; const applicationType = ApplicationType.SPA;
const application = await createApplication(applicationName, applicationType); const application = await createApplication(applicationName, applicationType);
expect(application.name).toBe(applicationName); expect(application.name).toBe(applicationName);
@ -25,7 +25,7 @@ describe('admin console application', () => {
}); });
it('should update application details successfully', async () => { it('should update application details successfully', async () => {
const application = await createApplication('test-update-app', ApplicationType.SPA); const application = await createApplication('test-update-app', ApplicationType.Traditional);
const newApplicationDescription = `new_${application.description ?? ''}`; const newApplicationDescription = `new_${application.description ?? ''}`;
@ -46,8 +46,15 @@ describe('admin console application', () => {
expect(updatedApplication.oidcClientMetadata.redirectUris).toEqual(newRedirectUris); expect(updatedApplication.oidcClientMetadata.redirectUris).toEqual(newRedirectUris);
}); });
it('should fetch all applications created above', async () => {
const applications = await getApplications();
const applicationNames = applications.map(({ name }) => name);
expect(applicationNames).toContain('test-create-app');
expect(applicationNames).toContain('test-update-app');
});
it('should delete application successfully', async () => { it('should delete application successfully', async () => {
const application = await createApplication('test-delete-app', ApplicationType.SPA); const application = await createApplication('test-delete-app', ApplicationType.Native);
await deleteApplication(application.id); await deleteApplication(application.id);