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