11import { CachedDIDResolver } from './cached-did-resolver' ;
2+ import { storageService } from '../storage' ;
23
34const mockStorageService = {
45 getItem : jest . fn ( ) ,
56 setItem : jest . fn ( ) ,
7+ removeItem : jest . fn ( ) ,
8+ getAllKeys : jest . fn ( ) ,
69} ;
710
811jest . mock ( '../storage' , ( ) => ( {
@@ -20,13 +23,13 @@ describe('CachedDIDResolver', () => {
2023 beforeEach ( ( ) => {
2124 jest . clearAllMocks ( ) ;
2225 mockStorageService . getItem . mockResolvedValue ( null ) ;
26+ mockStorageService . getAllKeys . mockResolvedValue ( [ ] ) ;
2327 resolver = new CachedDIDResolver ( mockRouter ) ;
2428 } ) ;
2529
2630 describe ( 'constructor' , ( ) => {
2731 it ( 'should initialize with default TTL' , ( ) => {
2832 expect ( resolver ) . toBeDefined ( ) ;
29- expect ( mockStorageService . getItem ) . toHaveBeenCalledWith ( 'did-cache' ) ;
3033 } ) ;
3134
3235 it ( 'should initialize with custom TTL' , ( ) => {
@@ -42,27 +45,54 @@ describe('CachedDIDResolver', () => {
4245
4346 it ( 'should resolve DID and cache the result on cache miss' , async ( ) => {
4447 mockRouter . resolve . mockResolvedValue ( testResult ) ;
48+ mockStorageService . getItem . mockResolvedValue ( null ) ;
4549
4650 const result = await resolver . resolve ( testDid ) ;
4751
4852 expect ( mockRouter . resolve ) . toHaveBeenCalledWith ( testDid ) ;
4953 expect ( result ) . toEqual ( testResult ) ;
50- expect ( mockStorageService . setItem ) . toHaveBeenCalled ( ) ;
54+ expect ( mockStorageService . setItem ) . toHaveBeenCalledWith (
55+ `did-cache:${ testDid } ` ,
56+ JSON . stringify ( {
57+ value : testResult ,
58+ id : testDid ,
59+ timestamp : expect . any ( Number ) ,
60+ } )
61+ ) ;
62+ } ) ;
63+
64+ it ( 'should use result.id for cache key instead of input did' , async ( ) => {
65+ const inputDid = 'did:example:input' ;
66+ const resultDid = 'did:example:resolved' ;
67+ const testResultWithDifferentId = { id : resultDid , publicKey : [ ] } ;
68+
69+ mockRouter . resolve . mockResolvedValue ( testResultWithDifferentId ) ;
70+ mockStorageService . getItem . mockResolvedValue ( null ) ;
71+
72+ const result = await resolver . resolve ( inputDid ) ;
73+
74+ expect ( result ) . toEqual ( testResultWithDifferentId ) ;
75+ // Should use result.id (resultDid) for the cache key, not inputDid
76+ expect ( mockStorageService . setItem ) . toHaveBeenCalledWith (
77+ `did-cache:${ resultDid } ` ,
78+ JSON . stringify ( {
79+ value : testResultWithDifferentId ,
80+ id : resultDid ,
81+ timestamp : expect . any ( Number ) ,
82+ } )
83+ ) ;
5184 } ) ;
5285
5386 it ( 'should return cached result on cache hit' , async ( ) => {
5487 const cachedData = {
55- [ testDid ] : {
56- value : testResult ,
57- id : testDid ,
58- timestamp : Date . now ( ) ,
59- } ,
88+ value : testResult ,
89+ id : testDid ,
90+ timestamp : Date . now ( ) ,
6091 } ;
61-
92+
6293 mockStorageService . getItem . mockResolvedValue ( JSON . stringify ( cachedData ) ) ;
63- const resolverWithCache = new CachedDIDResolver ( mockRouter ) ;
64-
65- const result = await resolverWithCache . resolve ( testDid ) ;
94+
95+ const result = await resolver . resolve ( testDid ) ;
6696
6797 expect ( result ) . toEqual ( testResult ) ;
6898 expect ( mockRouter . resolve ) . not . toHaveBeenCalled ( ) ;
@@ -72,72 +102,131 @@ describe('CachedDIDResolver', () => {
72102 const expiredTimestamp = Date . now ( ) - ( 8 * 24 * 60 * 60 * 1000 ) ; // 8 days ago (expired)
73103 const staleResult = { id : testDid , publicKey : [ ] , stale : true } ;
74104 const freshResult = { id : testDid , publicKey : [ ] , fresh : true } ;
75-
105+
76106 const cachedData = {
77- [ testDid ] : {
78- value : staleResult ,
79- id : testDid ,
80- timestamp : expiredTimestamp ,
81- } ,
107+ value : staleResult ,
108+ id : testDid ,
109+ timestamp : expiredTimestamp ,
82110 } ;
83111
84112 mockStorageService . getItem . mockResolvedValue ( JSON . stringify ( cachedData ) ) ;
85113 mockRouter . resolve . mockResolvedValue ( freshResult ) ;
86-
87- const resolverWithExpiredCache = new CachedDIDResolver ( mockRouter ) ;
88- const result = await resolverWithExpiredCache . resolve ( testDid ) ;
114+
115+ const result = await resolver . resolve ( testDid ) ;
89116
90117 // Should return stale value immediately
91118 expect ( result ) . toEqual ( staleResult ) ;
92-
119+
93120 // Background refresh should be triggered (not awaited)
94121 // Give it time to complete
95- await new Promise ( resolve => setTimeout ( resolve , 10 ) ) ;
96-
122+ await new Promise ( resolve => setTimeout ( resolve , 50 ) ) ;
123+
97124 expect ( mockRouter . resolve ) . toHaveBeenCalledWith ( testDid ) ;
125+ expect ( mockStorageService . setItem ) . toHaveBeenCalledWith (
126+ `did-cache:${ testDid } ` ,
127+ JSON . stringify ( {
128+ value : freshResult ,
129+ id : testDid ,
130+ timestamp : expect . any ( Number ) ,
131+ } )
132+ ) ;
98133 } ) ;
99- } ) ;
100134
101- describe ( 'getCache' , ( ) => {
102- it ( 'should return the current cache' , ( ) => {
103- const cache = resolver . getCache ( ) ;
104- expect ( cache ) . toBeDefined ( ) ;
105- expect ( typeof cache ) . toBe ( 'object' ) ;
135+ it ( 'should handle corrupted cache data gracefully' , async ( ) => {
136+ const testDid = 'did:example:corrupted' ;
137+ const testResult = { id : testDid , publicKey : [ ] } ;
138+
139+ // Return invalid JSON from storage
140+ mockStorageService . getItem . mockResolvedValue ( 'invalid json {' ) ;
141+ mockRouter . resolve . mockResolvedValue ( testResult ) ;
142+
143+ const consoleSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
144+
145+ const result = await resolver . resolve ( testDid ) ;
146+
147+ expect ( consoleSpy ) . toHaveBeenCalledWith ( 'Error parsing cache entry:' , expect . any ( Error ) ) ;
148+ expect ( mockRouter . resolve ) . toHaveBeenCalledWith ( testDid ) ;
149+ expect ( result ) . toEqual ( testResult ) ;
150+
151+ consoleSpy . mockRestore ( ) ;
106152 } ) ;
107153 } ) ;
108154
109- describe ( 'setCache' , ( ) => {
110- it ( 'should set the cache' , ( ) => {
111- const newCache = { 'did:example:456' : { value : { } , id : 'did:example:456' , timestamp : Date . now ( ) } } ;
112- resolver . setCache ( newCache ) ;
113- expect ( resolver . getCache ( ) ) . toEqual ( newCache ) ;
155+ describe ( 'getCachedDIDs' , ( ) => {
156+ it ( 'should return empty array when no cached DIDs exist' , async ( ) => {
157+ mockStorageService . getAllKeys . mockResolvedValue ( [ ] ) ;
158+
159+ const cachedDIDs = await resolver . getCachedDIDs ( ) ;
160+
161+ expect ( cachedDIDs ) . toEqual ( [ ] ) ;
162+ expect ( mockStorageService . getAllKeys ) . toHaveBeenCalled ( ) ;
163+ } ) ;
164+
165+ it ( 'should return list of cached DIDs' , async ( ) => {
166+ const allKeys = [
167+ 'did-cache:did:example:123' ,
168+ 'did-cache:did:example:456' ,
169+ 'did-cache:did:dock:789' ,
170+ 'other-key:something' ,
171+ 'user-preference:theme' ,
172+ ] ;
173+
174+ mockStorageService . getAllKeys . mockResolvedValue ( allKeys ) ;
175+
176+ const cachedDIDs = await resolver . getCachedDIDs ( ) ;
177+
178+ expect ( cachedDIDs ) . toEqual ( [
179+ 'did:example:123' ,
180+ 'did:example:456' ,
181+ 'did:dock:789' ,
182+ ] ) ;
183+ expect ( mockStorageService . getAllKeys ) . toHaveBeenCalled ( ) ;
184+ } ) ;
185+
186+ it ( 'should handle empty storage correctly' , async ( ) => {
187+ mockStorageService . getAllKeys . mockResolvedValue ( [ ] ) ;
188+
189+ const cachedDIDs = await resolver . getCachedDIDs ( ) ;
190+
191+ expect ( cachedDIDs ) . toEqual ( [ ] ) ;
114192 } ) ;
115193 } ) ;
116194
117195 describe ( 'clearCache' , ( ) => {
118- beforeEach ( ( ) => {
119- const cache = {
120- 'did:example:123' : { value : { } , id : 'did:example:123' , timestamp : Date . now ( ) } ,
121- 'did:example:456' : { value : { } , id : 'did:example:456' , timestamp : Date . now ( ) } ,
122- } ;
123- resolver . setCache ( cache ) ;
196+ it ( 'should clear specific DID from cache' , async ( ) => {
197+ const didToRemove = 'did:example:123' ;
198+
199+ await resolver . clearCache ( didToRemove ) ;
200+
201+ expect ( mockStorageService . removeItem ) . toHaveBeenCalledWith ( `did-cache:${ didToRemove } ` ) ;
202+ expect ( mockStorageService . removeItem ) . toHaveBeenCalledTimes ( 1 ) ;
124203 } ) ;
125204
126- it ( 'should clear specific DID from cache' , ( ) => {
127- resolver . clearCache ( 'did:example:123' ) ;
128-
129- const cache = resolver . getCache ( ) ;
130- expect ( cache [ 'did:example:123' ] ) . toBeUndefined ( ) ;
131- expect ( cache [ 'did:example:456' ] ) . toBeDefined ( ) ;
132- expect ( mockStorageService . setItem ) . toHaveBeenCalled ( ) ;
205+ it ( 'should clear entire cache when no DID specified' , async ( ) => {
206+ const allKeys = [
207+ 'did-cache:did:example:123' ,
208+ 'did-cache:did:example:456' ,
209+ 'other-key:something' ,
210+ ] ;
211+
212+ mockStorageService . getAllKeys . mockResolvedValue ( allKeys ) ;
213+
214+ await resolver . clearCache ( ) ;
215+
216+ expect ( mockStorageService . getAllKeys ) . toHaveBeenCalled ( ) ;
217+ expect ( mockStorageService . removeItem ) . toHaveBeenCalledWith ( 'did-cache:did:example:123' ) ;
218+ expect ( mockStorageService . removeItem ) . toHaveBeenCalledWith ( 'did-cache:did:example:456' ) ;
219+ expect ( mockStorageService . removeItem ) . not . toHaveBeenCalledWith ( 'other-key:something' ) ;
220+ expect ( mockStorageService . removeItem ) . toHaveBeenCalledTimes ( 2 ) ;
133221 } ) ;
134222
135- it ( 'should clear entire cache when no DID specified' , ( ) => {
136- resolver . clearCache ( ) ;
137-
138- const cache = resolver . getCache ( ) ;
139- expect ( Object . keys ( cache ) ) . toHaveLength ( 0 ) ;
140- expect ( mockStorageService . setItem ) . toHaveBeenCalled ( ) ;
223+ it ( 'should handle clearing cache when no cached entries exist' , async ( ) => {
224+ mockStorageService . getAllKeys . mockResolvedValue ( [ ] ) ;
225+
226+ await resolver . clearCache ( ) ;
227+
228+ expect ( mockStorageService . getAllKeys ) . toHaveBeenCalled ( ) ;
229+ expect ( mockStorageService . removeItem ) . not . toHaveBeenCalled ( ) ;
141230 } ) ;
142231 } ) ;
143232
@@ -151,34 +240,49 @@ describe('CachedDIDResolver', () => {
151240 expect ( mockRouter . supports ) . toHaveBeenCalledWith ( testId ) ;
152241 expect ( result ) . toBe ( true ) ;
153242 } ) ;
243+
244+ it ( 'should return false when router does not support DID' , ( ) => {
245+ const testId = 'did:unsupported:123' ;
246+ mockRouter . supports . mockReturnValue ( false ) ;
247+
248+ const result = resolver . supports ( testId ) ;
249+
250+ expect ( mockRouter . supports ) . toHaveBeenCalledWith ( testId ) ;
251+ expect ( result ) . toBe ( false ) ;
252+ } ) ;
154253 } ) ;
155254
156- describe ( 'loadCache' , ( ) => {
157- it ( 'should load cache from storage on initialization' , async ( ) => {
255+ describe ( 'background refresh' , ( ) => {
256+ it ( 'should handle background refresh failure gracefully' , async ( ) => {
257+ const testDid = 'did:example:refresh-fail' ;
258+ const expiredTimestamp = Date . now ( ) - ( 8 * 24 * 60 * 60 * 1000 ) ;
259+ const staleResult = { id : testDid , publicKey : [ ] } ;
260+
158261 const cachedData = {
159- 'did:example:123' : {
160- value : { id : 'did:example:123' } ,
161- id : 'did:example:123' ,
162- timestamp : Date . now ( ) ,
163- } ,
262+ value : staleResult ,
263+ id : testDid ,
264+ timestamp : expiredTimestamp ,
164265 } ;
165266
166267 mockStorageService . getItem . mockResolvedValue ( JSON . stringify ( cachedData ) ) ;
167-
168- const newResolver = new CachedDIDResolver ( mockRouter ) ;
169- await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ; // Allow async loadCache to complete
268+ mockRouter . resolve . mockRejectedValue ( new Error ( 'Network error' ) ) ;
170269
171- expect ( mockStorageService . getItem ) . toHaveBeenCalledWith ( 'did-cache' ) ;
172- expect ( newResolver . getCache ( ) ) . toEqual ( cachedData ) ;
173- } ) ;
270+ const consoleSpy = jest . spyOn ( console , 'warn' ) . mockImplementation ( ) ;
174271
175- it ( 'should handle empty cache from storage' , async ( ) => {
176- mockStorageService . getItem . mockResolvedValue ( null ) ;
177-
178- const newResolver = new CachedDIDResolver ( mockRouter ) ;
179- await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
272+ const result = await resolver . resolve ( testDid ) ;
273+
274+ expect ( result ) . toEqual ( staleResult ) ;
275+
276+ // Wait for background refresh to complete
277+ await new Promise ( resolve => setTimeout ( resolve , 50 ) ) ;
278+
279+ expect ( consoleSpy ) . toHaveBeenCalledWith (
280+ 'Background refresh failed for:' ,
281+ testDid ,
282+ expect . any ( Error )
283+ ) ;
180284
181- expect ( newResolver . getCache ( ) ) . toEqual ( { } ) ;
285+ consoleSpy . mockRestore ( ) ;
182286 } ) ;
183287 } ) ;
184288} ) ;
0 commit comments