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

refactor(core): fix id guard issue for post api

This commit is contained in:
Gao Sun 2023-10-12 14:56:34 +08:00
parent 9a995ef65f
commit fd7d5a4ab1
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
2 changed files with 28 additions and 21 deletions

View file

@ -10,27 +10,33 @@ import { createRequester, createTestPool } from './test-utils.js';
const { jest } = import.meta; const { jest } = import.meta;
type CreateSchema = { type CreateSchema = {
id?: string; id: string;
name?: string;
}; };
type Schema = { type Schema = {
id: string; id: string;
name: string;
}; };
describe('SchemaRouter', () => { describe('SchemaRouter', () => {
const schema: GeneratedSchema<'id', CreateSchema, Schema> = { const schema: GeneratedSchema<'id' | 'name', CreateSchema, Schema> = {
table: 'test_table', table: 'test_tables',
tableSingular: 'test_table', tableSingular: 'test_table',
fields: { fields: {
id: 'id', id: 'id',
name: 'name',
}, },
fieldKeys: ['id'], fieldKeys: ['id', 'name'],
createGuard: z.object({ id: z.string().optional() }), createGuard: z.object({ id: z.string(), name: z.string().optional() }),
guard: z.object({ id: z.string() }), guard: z.object({ id: z.string(), name: z.string() }),
updateGuard: z.object({ id: z.string().optional() }), updateGuard: z.object({ id: z.string(), name: z.string() }).partial(),
}; };
const entities = [{ id: 'test' }, { id: 'test2' }] as const satisfies readonly Schema[]; const entities = [
const actions: SchemaActions<'id', CreateSchema, Schema> = { { id: '1', name: 'test' },
{ id: '2', name: 'test2' },
] as const satisfies readonly Schema[];
const actions: SchemaActions<'name', CreateSchema, Schema> = {
queries: new SchemaQueries(createTestPool(), schema), queries: new SchemaQueries(createTestPool(), schema),
get: jest.fn().mockResolvedValue([entities.length, entities]), get: jest.fn().mockResolvedValue([entities.length, entities]),
getById: jest.fn(async (id) => { getById: jest.fn(async (id) => {
@ -40,8 +46,8 @@ describe('SchemaRouter', () => {
} }
return entity; return entity;
}), }),
post: jest.fn(async () => ({ id: 'test_new' })), post: jest.fn(async () => ({ id: 'new', name: 'test_new' })),
patchById: jest.fn(async (id, data) => ({ id, ...data })), patchById: jest.fn(async (id, data) => ({ id, name: 'name_patch_default', ...data })),
deleteById: jest.fn(), deleteById: jest.fn(),
}; };
const schemaRouter = new SchemaRouter(schema, actions); const schemaRouter = new SchemaRouter(schema, actions);
@ -76,11 +82,11 @@ describe('SchemaRouter', () => {
const response = await request.post(baseRoute).send({}); const response = await request.post(baseRoute).send({});
expect(actions.post).toHaveBeenCalledWith({}); expect(actions.post).toHaveBeenCalledWith({});
expect(response.body).toStrictEqual({ id: 'test_new' }); expect(response.body).toStrictEqual({ id: 'new', name: 'test_new' });
}); });
it('should throw with invalid input body', async () => { it('should throw with invalid input body', async () => {
const response = await request.post(baseRoute).send({ id: 1 }); const response = await request.post(baseRoute).send({ name: 1 });
expect(response.status).toEqual(400); expect(response.status).toEqual(400);
}); });
@ -88,15 +94,15 @@ describe('SchemaRouter', () => {
describe('getById', () => { describe('getById', () => {
it('should be able to get an entity by id', async () => { it('should be able to get an entity by id', async () => {
const response = await request.get(`${baseRoute}/test`); const response = await request.get(`${baseRoute}/1`);
expect(actions.getById).toHaveBeenCalledWith('test'); expect(actions.getById).toHaveBeenCalledWith('1');
expect(response.body).toStrictEqual(entities[0]); expect(response.body).toStrictEqual(entities[0]);
}); });
// This test case is actually nice-to-have. It's not required for the router to work. // This test case is actually nice-to-have. It's not required for the router to work.
it('should throw with invalid id', async () => { it('should throw with invalid id', async () => {
const response = await request.get(`${baseRoute}/2`); const response = await request.get(`${baseRoute}/foo`);
expect(response.status).toEqual(404); expect(response.status).toEqual(404);
}); });
@ -104,10 +110,10 @@ describe('SchemaRouter', () => {
describe('patchById', () => { describe('patchById', () => {
it('should be able to patch an entity by id', async () => { it('should be able to patch an entity by id', async () => {
const response = await request.patch(`${baseRoute}/test`).send({ id: 'test_new' }); const response = await request.patch(`${baseRoute}/test`).send({ name: 'test_new' });
expect(actions.patchById).toHaveBeenCalledWith('test', { id: 'test_new' }); expect(actions.patchById).toHaveBeenCalledWith('test', { name: 'test_new' });
expect(response.body).toStrictEqual({ id: 'test_new' }); expect(response.body).toStrictEqual({ id: 'test', name: 'test_new' });
expect(response.status).toEqual(200); expect(response.status).toEqual(200);
}); });
}); });

View file

@ -1,4 +1,4 @@
import { type SchemaLike, type GeneratedSchema } from '@logto/schemas'; import { type SchemaLike, type GeneratedSchema, type Guard } from '@logto/schemas';
import { generateStandardId, type OmitAutoSetFields } from '@logto/shared'; import { generateStandardId, type OmitAutoSetFields } from '@logto/shared';
import Router, { type IRouterParamContext } from 'koa-router'; import Router, { type IRouterParamContext } from 'koa-router';
import { z } from 'zod'; import { z } from 'zod';
@ -119,7 +119,8 @@ export default class SchemaRouter<
this.post( this.post(
'/', '/',
koaGuard({ koaGuard({
body: schema.createGuard, // eslint-disable-next-line no-restricted-syntax -- `.omit()` doesn't play well for generic types
body: schema.createGuard.omit({ id: true }) as Guard<Omit<CreateSchema, 'id'>>,
response: schema.guard, response: schema.guard,
status: [201, 422], status: [201, 422],
}), }),