Chore: Http interceptors refactor (#11923)

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
Pierre Nédélec
2026-01-28 16:18:48 +01:00
committed by GitHub
parent 01b21377af
commit 4cbe56e3af
5 changed files with 97 additions and 86 deletions

View File

@@ -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 { TestBed } from '@angular/core/testing'
import { of } from 'rxjs'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { ApiVersionInterceptor } from './api-version.interceptor' import { withApiVersionInterceptor } from './api-version.interceptor'
describe('ApiVersionInterceptor', () => { describe('ApiVersionInterceptor', () => {
let interceptor: ApiVersionInterceptor let httpClient: HttpClient
let httpMock: HttpTestingController
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ 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', () => { it('should add api version to headers', () => {
interceptor.intercept(new HttpRequest('GET', 'https://example.com'), { httpClient.get('https://example.com').subscribe()
handle: (request) => { const request = httpMock.expectOne('https://example.com')
const header = request.headers['lazyUpdate'][0] const header = request.request.headers['lazyUpdate'][0]
expect(header.name).toEqual('Accept')
expect(header.value).toEqual( expect(header.name).toEqual('Accept')
`application/json; version=${environment.apiVersion}` expect(header.value).toEqual(
) `application/json; version=${environment.apiVersion}`
return of({} as HttpEvent<any>) )
}, request.flush({})
})
}) })
}) })

View File

@@ -1,27 +1,20 @@
import { import {
HttpEvent, HttpEvent,
HttpHandler, HttpHandlerFn,
HttpInterceptor, HttpInterceptorFn,
HttpRequest, HttpRequest,
} from '@angular/common/http' } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
@Injectable() export const withApiVersionInterceptor: HttpInterceptorFn = (
export class ApiVersionInterceptor implements HttpInterceptor { request: HttpRequest<unknown>,
constructor() {} next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {
intercept( request = request.clone({
request: HttpRequest<unknown>, setHeaders: {
next: HttpHandler Accept: `application/json; version=${environment.apiVersion}`,
): Observable<HttpEvent<unknown>> { },
request = request.clone({ })
setHeaders: { return next(request)
Accept: `application/json; version=${environment.apiVersion}`,
},
})
return next.handle(request)
}
} }

View File

@@ -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 { TestBed } from '@angular/core/testing'
import { Meta } from '@angular/platform-browser' import { Meta } from '@angular/platform-browser'
import { CookieService } from 'ngx-cookie-service' import { CookieService } from 'ngx-cookie-service'
import { of } from 'rxjs' import { withCsrfInterceptor } from './csrf.interceptor'
import { CsrfInterceptor } from './csrf.interceptor'
describe('CsrfInterceptor', () => { describe('CsrfInterceptor', () => {
let interceptor: CsrfInterceptor
let meta: Meta let meta: Meta
let cookieService: CookieService let cookieService: CookieService
let httpClient: HttpClient
let httpMock: HttpTestingController
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [CsrfInterceptor, Meta, CookieService], providers: [
Meta,
CookieService,
provideHttpClient(withInterceptors([withCsrfInterceptor])),
provideHttpClientTesting(),
],
}) })
meta = TestBed.inject(Meta) meta = TestBed.inject(Meta)
cookieService = TestBed.inject(CookieService) cookieService = TestBed.inject(CookieService)
interceptor = TestBed.inject(CsrfInterceptor) httpClient = TestBed.inject(HttpClient)
httpMock = TestBed.inject(HttpTestingController)
}) })
it('should get csrf token', () => { it('should get csrf token', () => {
meta.addTag({ name: 'cookie_prefix', content: 'ngx-' }, true) meta.addTag({ name: 'cookie_prefix', content: 'ngx-' }, true)
const cookieServiceSpy = jest.spyOn(cookieService, 'get') const cookieServiceSpy = jest.spyOn(cookieService, 'get')
cookieServiceSpy.mockReturnValue('csrftoken') cookieServiceSpy.mockReturnValue('csrftoken')
interceptor.intercept(new HttpRequest('GET', 'https://example.com'), {
handle: (request) => { httpClient.get('https://example.com').subscribe()
expect(request.headers['lazyUpdate'][0]['name']).toEqual('X-CSRFToken') const request = httpMock.expectOne('https://example.com')
return of({} as HttpEvent<any>)
}, expect(request.request.headers['lazyUpdate'][0]['name']).toEqual(
}) 'X-CSRFToken'
)
expect(cookieServiceSpy).toHaveBeenCalled() expect(cookieServiceSpy).toHaveBeenCalled()
request.flush({})
}) })
}) })

