generated from ttipo/project-base
feat(backend): 반려동물 API — 등록·수정·삭제·순서·추억 보관함
앱의 우리 아이 기록(PetRepository)을 서버로 옮긴다.
- GET/POST /pets, PATCH/DELETE /pets/:id, PUT /pets/order, PUT/DELETE /pets/:id/archive
- 목록은 정한 순서(sort_order) 기준 커서 페이지네이션 — 공통 keysetPaginate 로 뽑았다
- 아이를 지우거나 보관할 때 일정·다이어리·명함이 따라 정리할 수 있게 PetLifecycleListener 를 두었다
(반려동물 모듈은 다른 도메인을 모르고, 정리는 같은 트랜잭션에서 한다)
- 사진을 바꾸면 쓰지 않는 파일만 지운다 — 다른 기록이 참조하면 FK 가 막는다
- 목록 응답 Swagger 를 실제 { items, pageInfo } 모양으로 그리는 ApiPaginatedResponse 추가
- e2e 에 목록 pageInfo 계약·미정의 쿼리 거부 검사를 되살렸다
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,64 @@ describe('응답 계약 (e2e)', () => {
|
||||
expect(body(res).data).toHaveProperty('id');
|
||||
});
|
||||
|
||||
it('목록 응답의 pageInfo 는 공유 계약 형태를 지킨다', async () => {
|
||||
const pet = {
|
||||
tint: '#FF9B6A',
|
||||
name: '콩이',
|
||||
species: '강아지',
|
||||
breed: '',
|
||||
gender: 'male',
|
||||
neutered: true,
|
||||
birthDate: '2021-09-14',
|
||||
weight: 3.2,
|
||||
weightUnit: 'kg',
|
||||
allergies: [],
|
||||
conditions: [],
|
||||
watchConditions: [],
|
||||
};
|
||||
for (const name of ['콩이', '두부']) {
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/pets')
|
||||
.set(auth())
|
||||
.send({ ...pet, name })
|
||||
.expect(201);
|
||||
}
|
||||
|
||||
const first = await request(app.getHttpServer())
|
||||
.get('/api/pets?limit=1')
|
||||
.set(auth())
|
||||
.expect(200);
|
||||
const pageInfo = body(first).data?.pageInfo;
|
||||
|
||||
expect(body(first).data?.items).toHaveLength(1);
|
||||
expect(Object.keys(pageInfo ?? {}).sort()).toEqual([
|
||||
'hasNext',
|
||||
'nextCursor',
|
||||
'total',
|
||||
]);
|
||||
expect(pageInfo?.hasNext).toBe(true);
|
||||
expect(typeof pageInfo?.nextCursor).toBe('string');
|
||||
expect(pageInfo?.total).toBe(2);
|
||||
|
||||
const last = await request(app.getHttpServer())
|
||||
.get(`/api/pets?limit=1&cursor=${pageInfo?.nextCursor ?? ''}`)
|
||||
.set(auth())
|
||||
.expect(200);
|
||||
|
||||
expect(body(last).data?.items).toHaveLength(1);
|
||||
expect(body(last).data?.pageInfo?.hasNext).toBe(false);
|
||||
expect(body(last).data?.pageInfo?.nextCursor).toBeNull();
|
||||
});
|
||||
|
||||
it('정의되지 않은 쿼리 파라미터는 거부된다 (forbidNonWhitelisted)', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/api/pets?정의되지않은파라미터=1')
|
||||
.set(auth())
|
||||
.expect(400);
|
||||
|
||||
expect(body(res).error?.code).toBe('VAL_001');
|
||||
});
|
||||
|
||||
it('로그인하지 않으면 AUTH_001 과 traceId 를 반환한다', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/api/users/me')
|
||||
|
||||
Reference in New Issue
Block a user