1- import { describe , it , expect , beforeEach , afterEach , vi } from 'vitest'
2- import { ApiKeyStorage } from './api-key-storage'
1+ import { describe , it , expect , beforeEach } from 'vitest'
2+ import { ApiKeyStorage , type Storage } from './api-key-storage'
33
44describe ( 'ApiKeyStorage' , ( ) => {
55 let storage : ApiKeyStorage
6- let localStorageMock : Record < string , string >
6+ let mockStorageData : Record < string , string >
7+ let mockStorage : Storage
78 const testKeyMaterial = 'test-key-material-for-encryption'
89
910 beforeEach ( ( ) => {
10- // Mock localStorage
11- localStorageMock = { }
12-
13- vi . stubGlobal ( 'localStorage' , {
11+ // Create in-memory storage mock (no global stubbing needed)
12+ mockStorageData = { }
13+ mockStorage = {
1414 // eslint-disable-next-line unicorn/no-null
15- getItem : vi . fn ( ( key : string ) => localStorageMock [ key ] ?? null ) , // null is correct for localStorage API
16- setItem : vi . fn ( ( key : string , value : string ) => {
17- localStorageMock [ key ] = value
18- } ) ,
19- removeItem : vi . fn ( ( key : string ) => {
15+ getItem : ( key : string ) => mockStorageData [ key ] ?? null ,
16+ setItem : ( key : string , value : string ) => {
17+ mockStorageData [ key ] = value
18+ } ,
19+ removeItem : ( key : string ) => {
2020 // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
21- delete localStorageMock [ key ]
22- } ) ,
23- clear : vi . fn ( ( ) => {
24- localStorageMock = { }
25- } ) ,
26- length : 0 ,
27- key : vi . fn ( ) ,
21+ delete mockStorageData [ key ]
22+ } ,
23+ }
24+
25+ // Inject both key material and storage for deterministic testing
26+ storage = new ApiKeyStorage ( {
27+ keyMaterial : testKeyMaterial ,
28+ storage : mockStorage ,
2829 } )
29-
30- // Use constructor parameter for consistent key derivation in tests
31- storage = new ApiKeyStorage ( testKeyMaterial )
32- } )
33-
34- afterEach ( ( ) => {
35- vi . restoreAllMocks ( )
36- vi . unstubAllGlobals ( )
3730 } )
3831
3932 describe ( 'saveApiKey' , ( ) => {
@@ -42,9 +35,7 @@ describe('ApiKeyStorage', () => {
4235
4336 await storage . saveApiKey ( apiKey )
4437
45- // eslint-disable-next-line @typescript-eslint/unbound-method
46- expect ( localStorage . setItem ) . toHaveBeenCalled ( )
47- const savedValue = localStorageMock [ 'bugzilla_api_key' ]
38+ const savedValue = mockStorageData [ 'bugzilla_api_key' ]
4839 expect ( savedValue ) . toBeDefined ( )
4940 expect ( savedValue ) . not . toBe ( apiKey ) // Should be encrypted
5041 } )
@@ -53,10 +44,10 @@ describe('ApiKeyStorage', () => {
5344 const apiKey = 'test-api-key-123'
5445
5546 await storage . saveApiKey ( apiKey )
56- const firstSave = localStorageMock [ 'bugzilla_api_key' ]
47+ const firstSave = mockStorageData [ 'bugzilla_api_key' ]
5748
5849 await storage . saveApiKey ( apiKey )
59- const secondSave = localStorageMock [ 'bugzilla_api_key' ]
50+ const secondSave = mockStorageData [ 'bugzilla_api_key' ]
6051
6152 // Should have different encrypted values due to random IV
6253 expect ( firstSave ) . not . toBe ( secondSave )
@@ -65,8 +56,7 @@ describe('ApiKeyStorage', () => {
6556 it ( 'should handle empty API key' , async ( ) => {
6657 await storage . saveApiKey ( '' )
6758
68- // eslint-disable-next-line @typescript-eslint/unbound-method
69- expect ( localStorage . setItem ) . toHaveBeenCalled ( )
59+ expect ( mockStorageData [ 'bugzilla_api_key' ] ) . toBeDefined ( )
7060 } )
7161 } )
7262
@@ -87,7 +77,7 @@ describe('ApiKeyStorage', () => {
8777 } )
8878
8979 it ( 'should return undefined when localStorage value is invalid' , async ( ) => {
90- localStorageMock [ 'bugzilla_api_key' ] = 'invalid-encrypted-data'
80+ mockStorageData [ 'bugzilla_api_key' ] = 'invalid-encrypted-data'
9181
9282 const retrieved = await storage . getApiKey ( )
9383
@@ -96,7 +86,7 @@ describe('ApiKeyStorage', () => {
9686
9787 it ( 'should handle decryption errors gracefully' , async ( ) => {
9888 // Set malformed encrypted data
99- localStorageMock [ 'bugzilla_api_key' ] = JSON . stringify ( {
89+ mockStorageData [ 'bugzilla_api_key' ] = JSON . stringify ( {
10090 iv : 'invalid-iv' ,
10191 encryptedData : 'invalid-data' ,
10292 } )
@@ -113,9 +103,7 @@ describe('ApiKeyStorage', () => {
113103
114104 storage . clearApiKey ( )
115105
116- // eslint-disable-next-line @typescript-eslint/unbound-method
117- expect ( localStorage . removeItem ) . toHaveBeenCalledWith ( 'bugzilla_api_key' )
118- expect ( localStorageMock [ 'bugzilla_api_key' ] ) . toBeUndefined ( )
106+ expect ( mockStorageData [ 'bugzilla_api_key' ] ) . toBeUndefined ( )
119107 } )
120108
121109 it ( 'should not throw error when no key exists' , ( ) => {
@@ -144,10 +132,10 @@ describe('ApiKeyStorage', () => {
144132 describe ( 'encryption' , ( ) => {
145133 it ( 'should encrypt different keys to different values' , async ( ) => {
146134 await storage . saveApiKey ( 'key1' )
147- const encrypted1 = localStorageMock [ 'bugzilla_api_key' ]
135+ const encrypted1 = mockStorageData [ 'bugzilla_api_key' ]
148136
149137 await storage . saveApiKey ( 'key2' )
150- const encrypted2 = localStorageMock [ 'bugzilla_api_key' ]
138+ const encrypted2 = mockStorageData [ 'bugzilla_api_key' ]
151139
152140 expect ( encrypted1 ) . not . toBe ( encrypted2 )
153141 } )
0 commit comments