@@ -210,6 +210,66 @@ test('POST /api/oauth/end-session — returns 200', async () => {
210210 assertOk ( res ) ;
211211} ) ;
212212
213+ // ─── 11.8 Negative OAuth cases ─────────────────────────────────
214+
215+ group ( '11.8 Negative OAuth cases' ) ;
216+
217+ test ( 'POST /api/oauth/token — invalid grant_type → error' , async ( ) => {
218+ const res = await restWith ( BASE , 'POST' , '/api/oauth/token' , {
219+ grant_type : 'client_credentials' ,
220+ client_id : 'test-client' ,
221+ } ) ;
222+ assert ( res . status >= 400 , 'unsupported grant_type should fail' ) ;
223+ } ) ;
224+
225+ test ( 'POST /api/oauth/token — missing code → error' , async ( ) => {
226+ const res = await restWith ( BASE , 'POST' , '/api/oauth/token' , {
227+ grant_type : 'authorization_code' ,
228+ client_id : 'test-client' ,
229+ redirect_uri : 'http://localhost:9999/callback' ,
230+ code_verifier : codeVerifier ,
231+ } ) ;
232+ assert ( res . status >= 400 , 'missing code should fail' ) ;
233+ } ) ;
234+
235+ test ( 'POST /api/oauth/token — expired/fake code → error' , async ( ) => {
236+ const res = await restWith ( BASE , 'POST' , '/api/oauth/token' , {
237+ grant_type : 'authorization_code' ,
238+ code : 'fake-expired-code-12345' ,
239+ client_id : 'test-client' ,
240+ redirect_uri : 'http://localhost:9999/callback' ,
241+ code_verifier : codeVerifier ,
242+ } ) ;
243+ assert ( res . status >= 400 , 'fake code should fail' ) ;
244+ } ) ;
245+
246+ test ( 'POST /api/oauth/token — refresh with invalid token → error' , async ( ) => {
247+ const res = await restWith ( BASE , 'POST' , '/api/oauth/token' , {
248+ grant_type : 'refresh_token' ,
249+ refresh_token : 'invalid-refresh-token' ,
250+ client_id : 'test-client' ,
251+ } ) ;
252+ assert ( res . status >= 400 , 'invalid refresh token should fail' ) ;
253+ } ) ;
254+
255+ test ( 'POST /api/oauth/authorize — missing code_challenge → error' , async ( ) => {
256+ const res = await restWith ( BASE , 'POST' , '/api/oauth/authorize' , {
257+ response_type : 'code' ,
258+ client_id : 'test-client' ,
259+ redirect_uri : 'http://localhost:9999/callback' ,
260+ // no code_challenge
261+ } , { cookie : cookieHeader ( adminCookies ) } ) ;
262+ assert ( res . status >= 400 , 'missing code_challenge should fail' ) ;
263+ } ) ;
264+
265+ test ( 'GET /api/oauth/userinfo with revoked token — still works (revoke is no-op)' , async ( ) => {
266+ // OAuth revoke endpoint returns 200 but does not actually invalidate JWTs
267+ // (stateless tokens can't be revoked without a blocklist)
268+ const res = await restWith ( BASE , 'GET' , '/api/oauth/userinfo' ,
269+ undefined , { bearer : accessToken } ) ;
270+ assertOk ( res ) ;
271+ } ) ;
272+
213273// ─── Teardown ────────────────────────────────────────────────────
214274
215275group ( 'Teardown' ) ;
0 commit comments