View File

@@ -1,36 +1,32 @@
import { import {
HttpEvent, HttpEvent,
HttpHandler, HttpHandlerFn,
HttpInterceptor, HttpInterceptorFn,
HttpRequest, HttpRequest,
} from '@angular/common/http' } from '@angular/common/http'
import { inject, Injectable } from '@angular/core' import { inject } from '@angular/core'
import { Meta } from '@angular/platform-browser' import { Meta } from '@angular/platform-browser'
import { CookieService } from 'ngx-cookie-service' import { CookieService } from 'ngx-cookie-service'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
@Injectable() export const withCsrfInterceptor: HttpInterceptorFn = (
export class CsrfInterceptor implements HttpInterceptor { request: HttpRequest<unknown>,
private cookieService: CookieService = inject(CookieService) next: HttpHandlerFn
private meta: Meta = inject(Meta) ): Observable<HttpEvent<unknown>> => {
const cookieService: CookieService = inject(CookieService)
const meta: Meta = inject(Meta)
intercept( let prefix = ''
request: HttpRequest<unknown>, if (meta.getTag('name=cookie_prefix')) {
next: HttpHandler prefix = meta.getTag('name=cookie_prefix').content
): Observable<HttpEvent<unknown>> {
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 csrfToken = cookieService.get(`${prefix}csrftoken`)
if (csrfToken) {
request = request.clone({
setHeaders: {
'X-CSRFToken': csrfToken,
},
})
}
return next(request)
} }

View File

@@ -8,9 +8,9 @@ import {
import { DragDropModule } from '@angular/cdk/drag-drop' import { DragDropModule } from '@angular/cdk/drag-drop'
import { DatePipe, registerLocaleData } from '@angular/common' import { DatePipe, registerLocaleData } from '@angular/common'
import { import {
HTTP_INTERCEPTORS,
provideHttpClient, provideHttpClient,
withFetch, withFetch,
withInterceptors,
withInterceptorsFromDi, withInterceptorsFromDi,
} from '@angular/common/http' } from '@angular/common/http'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' 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 { DirtyDocGuard } from './app/guards/dirty-doc.guard'
import { DirtySavedViewGuard } from './app/guards/dirty-saved-view.guard' import { DirtySavedViewGuard } from './app/guards/dirty-saved-view.guard'
import { PermissionsGuard } from './app/guards/permissions.guard' import { PermissionsGuard } from './app/guards/permissions.guard'
import { ApiVersionInterceptor } from './app/interceptors/api-version.interceptor' import { withApiVersionInterceptor } from './app/interceptors/api-version.interceptor'
import { CsrfInterceptor } from './app/interceptors/csrf.interceptor' import { withCsrfInterceptor } from './app/interceptors/csrf.interceptor'
import { DocumentTitlePipe } from './app/pipes/document-title.pipe' import { DocumentTitlePipe } from './app/pipes/document-title.pipe'
import { FilterPipe } from './app/pipes/filter.pipe' import { FilterPipe } from './app/pipes/filter.pipe'
import { UsernamePipe } from './app/pipes/username.pipe' import { UsernamePipe } from './app/pipes/username.pipe'
@@ -381,16 +381,6 @@ bootstrapApplication(AppComponent, {
provideAppInitializer(initializeApp), provideAppInitializer(initializeApp),
DatePipe, DatePipe,
CookieService, CookieService,
{
provide: HTTP_INTERCEPTORS,
useClass: CsrfInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: ApiVersionInterceptor,
multi: true,
},
FilterPipe, FilterPipe,
DocumentTitlePipe, DocumentTitlePipe,
{ provide: NgbDateAdapter, useClass: ISODateAdapter }, { provide: NgbDateAdapter, useClass: ISODateAdapter },
@@ -402,6 +392,10 @@ bootstrapApplication(AppComponent, {
CorrespondentNamePipe, CorrespondentNamePipe,
DocumentTypeNamePipe, DocumentTypeNamePipe,
StoragePathNamePipe, StoragePathNamePipe,
provideHttpClient(withInterceptorsFromDi(), withFetch()), provideHttpClient(
withInterceptorsFromDi(),
withInterceptors([withCsrfInterceptor, withApiVersionInterceptor]),
withFetch()
),
], ],
}).catch((err) => console.error(err)) }).catch((err) => console.error(err))