11import type {
2+ ApiKeyJSON ,
23 ClientJSON ,
34 OrganizationMembershipJSON ,
45 OrganizationSuggestionJSON ,
@@ -9,6 +10,7 @@ import { http, HttpResponse, type JsonBodyType } from 'msw';
910import { setupWorker } from 'msw/browser' ;
1011
1112import {
13+ fapiApiKey ,
1214 fapiClient ,
1315 type FapiEnvironment ,
1416 fapiEnvironment ,
@@ -29,6 +31,7 @@ export interface FakeFapiState {
2931 memberships : OrganizationMembershipJSON [ ] ;
3032 invitations : UserOrganizationInvitationJSON [ ] ;
3133 suggestions : OrganizationSuggestionJSON [ ] ;
34+ apiKeys : ApiKeyJSON [ ] ;
3235}
3336
3437export type FakeFapiSeed = Partial < FakeFapiState > ;
@@ -71,6 +74,10 @@ function findSession(state: FakeFapiState, id: unknown): SessionJSON | undefined
7174 return state . client . sessions . find ( session => session . id === id ) ;
7275}
7376
77+ function error ( code : string , status = 400 ) {
78+ return HttpResponse . json ( { errors : [ { code, message : code , long_message : code } ] } , { status } ) ;
79+ }
80+
7481function missing ( ) {
7582 return HttpResponse . json ( { errors : [ { code : 'resource_not_found' , message : 'not found' } ] } , { status : 404 } ) ;
7683}
@@ -82,6 +89,7 @@ export function serveFapi(seed: FakeFapiSeed = {}): FakeFapiState {
8289 memberships : [ ] ,
8390 invitations : [ ] ,
8491 suggestions : [ ] ,
92+ apiKeys : [ ] ,
8593 ...seed ,
8694 } ;
8795
@@ -161,6 +169,41 @@ export function serveFapi(seed: FakeFapiSeed = {}): FakeFapiState {
161169 state . suggestions = state . suggestions . map ( s => ( s . id === accepted . id ? accepted : s ) ) ;
162170 return envelope ( accepted , state . client ) ;
163171 } ) ,
172+ http . get ( fapiUrl ( '/api_keys' ) , ( { request } ) => {
173+ const url = new URL ( request . url ) ;
174+ const subject = url . searchParams . get ( 'subject' ) ;
175+ const query = ( url . searchParams . get ( 'query' ) ?? '' ) . toLowerCase ( ) ;
176+ const keys = state . apiKeys . filter (
177+ key => ! key . revoked && key . subject === subject && key . name . toLowerCase ( ) . includes ( query ) ,
178+ ) ;
179+ return HttpResponse . json ( page ( keys , url ) ) ;
180+ } ) ,
181+ http . post ( fapiUrl ( '/api_keys' ) , async ( { request } ) => {
182+ const body : { name : string ; subject : string ; seconds_until_expiration ?: number } = await request . json ( ) ;
183+ if ( state . apiKeys . some ( key => ! key . revoked && key . subject === body . subject && key . name === body . name ) ) {
184+ return error ( 'token_creation_conflict' , 409 ) ;
185+ }
186+ const now = Date . now ( ) ;
187+ const created = fapiApiKey ( {
188+ id : `ak_${ state . apiKeys . length + 1 } ` ,
189+ name : body . name ,
190+ subject : body . subject ,
191+ expiration : body . seconds_until_expiration ? now + body . seconds_until_expiration * 1000 : null ,
192+ created_at : now ,
193+ updated_at : now ,
194+ } ) ;
195+ state . apiKeys = [ created , ...state . apiKeys ] ;
196+ return HttpResponse . json ( { ...created , secret : `ak_secret_${ created . id } ` } ) ;
197+ } ) ,
198+ http . post ( fapiUrl ( '/api_keys/:id/revoke' ) , ( { params } ) => {
199+ const key = state . apiKeys . find ( k => k . id === params . id ) ;
200+ if ( ! key ) {
201+ return missing ( ) ;
202+ }
203+ const revoked = { ...key , revoked : true } ;
204+ state . apiKeys = state . apiKeys . map ( k => ( k . id === revoked . id ? revoked : k ) ) ;
205+ return HttpResponse . json ( revoked ) ;
206+ } ) ,
164207 ) ;
165208
166209 return state ;
@@ -211,7 +254,6 @@ export function holdRequests(method: 'get' | 'post', path: string): HeldRequests
211254 return {
212255 requests,
213256 release : ( ) => settle ( undefined ) ,
214- fail : ( code = 'form_param_invalid' ) =>
215- settle ( HttpResponse . json ( { errors : [ { code, message : code , long_message : code } ] } , { status : 400 } ) ) ,
257+ fail : ( code = 'form_param_invalid' ) => settle ( error ( code ) ) ,
216258 } ;
217259}
0 commit comments