Skip to content

Commit 6b4afb9

Browse files
Merge pull request #52 from fuyueagain/dev
test(admin): add Jest suites for core admin APIs
2 parents 74e7649 + b9f311c commit 6b4afb9

4 files changed

Lines changed: 669 additions & 0 deletions

File tree

tests/apiadmincheckins.test.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// /tests/apiadmincheckins.test.ts
2+
3+
// 1) Mock next/server 的 NextResponse
4+
jest.mock('next/server', () => ({
5+
NextResponse: {
6+
json: jest.fn((data: any, opts?: any) => ({
7+
json: async () => data,
8+
status: opts?.status ?? 200,
9+
ok: opts?.status ? opts.status >= 200 && opts.status < 300 : true,
10+
})),
11+
},
12+
}));
13+
14+
// 2) Mock next-auth 的 getServerSession(默认返回管理员)
15+
jest.mock('next-auth', () => ({
16+
getServerSession: jest.fn(() => Promise.resolve({ user: { role: 'admin' } })),
17+
}));
18+
19+
// 3) Mock PrismaClient(覆盖 checkin 与 checkinPhoto)
20+
jest.mock('@prisma/client', () => {
21+
const checkin = {
22+
findMany: jest.fn(),
23+
update: jest.fn(),
24+
findUnique: jest.fn(),
25+
delete: jest.fn(),
26+
deleteMany: jest.fn(),
27+
};
28+
const checkinPhoto = {
29+
deleteMany: jest.fn(),
30+
};
31+
32+
const mockPrisma = { checkin, checkinPhoto } as any;
33+
class PrismaClient {
34+
constructor() {
35+
return mockPrisma;
36+
}
37+
}
38+
return { PrismaClient };
39+
});
40+
41+
// 4) 在 mock 之后导入被测路由
42+
import { GET as GET_CHECKINS } from '@/app/api/admin/checkins/route';
43+
import { PUT as PUT_CHECKIN_DETAIL, DELETE as DELETE_CHECKIN_DETAIL } from '@/app/api/admin/checkins/[id]/route';
44+
import { getServerSession } from 'next-auth';
45+
46+
const { PrismaClient } = require('@prisma/client');
47+
const prisma = new PrismaClient();
48+
jest.mock('@/app/api/auth/[...nextauth]/route', () => ({
49+
authOptions: {},
50+
}));
51+
52+
describe('管理员打卡记录接口 - 权限与列表', () => {
53+
beforeEach(() => {
54+
jest.clearAllMocks();
55+
(getServerSession as jest.Mock).mockResolvedValue({ user: { role: 'admin' } });
56+
});
57+
58+
test('未授权返回 401', async () => {
59+
(getServerSession as jest.Mock).mockResolvedValueOnce(null);
60+
const req = new Request('http://localhost/api/admin/checkins');
61+
const res = await GET_CHECKINS(req as any);
62+
const json = await res.json();
63+
64+
expect(res.status).toBe(401);
65+
expect(json.message).toBe('未授权访问');
66+
});
67+
68+
test('成功返回格式化后的打卡记录列表', async () => {
69+
const now = new Date();
70+
prisma.checkin.findMany.mockResolvedValue([
71+
{
72+
id: 'c1', userId: 'u1', routeId: 'r1', poiId: 'p1', status: 'pending', createdAt: now,
73+
user: { nickname: 'Alice' },
74+
poi: { name: '塔楼' },
75+
},
76+
]);
77+
78+
const req = new Request('http://localhost/api/admin/checkins');
79+
const res = await GET_CHECKINS(req as any);
80+
const json = await res.json();
81+
82+
expect(res.status).toBe(200);
83+
expect(json.success).toBe(true);
84+
expect(Array.isArray(json.data.checkins)).toBe(true);
85+
expect(json.data.checkins[0].user.nickname).toBe('Alice');
86+
expect(json.data.checkins[0].poi.name).toBe('塔楼');
87+
});
88+
89+
test('获取列表异常返回 500', async () => {
90+
prisma.checkin.findMany.mockRejectedValue(new Error('DB'));
91+
const req = new Request('http://localhost/api/admin/checkins');
92+
const res = await GET_CHECKINS(req as any);
93+
const json = await res.json();
94+
95+
expect(res.status).toBe(500);
96+
expect(json.success).toBe(false);
97+
});
98+
});
99+
100+
describe('PUT /api/admin/checkins/[id] 状态更新', () => {
101+
beforeEach(() => { jest.clearAllMocks(); });
102+
103+
test('合法状态更新返回 200', async () => {
104+
prisma.checkin.update.mockResolvedValue({ id: 'c1', status: 'approved' });
105+
const req = new Request('http://localhost/api/admin/checkins/c1', {
106+
method: 'PUT',
107+
headers: { 'Content-Type': 'application/json' },
108+
body: JSON.stringify({ status: 'approved' }),
109+
});
110+
const params = Promise.resolve({ id: 'c1' });
111+
const res = await PUT_CHECKIN_DETAIL(req as any, { params } as any);
112+
const json = await res.json();
113+
114+
expect(res.status).toBe(200);
115+
expect(json.data.checkin.status).toBe('approved');
116+
expect(prisma.checkin.update).toHaveBeenCalledWith({ where: { id: 'c1' }, data: { status: 'approved' } });
117+
});
118+
119+
test('非法状态返回 400', async () => {
120+
const req = new Request('http://localhost/api/admin/checkins/c1', {
121+
method: 'PUT',
122+
headers: { 'Content-Type': 'application/json' },
123+
body: JSON.stringify({ status: 'unknown' }),
124+
});
125+
const params = Promise.resolve({ id: 'c1' });
126+
const res = await PUT_CHECKIN_DETAIL(req as any, { params } as any);
127+
const json = await res.json();
128+
expect(res.status).toBe(400);
129+
expect(json.message).toBe('状态值无效');
130+
});
131+
});
132+
133+
describe('DELETE /api/admin/checkins/[id] 删除逻辑', () => {
134+
beforeEach(() => { jest.clearAllMocks(); });
135+
136+
test('不存在返回 404', async () => {
137+
prisma.checkin.findUnique.mockResolvedValue(null);
138+
const req = new Request('http://localhost/api/admin/checkins/c1', { method: 'DELETE' });
139+
const params = Promise.resolve({ id: 'c1' });
140+
const res = await DELETE_CHECKIN_DETAIL(req as any, { params } as any);
141+
const json = await res.json();
142+
143+
expect(res.status).toBe(404);
144+
expect(json.message).toBe('打卡记录不存在');
145+
});
146+
147+
test('成功删除并清理照片返回 200', async () => {
148+
prisma.checkin.findUnique.mockResolvedValue({ id: 'c1' });
149+
prisma.checkinPhoto.deleteMany.mockResolvedValue({});
150+
prisma.checkin.delete.mockResolvedValue({ id: 'c1' });
151+
152+
const req = new Request('http://localhost/api/admin/checkins/c1', { method: 'DELETE' });
153+
const params = Promise.resolve({ id: 'c1' });
154+
const res = await DELETE_CHECKIN_DETAIL(req as any, { params } as any);
155+
const json = await res.json();
156+
157+
expect(res.status).toBe(200);
158+
expect(prisma.checkinPhoto.deleteMany).toHaveBeenCalledWith({ where: { checkinId: 'c1' } });
159+
expect(prisma.checkin.delete).toHaveBeenCalledWith({ where: { id: 'c1' } });
160+
});
161+
162+
test('删除异常返回 500', async () => {
163+
prisma.checkin.findUnique.mockResolvedValue({ id: 'c1' });
164+
prisma.checkinPhoto.deleteMany.mockResolvedValue({});
165+
prisma.checkin.delete.mockRejectedValue(new Error('fail'));
166+
167+
const req = new Request('http://localhost/api/admin/checkins/c1', { method: 'DELETE' });
168+
const params = Promise.resolve({ id: 'c1' });
169+
const res = await DELETE_CHECKIN_DETAIL(req as any, { params } as any);
170+
const json = await res.json();
171+
172+
expect(res.status).toBe(500);
173+
expect(json.success).toBe(false);
174+
});
175+
});

0 commit comments

Comments
 (0)