From 4cbe56e3af3aee026104251167c09a16154e805e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20N=C3=A9d=C3=A9lec?= <10513639+pierrenedelec@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:18:48 +0100 Subject: [PATCH] Chore: Http interceptors refactor (#11923) --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- .../api-version.interceptor.spec.ts | 43 ++++++++++------- .../interceptors/api-version.interceptor.ts | 31 +++++-------- .../app/interceptors/csrf.interceptor.spec.ts | 41 ++++++++++++----- .../src/app/interceptors/csrf.interceptor.ts | 46 +++++++++---------- src-ui/src/main.ts | 22 ++++----- 5 files changed, 97 insertions(+), 86 deletions(-) diff --git a/src-ui/src/app/interceptors/api-version.interceptor.spec.ts b/src-ui/src/app/interceptors/api-version.interceptor.spec.ts index c4ddd9349..154c50bc9 100644 --- a/src-ui/src/app/interceptors/api-version.interceptor.spec.ts +++ b/src-ui/src/app/interceptors/api-version.interceptor.spec.ts @@ -1,30 +1,41 @@ -import { HttpEvent, HttpRequest } from '@angular/common/http' +import { + HttpClient, + provideHttpClient, + withInterceptors, +} from '@angular/common/http' +import { + HttpTestingController, + provideHttpClientTesting, +} from '@angular/common/http/testing' import { TestBed } from '@angular/core/testing' -import { of } from 'rxjs' import { environment } from 'src/environments/environment' -import { ApiVersionInterceptor } from './api-version.interceptor' +import { withApiVersionInterceptor } from './api-version.interceptor' describe('ApiVersionInterceptor', () => { - let interceptor: ApiVersionInterceptor + let httpClient: HttpClient + let httpMock: HttpTestingController beforeEach(() => { TestBed.configureTestingModule({ - providers: [ApiVersionInterceptor], + providers: [ + provideHttpClient(withInterceptors([withApiVersionInterceptor])), + provideHttpClientTesting(), + ], }) - interceptor = TestBed.inject(ApiVersionInterceptor) + httpClient = TestBed.inject(HttpClient) + httpMock = TestBed.inject(HttpTestingController) }) it('should add api version to headers', () => { - interceptor.intercept(new HttpRequest('GET', 'https://example.com'), { - handle: (request) => { - const header = request.headers['lazyUpdate'][0] - expect(header.name).toEqual('Accept') - expect(header.value).toEqual( - `application/json; version=${environment.apiVersion}` - ) - return of({} as HttpEvent) - }, - }) + httpClient.get('https://example.com').subscribe() + const request = httpMock.expectOne('https://example.com') + const header = request.request.headers['lazyUpdate'][0] + + expect(header.name).toEqual('Accept') + expect(header.value).toEqual( + `application/json; version=${environment.apiVersion}` + ) + request.flush({}) }) }) diff --git a/src-ui/src/app/interceptors/api-version.interceptor.ts b/src-ui/src/app/interceptors/api-version.interceptor.ts index f6ec6798d..974184675 100644 --- a/src-ui/src/app/interceptors/api-version.interceptor.ts +++ b/src-ui/src/app/interceptors/api-version.interceptor.ts @@ -1,27 +1,20 @@ import { HttpEvent, - HttpHandler, - HttpInterceptor, + HttpHandlerFn, + HttpInterceptorFn, HttpRequest, } from '@angular/common/http' -import { Injectable } from '@angular/core' import { Observable } from 'rxjs' import { environment } from 'src/environments/environment' -@Injectable() -export class ApiVersionInterceptor implements HttpInterceptor { - constructor() {} - - intercept( - request: HttpRequest, - next: HttpHandler - ): Observable> { - request = request.clone({ - setHeaders: { - Accept: `application/json; version=${environment.apiVersion}`, - }, - }) - - return next.handle(request) - } +export const withApiVersionInterceptor: HttpInterceptorFn = ( + request: HttpRequest, + next: HttpHandlerFn +): Observable> => { + request = request.clone({ + setHeaders: { + Accept: `application/json; version=${environment.apiVersion}`, + }, + }) + return next(request) } diff --git a/src-ui/src/app/interceptors/csrf.interceptor.spec.ts b/src-ui/src/app/interceptors/csrf.interceptor.spec.ts index fb2e1a2fa..2a6ec4175 100644 --- a/src-ui/src/app/interceptors/csrf.interceptor.spec.ts +++ b/src-ui/src/app/interceptors/csrf.interceptor.spec.ts @@ -1,35 +1,52 @@ -import { HttpEvent, HttpRequest } from '@angular/common/http' +import { + HttpClient, + provideHttpClient, + withInterceptors, +} from '@angular/common/http' +import { + HttpTestingController, + provideHttpClientTesting, +} from '@angular/common/http/testing' import { TestBed } from '@angular/core/testing' import { Meta } from '@angular/platform-browser' import { CookieService } from 'ngx-cookie-service' -import { of } from 'rxjs' -import { CsrfInterceptor } from './csrf.interceptor' +import { withCsrfInterceptor } from './csrf.interceptor' describe('CsrfInterceptor', () => { - let interceptor: CsrfInterceptor let meta: Meta let cookieService: CookieService + let httpClient: HttpClient + let httpMock: HttpTestingController beforeEach(() => { TestBed.configureTestingModule({ - providers: [CsrfInterceptor, Meta, CookieService], + providers: [ + Meta, + CookieService, + provideHttpClient(withInterceptors([withCsrfInterceptor])), + provideHttpClientTesting(), + ], }) meta = TestBed.inject(Meta) cookieService = TestBed.inject(CookieService) - interceptor = TestBed.inject(CsrfInterceptor) + httpClient = TestBed.inject(HttpClient) + httpMock = TestBed.inject(HttpTestingController) }) it('should get csrf token', () => { meta.addTag({ name: 'cookie_prefix', content: 'ngx-' }, true) + const cookieServiceSpy = jest.spyOn(cookieService, 'get') cookieServiceSpy.mockReturnValue('csrftoken') - interceptor.intercept(new HttpRequest('GET', 'https://example.com'), { - handle: (request) => { - expect(request.headers['lazyUpdate'][0]['name']).toEqual('X-CSRFToken') - return of({} as HttpEvent) - }, - }) + + httpClient.get('https://example.com').subscribe() + const request = httpMock.expectOne('https://example.com') + + expect(request.request.headers['lazyUpdate'][0]['name']).toEqual( + 'X-CSRFToken' + ) expect(cookieServiceSpy).toHaveBeenCalled() + request.flush({}) }) }) diff --git a/src-ui/src/app/interceptors/csrf.interceptor.ts b/src-ui/src/app/interceptors/csrf.interceptor.ts index 03a2fa7b3..e7fad4481 100644 --- a/src-ui/src/app/interceptors/csrf.interceptor.ts +++ b/src-ui/src/app/interceptors/csrf.interceptor.ts @@ -1,36 +1,32 @@ import { HttpEvent, - HttpHandler, - HttpInterceptor, + HttpHandlerFn, + HttpInterceptorFn, HttpRequest, } from '@angular/common/http' -import { inject, Injectable } from '@angular/core' +import { inject } from '@angular/core' import { Meta } from '@angular/platform-browser' import { CookieService } from 'ngx-cookie-service' import { Observable } from 'rxjs' -@Injectable() -export class CsrfInterceptor implements HttpInterceptor { - private cookieService: CookieService = inject(CookieService) - private meta: Meta = inject(Meta) +export const withCsrfInterceptor: HttpInterceptorFn = ( + request: HttpRequest, + next: HttpHandlerFn +): Observable> => { + const cookieService: CookieService = inject(CookieService) + const meta: Meta = inject(Meta) - intercept( - request: HttpRequest, - next: HttpHandler - ): Observable> { - let prefix = '' - if (this.meta.getTag('name=cookie_prefix')) { - prefix = this.meta.getTag('name=cookie_prefix').content - } - let csrfToken = this.cookieService.get(`${prefix}csrftoken`) - if (csrfToken) { - request = request.clone({ - setHeaders: { - 'X-CSRFToken': csrfToken, - }, - }) - } - - return next.handle(request) + let prefix = '' + if (meta.getTag('name=cookie_prefix')) { + prefix = meta.getTag('name=cookie_prefix').content } + let csrfToken = cookieService.get(`${prefix}csrftoken`) + if (csrfToken) { + request = request.clone({ + setHeaders: { + 'X-CSRFToken': csrfToken, + }, + }) + } + return next(request) } diff --git a/src-ui/src/main.ts b/src-ui/src/main.ts index f6f50a288..e894bf196 100644 --- a/src-ui/src/main.ts +++ b/src-ui/src/main.ts @@ -8,9 +8,9 @@ import { import { DragDropModule } from '@angular/cdk/drag-drop' import { DatePipe, registerLocaleData } from '@angular/common' import { - HTTP_INTERCEPTORS, provideHttpClient, withFetch, + withInterceptors, withInterceptorsFromDi, } from '@angular/common/http' import { FormsModule, ReactiveFormsModule } from '@angular/forms' @@ -151,8 +151,8 @@ import { AppComponent } from './app/app.component' import { DirtyDocGuard } from './app/guards/dirty-doc.guard' import { DirtySavedViewGuard } from './app/guards/dirty-saved-view.guard' import { PermissionsGuard } from './app/guards/permissions.guard' -import { ApiVersionInterceptor } from './app/interceptors/api-version.interceptor' -import { CsrfInterceptor } from './app/interceptors/csrf.interceptor' +import { withApiVersionInterceptor } from './app/interceptors/api-version.interceptor' +import { withCsrfInterceptor } from './app/interceptors/csrf.interceptor' import { DocumentTitlePipe } from './app/pipes/document-title.pipe' import { FilterPipe } from './app/pipes/filter.pipe' import { UsernamePipe } from './app/pipes/username.pipe' @@ -381,16 +381,6 @@ bootstrapApplication(AppComponent, { provideAppInitializer(initializeApp), DatePipe, CookieService, - { - provide: HTTP_INTERCEPTORS, - useClass: CsrfInterceptor, - multi: true, - }, - { - provide: HTTP_INTERCEPTORS, - useClass: ApiVersionInterceptor, - multi: true, - }, FilterPipe, DocumentTitlePipe, { provide: NgbDateAdapter, useClass: ISODateAdapter }, @@ -402,6 +392,10 @@ bootstrapApplication(AppComponent, { CorrespondentNamePipe, DocumentTypeNamePipe, StoragePathNamePipe, - provideHttpClient(withInterceptorsFromDi(), withFetch()), + provideHttpClient( + withInterceptorsFromDi(), + withInterceptors([withCsrfInterceptor, withApiVersionInterceptor]), + withFetch() + ), ], }).catch((err) => console.error(err))