Created
July 10, 2023 13:23
Revisions
-
Erik Cunha created this gist
Jul 10, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,108 @@ import { HttpEvent, HttpHandler, HttpHeaders, HttpRequest, } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController, } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { Observable } from 'rxjs'; import { CacheInterceptor } from './cache.interceptor'; describe('CacheInterceptor', () => { let interceptor: CacheInterceptor; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [CacheInterceptor], }); interceptor = TestBed.inject(CacheInterceptor); httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => { httpMock.verify(); }); function createRequest(shouldCache: boolean): HttpRequest<any> { const headers = shouldCache ? new HttpHeaders() : new HttpHeaders({ 'Cache-Expiration': 'no-cache' }); return new HttpRequest<any>('GET', 'https://test.com/api/test', { headers, }); } it('should be created', () => { expect(interceptor).toBeTruthy(); }); it('should not cache the response if Cache-Expiration header is set to no-cache', () => { const request = createRequest(false); const next: HttpHandler = { handle: jest.fn().mockReturnValue(new Observable<HttpEvent<any>>()), }; interceptor.intercept(request, next); expect(interceptor.getCachedResponse(request)).toBeNull(); }); it('should cache the response if Cache-Expiration header is not set to no-cache', (done) => { const request = createRequest(true); const next: HttpHandler = { handle: jest.fn().mockReturnValue( new Observable<HttpEvent<any>>((subscriber) => { subscriber.next({ type: 0 }); subscriber.complete(); }) ), }; interceptor.intercept(request, next).subscribe(() => { expect(interceptor.getCachedResponse(request)).toBeTruthy(); done(); }); }); it('should return cached response if not expired', (done) => { const request = createRequest(true); const next: HttpHandler = { handle: jest.fn().mockReturnValue( new Observable<HttpEvent<any>>((subscriber) => { subscriber.next({ type: 0 }); subscriber.complete(); }) ), }; interceptor.intercept(request, next).subscribe(() => { jest.spyOn(Date, 'now').mockReturnValue(Date.now() + 20000); expect(interceptor.getCachedResponse(request)).toBeTruthy(); done(); }); }); it('should not return cached response if expired', (done) => { const request = createRequest(true); const next: HttpHandler = { handle: jest.fn().mockReturnValue( new Observable<HttpEvent<any>>((subscriber) => { subscriber.next({ type: 0 }); subscriber.complete(); }) ), }; interceptor.intercept(request, next).subscribe(() => { jest.spyOn(Date, 'now').mockReturnValue(Date.now() + 31000); expect(interceptor.getCachedResponse(request)).toBeNull(); done(); }); }); }); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,65 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { shareReplay } from 'rxjs/operators'; @Injectable() export class CacheInterceptor implements HttpInterceptor { private cache: Map< string, { timestamp: number; response$: Observable<HttpEvent<any>> } > = new Map(); intercept( request: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { const shouldCache = request.headers.get('Cache-Expiration') !== 'no-cache'; if (shouldCache) { const cachedResponse$ = this.getCachedResponse(request); if (cachedResponse$) { return cachedResponse$; } } const response$ = next.handle(request).pipe(shareReplay()); if (shouldCache) { this.cache.set(request.urlWithParams, { timestamp: Date.now(), response$, }); } return response$; } getCachedResponse( request: HttpRequest<unknown> ): Observable<HttpEvent<unknown>> | null { const cached = this.cache.get(request.urlWithParams); if (!cached) { return null; } console.warn('cached', cached); const cacheAge = Date.now() - cached.timestamp; const maxCacheAge = 30000; if (cacheAge > maxCacheAge) { this.cache.delete(request.urlWithParams); return null; } return cached.response$; } }