1+ import { NextApiRequest , NextApiResponse } from 'next' ;
2+ import reportHandler from './report' ;
3+ import { sendAbuseReport } from '../../lib/slack-notifier' ;
4+ import got from '../../lib/got-client' ;
5+
6+ jest . mock ( '../../lib/slack-notifier' ) ;
7+ jest . mock ( '../../lib/got-client' ) ;
8+
9+ describe ( '/api/report' , ( ) => {
10+ let req : Partial < NextApiRequest > ;
11+ let res : Partial < NextApiResponse > ;
12+ let statusMock : jest . Mock ;
13+ let jsonMock : jest . Mock ;
14+ let endMock : jest . Mock ;
15+
16+ beforeEach ( ( ) => {
17+ statusMock = jest . fn ( ) . mockReturnThis ( ) ;
18+ jsonMock = jest . fn ( ) . mockReturnThis ( ) ;
19+ endMock = jest . fn ( ) . mockReturnThis ( ) ;
20+
21+ req = {
22+ method : 'POST' ,
23+ body : {
24+ playbackId : 'test-playback-id' ,
25+ reason : 'inappropriate content'
26+ }
27+ } ;
28+
29+ res = {
30+ status : statusMock ,
31+ json : jsonMock ,
32+ end : endMock ,
33+ setHeader : jest . fn ( )
34+ } ;
35+
36+ jest . clearAllMocks ( ) ;
37+ ( got . post as jest . Mock ) . mockResolvedValue ( { } ) ;
38+ } ) ;
39+
40+ describe ( 'POST requests' , ( ) => {
41+ it ( 'should handle valid abuse report' , async ( ) => {
42+ ( sendAbuseReport as jest . Mock ) . mockResolvedValue ( null ) ;
43+
44+ await reportHandler ( req as NextApiRequest , res as NextApiResponse ) ;
45+
46+ expect ( sendAbuseReport ) . toHaveBeenCalledWith ( {
47+ playbackId : 'test-playback-id' ,
48+ reason : 'inappropriate content' ,
49+ comment : undefined
50+ } ) ;
51+ expect ( jsonMock ) . toHaveBeenCalledWith ( { message : 'thank you' } ) ;
52+ } ) ;
53+
54+ it ( 'should handle report with comment' , async ( ) => {
55+ req . body = {
56+ playbackId : 'test-id' ,
57+ reason : 'spam' ,
58+ comment : 'This is additional context'
59+ } ;
60+ ( sendAbuseReport as jest . Mock ) . mockResolvedValue ( null ) ;
61+
62+ await reportHandler ( req as NextApiRequest , res as NextApiResponse ) ;
63+
64+ expect ( sendAbuseReport ) . toHaveBeenCalledWith ( {
65+ playbackId : 'test-id' ,
66+ reason : 'spam' ,
67+ comment : 'This is additional context'
68+ } ) ;
69+ } ) ;
70+
71+ it ( 'should send to Airtable when configured' , async ( ) => {
72+ process . env . AIRTABLE_KEY = 'test-key' ;
73+ process . env . AIRTABLE_BASE_ID = 'test-base' ;
74+
75+ await reportHandler ( req as NextApiRequest , res as NextApiResponse ) ;
76+
77+ expect ( got . post ) . toHaveBeenCalledWith (
78+ 'https://api.airtable.com/v0/test-base/Reported' ,
79+ expect . objectContaining ( {
80+ headers : {
81+ Authorization : 'Bearer test-key'
82+ } ,
83+ json : {
84+ records : [
85+ {
86+ fields : {
87+ playbackId : 'test-playback-id' ,
88+ reason : 'inappropriate content' ,
89+ comment : undefined ,
90+ status : 'Pending'
91+ }
92+ }
93+ ]
94+ }
95+ } )
96+ ) ;
97+
98+ delete process . env . AIRTABLE_KEY ;
99+ delete process . env . AIRTABLE_BASE_ID ;
100+ } ) ;
101+
102+ it ( 'should continue even if Airtable fails' , async ( ) => {
103+ process . env . AIRTABLE_KEY = 'test-key' ;
104+ process . env . AIRTABLE_BASE_ID = 'test-base' ;
105+ ( got . post as jest . Mock ) . mockRejectedValueOnce ( new Error ( 'Airtable error' ) ) ;
106+ ( sendAbuseReport as jest . Mock ) . mockResolvedValue ( null ) ;
107+
108+ await reportHandler ( req as NextApiRequest , res as NextApiResponse ) ;
109+
110+ expect ( sendAbuseReport ) . toHaveBeenCalled ( ) ;
111+ expect ( jsonMock ) . toHaveBeenCalledWith ( { message : 'thank you' } ) ;
112+
113+ delete process . env . AIRTABLE_KEY ;
114+ delete process . env . AIRTABLE_BASE_ID ;
115+ } ) ;
116+
117+ it ( 'should continue even if Slack fails' , async ( ) => {
118+ ( sendAbuseReport as jest . Mock ) . mockRejectedValue ( new Error ( 'Slack error' ) ) ;
119+
120+ await reportHandler ( req as NextApiRequest , res as NextApiResponse ) ;
121+
122+ expect ( jsonMock ) . toHaveBeenCalledWith ( { message : 'thank you' } ) ;
123+ } ) ;
124+ } ) ;
125+
126+ describe ( 'non-POST requests' , ( ) => {
127+ it ( 'should reject GET requests' , async ( ) => {
128+ req . method = 'GET' ;
129+
130+ await reportHandler ( req as NextApiRequest , res as NextApiResponse ) ;
131+
132+ expect ( res . setHeader ) . toHaveBeenCalledWith ( 'Allow' , [ 'POST' ] ) ;
133+ expect ( statusMock ) . toHaveBeenCalledWith ( 405 ) ;
134+ expect ( endMock ) . toHaveBeenCalledWith ( 'Method GET Not Allowed' ) ;
135+ } ) ;
136+
137+ it ( 'should reject PUT requests' , async ( ) => {
138+ req . method = 'PUT' ;
139+
140+ await reportHandler ( req as NextApiRequest , res as NextApiResponse ) ;
141+
142+ expect ( res . setHeader ) . toHaveBeenCalledWith ( 'Allow' , [ 'POST' ] ) ;
143+ expect ( statusMock ) . toHaveBeenCalledWith ( 405 ) ;
144+ expect ( endMock ) . toHaveBeenCalledWith ( 'Method PUT Not Allowed' ) ;
145+ } ) ;
146+
147+ it ( 'should reject DELETE requests' , async ( ) => {
148+ req . method = 'DELETE' ;
149+
150+ await reportHandler ( req as NextApiRequest , res as NextApiResponse ) ;
151+
152+ expect ( res . setHeader ) . toHaveBeenCalledWith ( 'Allow' , [ 'POST' ] ) ;
153+ expect ( statusMock ) . toHaveBeenCalledWith ( 405 ) ;
154+ expect ( endMock ) . toHaveBeenCalledWith ( 'Method DELETE Not Allowed' ) ;
155+ } ) ;
156+ } ) ;
157+ } ) ;
0 commit comments