@@ -268,4 +268,231 @@ describe('CacheDecorator', () => {
268268 Assert . strictEqual ( await strategy . getItem < string [ ] > ( 'getUsersPromise' ) , data ) ;
269269 } ) ;
270270 } ) ;
271+
272+ describe ( 'DISABLE_CACHE_DECORATOR environment variable' , ( ) => {
273+ afterEach ( ( ) => {
274+ delete process . env . DISABLE_CACHE_DECORATOR ;
275+ } ) ;
276+
277+ it ( 'Should skip caching when DISABLE_CACHE_DECORATOR is set' , async ( ) => {
278+ process . env . DISABLE_CACHE_DECORATOR = 'true' ;
279+
280+ const disableStorage = new MemoryStorage ( ) ;
281+ const disableStrategy = new ExpirationStrategy ( disableStorage ) ;
282+
283+ class TestClassDisabled {
284+ callCount = 0 ;
285+
286+ @Cache ( disableStrategy , { ttl : 1000 } )
287+ public getUsers ( ) : string [ ] {
288+ this . callCount ++ ;
289+ return data ;
290+ }
291+ }
292+
293+ const myClass = new TestClassDisabled ( ) ;
294+
295+ await myClass . getUsers ( ) ;
296+ await myClass . getUsers ( ) ;
297+ await myClass . getUsers ( ) ;
298+
299+ // Method should be called every time when cache is disabled
300+ Assert . strictEqual ( myClass . callCount , 3 ) ;
301+ } ) ;
302+
303+ it ( 'Should work normally when DISABLE_CACHE_DECORATOR is not set' , async ( ) => {
304+ delete process . env . DISABLE_CACHE_DECORATOR ;
305+
306+ const normalStorage = new MemoryStorage ( ) ;
307+ const normalStrategy = new ExpirationStrategy ( normalStorage ) ;
308+
309+ class TestClassNormal {
310+ callCount = 0 ;
311+
312+ @Cache ( normalStrategy , { ttl : 1000 } )
313+ public getUsers ( ) : string [ ] {
314+ this . callCount ++ ;
315+ return data ;
316+ }
317+ }
318+
319+ const myClass = new TestClassNormal ( ) ;
320+
321+ await myClass . getUsers ( ) ;
322+ await myClass . getUsers ( ) ;
323+ await myClass . getUsers ( ) ;
324+
325+ // Method should be called only once when caching is enabled
326+ Assert . strictEqual ( myClass . callCount , 1 ) ;
327+ } ) ;
328+ } ) ;
329+
330+ describe ( 'Cache error handling' , ( ) => {
331+ it ( 'Should handle cache read errors gracefully' , async ( ) => {
332+ const failingStorage = {
333+ getItem : ( ) => {
334+ throw new Error ( 'Read error' ) ;
335+ } ,
336+ setItem : async ( ) => { } ,
337+ clear : async ( ) => { }
338+ } ;
339+ const failStrategy = new ExpirationStrategy ( failingStorage as unknown as MemoryStorage ) ;
340+
341+ class TestClassReadFail {
342+ callCount = 0 ;
343+
344+ @Cache ( failStrategy , { ttl : 1000 } )
345+ public getUsers ( ) : string [ ] {
346+ this . callCount ++ ;
347+ return data ;
348+ }
349+ }
350+
351+ const myClass = new TestClassReadFail ( ) ;
352+
353+ // Should not throw, just log warning and continue
354+ const result = await myClass . getUsers ( ) ;
355+ Assert . deepStrictEqual ( result , data ) ;
356+ } ) ;
357+
358+ it ( 'Should handle cache write errors gracefully' , async ( ) => {
359+ const failingStorage = {
360+ getItem : async ( ) => undefined ,
361+ setItem : async ( ) => {
362+ throw new Error ( 'Write error' ) ;
363+ } ,
364+ clear : async ( ) => { }
365+ } ;
366+ const failStrategy = new ExpirationStrategy ( failingStorage as unknown as MemoryStorage ) ;
367+
368+ class TestClassWriteFail {
369+ callCount = 0 ;
370+
371+ @Cache ( failStrategy , { ttl : 1000 } )
372+ public getUsers ( ) : string [ ] {
373+ this . callCount ++ ;
374+ return data ;
375+ }
376+ }
377+
378+ const myClass = new TestClassWriteFail ( ) ;
379+
380+ // Should not throw, just log warning and continue
381+ const result = await myClass . getUsers ( ) ;
382+ Assert . deepStrictEqual ( result , data ) ;
383+ } ) ;
384+ } ) ;
385+
386+ describe ( 'Different argument types' , ( ) => {
387+ const argStorage = new MemoryStorage ( ) ;
388+ const argStrategy = new ExpirationStrategy ( argStorage ) ;
389+
390+ beforeEach ( async ( ) => {
391+ await argStrategy . clear ( ) ;
392+ } ) ;
393+
394+ class TestClassArgs {
395+ callCount = 0 ;
396+
397+ @Cache ( argStrategy , { ttl : 1000 } )
398+ public getWithArgs ( ...args : unknown [ ] ) : unknown [ ] {
399+ this . callCount ++ ;
400+ return args ;
401+ }
402+ }
403+
404+ it ( 'Should cache with object arguments' , async ( ) => {
405+ const myClass = new TestClassArgs ( ) ;
406+ const arg = { id : 1 , name : 'test' } ;
407+
408+ await myClass . getWithArgs ( arg ) ;
409+ await myClass . getWithArgs ( arg ) ;
410+
411+ Assert . strictEqual ( myClass . callCount , 1 ) ;
412+ } ) ;
413+
414+ it ( 'Should cache with array arguments' , async ( ) => {
415+ const myClass = new TestClassArgs ( ) ;
416+ const arg = [ 1 , 2 , 3 ] ;
417+
418+ await myClass . getWithArgs ( arg ) ;
419+ await myClass . getWithArgs ( arg ) ;
420+
421+ Assert . strictEqual ( myClass . callCount , 1 ) ;
422+ } ) ;
423+
424+ it ( 'Should differentiate between different arguments' , async ( ) => {
425+ const myClass = new TestClassArgs ( ) ;
426+
427+ await myClass . getWithArgs ( 1 ) ;
428+ await myClass . getWithArgs ( 2 ) ;
429+ await myClass . getWithArgs ( 3 ) ;
430+
431+ Assert . strictEqual ( myClass . callCount , 3 ) ;
432+ } ) ;
433+
434+ it ( 'Should cache with multiple arguments' , async ( ) => {
435+ const myClass = new TestClassArgs ( ) ;
436+
437+ await myClass . getWithArgs ( 'a' , 1 , true ) ;
438+ await myClass . getWithArgs ( 'a' , 1 , true ) ;
439+
440+ Assert . strictEqual ( myClass . callCount , 1 ) ;
441+ } ) ;
442+ } ) ;
443+
444+ describe ( 'Error propagation' , ( ) => {
445+ it ( 'Should propagate errors from decorated method' , async ( ) => {
446+ const errorStorage = new MemoryStorage ( ) ;
447+ const errorStrategy = new ExpirationStrategy ( errorStorage ) ;
448+
449+ class TestClassError {
450+ @Cache ( errorStrategy , { ttl : 1000 } )
451+ public async throwingMethod ( ) : Promise < string > {
452+ throw new Error ( 'Test error' ) ;
453+ }
454+ }
455+
456+ const myClass = new TestClassError ( ) ;
457+
458+ await Assert . rejects (
459+ async ( ) => {
460+ await myClass . throwingMethod ( ) ;
461+ } ,
462+ { message : 'Test error' }
463+ ) ;
464+ } ) ;
465+
466+ it ( 'Should not cache failed method calls' , async ( ) => {
467+ const errorStorage = new MemoryStorage ( ) ;
468+ const errorStrategy = new ExpirationStrategy ( errorStorage ) ;
469+
470+ class TestClassErrorCount {
471+ callCount = 0 ;
472+
473+ @Cache ( errorStrategy , { ttl : 1000 } )
474+ public async throwingMethod ( ) : Promise < string > {
475+ this . callCount ++ ;
476+ throw new Error ( 'Test error' ) ;
477+ }
478+ }
479+
480+ const myClass = new TestClassErrorCount ( ) ;
481+
482+ try {
483+ await myClass . throwingMethod ( ) ;
484+ } catch {
485+ // Expected
486+ }
487+
488+ try {
489+ await myClass . throwingMethod ( ) ;
490+ } catch {
491+ // Expected
492+ }
493+
494+ // Each call should execute since errors are not cached
495+ Assert . strictEqual ( myClass . callCount , 2 ) ;
496+ } ) ;
497+ } ) ;
271498} ) ;
0 commit comments