@@ -28,6 +28,32 @@ function decodeJwt(token: string) {
2828 return { header, payload, signatureB64 : signatureB64 ! , headerB64 : headerB64 ! , payloadB64 : payloadB64 ! }
2929}
3030
31+ // In-memory StorageOp for testing (matches id.org.ai's StorageOp type)
32+ function createMemoryStorageOp ( ) {
33+ const store = new Map < string , unknown > ( )
34+ return async ( op : { op : 'get' | 'put' | 'delete' | 'list' ; key ?: string ; value ?: unknown ; options ?: { prefix ?: string } } ) => {
35+ switch ( op . op ) {
36+ case 'get' :
37+ return { value : store . get ( op . key ! ) ?? undefined }
38+ case 'put' :
39+ store . set ( op . key ! , op . value )
40+ return { }
41+ case 'delete' :
42+ store . delete ( op . key ! )
43+ return { }
44+ case 'list' : {
45+ const entries : Record < string , unknown > = { }
46+ for ( const [ k , v ] of store ) {
47+ if ( ! op . options ?. prefix || k . startsWith ( op . options . prefix ) ) entries [ k ] = v
48+ }
49+ return entries
50+ }
51+ default :
52+ return { }
53+ }
54+ }
55+ }
56+
3157describe ( 'SigningKey generation' , ( ) => {
3258 it ( 'generateSigningKey creates a valid RSA-2048 key pair' , async ( ) => {
3359 const key = await generateSigningKey ( )
@@ -228,7 +254,7 @@ describe('signAccessToken', () => {
228254
229255describe ( 'SigningKeyManager' , ( ) => {
230256 it ( 'initializes with a generated key when no keys exist' , async ( ) => {
231- const manager = new SigningKeyManager ( )
257+ const manager = new SigningKeyManager ( createMemoryStorageOp ( ) )
232258 const key = await manager . getCurrentKey ( )
233259
234260 expect ( key ) . toBeDefined ( )
@@ -237,34 +263,34 @@ describe('SigningKeyManager', () => {
237263 } )
238264
239265 it ( 'getActiveKey returns the same key on subsequent calls' , async ( ) => {
240- const manager = new SigningKeyManager ( )
266+ const manager = new SigningKeyManager ( createMemoryStorageOp ( ) )
241267 const key1 = await manager . getCurrentKey ( )
242268 const key2 = await manager . getCurrentKey ( )
243269 expect ( key1 . kid ) . toBe ( key2 . kid )
244270 } )
245271
246272 it ( 'getAllKeys returns all keys' , async ( ) => {
247- const manager = new SigningKeyManager ( { maxKeys : 3 } )
273+ const manager = new SigningKeyManager ( createMemoryStorageOp ( ) )
248274 await manager . getCurrentKey ( )
249275 await manager . rotateKey ( )
250276
251277 const allKeys = manager . getAllKeys ( )
252278 expect ( allKeys ) . toHaveLength ( 2 )
253279 } )
254280
255- it ( 'toJWKS returns JWKs for all keys' , async ( ) => {
256- const manager = new SigningKeyManager ( { maxKeys : 3 } )
281+ it ( 'getJWKS returns JWKs for all keys' , async ( ) => {
282+ const manager = new SigningKeyManager ( createMemoryStorageOp ( ) )
257283 await manager . getCurrentKey ( )
258284 await manager . rotateKey ( )
259285
260- const jwks = await manager . toJWKS ( )
286+ const jwks = await manager . getJWKS ( )
261287 expect ( jwks . keys ) . toHaveLength ( 2 )
262288 expect ( jwks . keys [ 0 ] ! . kty ) . toBe ( 'RSA' )
263289 expect ( jwks . keys [ 1 ] ! . kty ) . toBe ( 'RSA' )
264290 } )
265291
266292 it ( 'rotateKey adds a new key and makes it active' , async ( ) => {
267- const manager = new SigningKeyManager ( { maxKeys : 3 } )
293+ const manager = new SigningKeyManager ( createMemoryStorageOp ( ) )
268294 const original = await manager . getCurrentKey ( )
269295 const rotated = await manager . rotateKey ( )
270296 const current = await manager . getCurrentKey ( )
@@ -274,26 +300,27 @@ describe('SigningKeyManager', () => {
274300 } )
275301
276302 it ( 'rotateKey respects maxKeys limit and removes oldest' , async ( ) => {
277- const manager = new SigningKeyManager ( { maxKeys : 2 } )
303+ const manager = new SigningKeyManager ( createMemoryStorageOp ( ) )
278304 const first = await manager . getCurrentKey ( )
279305 await manager . rotateKey ( )
280306 await manager . rotateKey ( )
281307
282308 const allKeys = manager . getAllKeys ( )
309+ // Default maxKeys is 2, so after 2 rotations we should have 2 keys
283310 expect ( allKeys ) . toHaveLength ( 2 )
284311 // The first key should have been removed
285312 expect ( allKeys . find ( ( k ) => k . kid === first . kid ) ) . toBeUndefined ( )
286313 } )
287314
288315 it ( 'key persistence: exportKeys/loadKeys roundtrip works' , async ( ) => {
289- const manager1 = new SigningKeyManager ( { maxKeys : 3 } )
316+ const manager1 = new SigningKeyManager ( createMemoryStorageOp ( ) )
290317 await manager1 . getCurrentKey ( )
291318 await manager1 . rotateKey ( )
292319
293320 const exported = await manager1 . exportKeys ( )
294321 expect ( exported ) . toHaveLength ( 2 )
295322
296- const manager2 = new SigningKeyManager ( { maxKeys : 3 } )
323+ const manager2 = new SigningKeyManager ( createMemoryStorageOp ( ) )
297324 await manager2 . loadKeys ( exported )
298325
299326 const keys1 = manager1 . getAllKeys ( )
@@ -322,7 +349,7 @@ describe('SigningKeyManager', () => {
322349 } )
323350
324351 it ( 'signAccessToken uses the current key' , async ( ) => {
325- const manager = new SigningKeyManager ( )
352+ const manager = new SigningKeyManager ( createMemoryStorageOp ( ) )
326353 const token = await manager . signAccessToken (
327354 { sub : 'user-1' , client_id : 'client-1' } ,
328355 { issuer : 'https://oauth.do' }
@@ -334,7 +361,7 @@ describe('SigningKeyManager', () => {
334361 } )
335362
336363 it ( 'default maxKeys is 2' , async ( ) => {
337- const manager = new SigningKeyManager ( )
364+ const manager = new SigningKeyManager ( createMemoryStorageOp ( ) )
338365 await manager . getCurrentKey ( )
339366 await manager . rotateKey ( )
340367 await manager . rotateKey ( )
0 commit comments