fix: fix interceptors
This commit is contained in:
parent
eacbbb2815
commit
26a06fdbf0
@ -1,8 +1,10 @@
|
||||
import { TestBed } from '@/testing';
|
||||
import { provideHttpClientTesting } from '@/testing/http';
|
||||
import {
|
||||
HTTP_CLIENT_TEST_CONTROLLER,
|
||||
provideHttpClientTesting,
|
||||
} from '@/testing/http';
|
||||
import { HttpHeaders } from '@ngify/http';
|
||||
import { HttpTestingController } from '@ngify/http/testing';
|
||||
import { provideHttpClient, withInterceptorsFromDi } from 'oidc-client-rx';
|
||||
import type { HttpTestingController } from '@ngify/http/testing';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DataService } from './data.service';
|
||||
import { HttpBaseService } from './http-base.service';
|
||||
@ -14,15 +16,10 @@ describe('Data Service', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
DataService,
|
||||
HttpBaseService,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
providers: [DataService, HttpBaseService, provideHttpClientTesting()],
|
||||
});
|
||||
dataService = TestBed.inject(DataService);
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
httpMock = TestBed.inject(HTTP_CLIENT_TEST_CONTROLLER);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
|
@ -1,17 +1,121 @@
|
||||
import type { HttpFeature, HttpInterceptor } from '@ngify/http';
|
||||
import { InjectionToken } from 'injection-js';
|
||||
import {
|
||||
type HttpBackend,
|
||||
HttpClient,
|
||||
type HttpFeature,
|
||||
HttpFeatureKind,
|
||||
type HttpInterceptor,
|
||||
type HttpInterceptorFn,
|
||||
withInterceptors,
|
||||
withLegacyInterceptors,
|
||||
} from '@ngify/http';
|
||||
import { InjectionToken, Optional, type Provider } from 'injection-js';
|
||||
import type { ArrayOrNullableOne } from '../utils/types';
|
||||
export { HttpParams, HttpParamsOptions } from './params';
|
||||
|
||||
export const HTTP_INTERCEPTORS = new InjectionToken<readonly HttpInterceptor[]>(
|
||||
'HTTP_INTERCEPTORS'
|
||||
export const HTTP_FEATURES = new InjectionToken<HttpFeature[]>('HTTP_FEATURES');
|
||||
|
||||
export const HTTP_INTERCEPTOR_FNS = new InjectionToken<HttpInterceptorFn[]>(
|
||||
'HTTP_INTERCEPTOR_FNS'
|
||||
);
|
||||
|
||||
export function provideHttpClient() {
|
||||
// todo
|
||||
throw new Error('todo!');
|
||||
}
|
||||
export const HTTP_LEGACY_INTERCEPTORS = new InjectionToken<HttpInterceptor[]>(
|
||||
'HTTP_LEGACY_INTERCEPTORS'
|
||||
);
|
||||
|
||||
export function withInterceptorsFromDi(): HttpFeature {
|
||||
// todo
|
||||
throw new Error('todo!');
|
||||
export const HTTP_BACKEND = new InjectionToken<HttpBackend>('HTTP_BACKEND');
|
||||
|
||||
export const HTTP_XSRF_PROTECTION = new InjectionToken<HttpInterceptorFn>(
|
||||
'HTTP_XSRF_PROTECTION'
|
||||
);
|
||||
|
||||
export function provideHttpClient(features: HttpFeature[] = []): Provider[] {
|
||||
return [
|
||||
{
|
||||
provide: HTTP_INTERCEPTOR_FNS,
|
||||
multi: true,
|
||||
useValue: [],
|
||||
},
|
||||
{
|
||||
provide: HTTP_LEGACY_INTERCEPTORS,
|
||||
multi: true,
|
||||
useValue: [],
|
||||
},
|
||||
{
|
||||
provide: HTTP_FEATURES,
|
||||
useFactory: (
|
||||
interceptors: ArrayOrNullableOne<HttpInterceptorFn>[]
|
||||
): HttpFeature[] => {
|
||||
const normalizedInterceptors = [interceptors]
|
||||
.flat(Number.MAX_SAFE_INTEGER)
|
||||
.filter(Boolean) as HttpInterceptorFn[];
|
||||
return normalizedInterceptors.length
|
||||
? [withInterceptors(normalizedInterceptors)]
|
||||
: [];
|
||||
},
|
||||
multi: true,
|
||||
deps: [HTTP_INTERCEPTOR_FNS],
|
||||
},
|
||||
{
|
||||
provide: HTTP_FEATURES,
|
||||
useFactory: (
|
||||
interceptors: ArrayOrNullableOne<HttpInterceptor>[]
|
||||
): HttpFeature[] => {
|
||||
const normalizedInterceptors = [interceptors]
|
||||
.flat(Number.MAX_SAFE_INTEGER)
|
||||
.filter(Boolean) as HttpInterceptor[];
|
||||
return normalizedInterceptors.length
|
||||
? [withLegacyInterceptors(normalizedInterceptors)]
|
||||
: [];
|
||||
},
|
||||
multi: true,
|
||||
deps: [HTTP_LEGACY_INTERCEPTORS],
|
||||
},
|
||||
{
|
||||
provide: HTTP_FEATURES,
|
||||
useFactory: (backend: HttpBackend | null | undefined): HttpFeature[] => {
|
||||
return backend
|
||||
? [
|
||||
{
|
||||
kind: HttpFeatureKind.Backend,
|
||||
value: backend,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
},
|
||||
multi: true,
|
||||
deps: [[new Optional(), HTTP_BACKEND]],
|
||||
},
|
||||
{
|
||||
provide: HTTP_FEATURES,
|
||||
useFactory: (
|
||||
interceptor: HttpInterceptorFn | null | undefined
|
||||
): HttpFeature[] => {
|
||||
return interceptor
|
||||
? [
|
||||
{
|
||||
kind: HttpFeatureKind.XsrfProtection,
|
||||
value: interceptor,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
},
|
||||
multi: true,
|
||||
deps: [[new Optional(), HTTP_XSRF_PROTECTION]],
|
||||
},
|
||||
{
|
||||
provide: HTTP_FEATURES,
|
||||
useValue: features,
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
provide: HttpClient,
|
||||
useFactory: (features: ArrayOrNullableOne<HttpFeature>[]) => {
|
||||
const normalizedFeatures = [features]
|
||||
.flat(Number.MAX_SAFE_INTEGER)
|
||||
.filter(Boolean) as HttpFeature[];
|
||||
return new HttpClient(...normalizedFeatures);
|
||||
},
|
||||
deps: [HTTP_FEATURES],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { TestBed } from '@/testing';
|
||||
import {
|
||||
HTTP_INTERCEPTORS,
|
||||
HttpClient,
|
||||
provideHttpClient,
|
||||
withInterceptors,
|
||||
withInterceptorsFromDi,
|
||||
} from '@ngify/http';
|
||||
import {
|
||||
HttpTestingController,
|
||||
HTTP_CLIENT_TEST_CONTROLLER,
|
||||
provideHttpClientTesting,
|
||||
} from '@ngify/http/testing';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
} from '@/testing';
|
||||
import { HttpClient } from '@ngify/http';
|
||||
import type { HttpTestingController } from '@ngify/http/testing';
|
||||
import { HTTP_INTERCEPTOR_FNS, HTTP_LEGACY_INTERCEPTORS } from 'oidc-client-rx';
|
||||
import { ReplaySubject, firstValueFrom, share } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||
import { ConfigurationService } from '../config/config.service';
|
||||
@ -33,20 +29,19 @@ describe('AuthHttpInterceptor', () => {
|
||||
providers: [
|
||||
ClosestMatchingRouteService,
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
provide: HTTP_LEGACY_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi: true,
|
||||
},
|
||||
mockProvider(AuthStateService),
|
||||
mockProvider(LoggerService),
|
||||
mockProvider(ConfigurationService),
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
});
|
||||
|
||||
httpClient = TestBed.inject(HttpClient);
|
||||
httpTestingController = TestBed.inject(HttpTestingController);
|
||||
httpTestingController = TestBed.inject(HTTP_CLIENT_TEST_CONTROLLER);
|
||||
configurationService = TestBed.inject(ConfigurationService);
|
||||
authStateService = TestBed.inject(AuthStateService);
|
||||
closestMatchingRouteService = TestBed.inject(ClosestMatchingRouteService);
|
||||
@ -64,8 +59,12 @@ describe('AuthHttpInterceptor', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: HTTP_INTERCEPTOR_FNS,
|
||||
useFactory: authInterceptor,
|
||||
multi: true,
|
||||
},
|
||||
ClosestMatchingRouteService,
|
||||
provideHttpClient(withInterceptors([authInterceptor()])),
|
||||
provideHttpClientTesting(),
|
||||
mockProvider(AuthStateService),
|
||||
mockProvider(LoggerService),
|
||||
@ -74,7 +73,7 @@ describe('AuthHttpInterceptor', () => {
|
||||
});
|
||||
|
||||
httpClient = TestBed.inject(HttpClient);
|
||||
httpTestingController = TestBed.inject(HttpTestingController);
|
||||
httpTestingController = TestBed.inject(HTTP_CLIENT_TEST_CONTROLLER);
|
||||
configurationService = TestBed.inject(ConfigurationService);
|
||||
authStateService = TestBed.inject(AuthStateService);
|
||||
closestMatchingRouteService = TestBed.inject(ClosestMatchingRouteService);
|
||||
@ -106,14 +105,27 @@ describe('AuthHttpInterceptor', () => {
|
||||
true
|
||||
);
|
||||
|
||||
const response = await firstValueFrom(httpClient.get(actionUrl));
|
||||
expect(response).toBeTruthy();
|
||||
const test$ = httpClient.get(actionUrl).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const httpRequest = httpTestingController.expectOne(actionUrl);
|
||||
|
||||
httpRequest.flush('something');
|
||||
|
||||
expect(httpRequest.request.headers.has('Authorization')).toEqual(true);
|
||||
|
||||
httpRequest.flush('something');
|
||||
const response = await firstValueFrom(test$);
|
||||
|
||||
expect(response).toBeTruthy();
|
||||
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
@ -132,14 +144,26 @@ describe('AuthHttpInterceptor', () => {
|
||||
true
|
||||
);
|
||||
|
||||
const response = await firstValueFrom(httpClient.get(actionUrl));
|
||||
expect(response).toBeTruthy();
|
||||
const test$ = httpClient.get(actionUrl).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const httpRequest = httpTestingController.expectOne(actionUrl);
|
||||
|
||||
expect(httpRequest.request.headers.has('Authorization')).toEqual(false);
|
||||
|
||||
httpRequest.flush('something');
|
||||
|
||||
const response = await firstValueFrom(test$);
|
||||
expect(response).toBeTruthy();
|
||||
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
@ -159,15 +183,27 @@ describe('AuthHttpInterceptor', () => {
|
||||
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
|
||||
'thisIsAToken'
|
||||
);
|
||||
const test$ = httpClient.get(actionUrl).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
const response = await firstValueFrom(httpClient.get(actionUrl));
|
||||
expect(response).toBeTruthy();
|
||||
test$.subscribe();
|
||||
|
||||
const httpRequest = httpTestingController.expectOne(actionUrl);
|
||||
|
||||
expect(httpRequest.request.headers.has('Authorization')).toEqual(false);
|
||||
|
||||
httpRequest.flush('something');
|
||||
|
||||
const response = await firstValueFrom(test$);
|
||||
|
||||
expect(response).toBeTruthy();
|
||||
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
@ -185,14 +221,26 @@ describe('AuthHttpInterceptor', () => {
|
||||
true
|
||||
);
|
||||
|
||||
const response = await firstValueFrom(httpClient.get(actionUrl));
|
||||
expect(response).toBeTruthy();
|
||||
const test$ = httpClient.get(actionUrl).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const httpRequest = httpTestingController.expectOne(actionUrl);
|
||||
|
||||
expect(httpRequest.request.headers.has('Authorization')).toEqual(false);
|
||||
|
||||
httpRequest.flush('something');
|
||||
|
||||
const response = await firstValueFrom(test$);
|
||||
expect(response).toBeTruthy();
|
||||
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
@ -211,14 +259,27 @@ describe('AuthHttpInterceptor', () => {
|
||||
);
|
||||
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('');
|
||||
|
||||
const response = await firstValueFrom(httpClient.get(actionUrl));
|
||||
expect(response).toBeTruthy();
|
||||
const test$ = httpClient.get(actionUrl).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const httpRequest = httpTestingController.expectOne(actionUrl);
|
||||
|
||||
expect(httpRequest.request.headers.has('Authorization')).toEqual(false);
|
||||
|
||||
httpRequest.flush('something');
|
||||
|
||||
const response = await firstValueFrom(test$);
|
||||
|
||||
expect(response).toBeTruthy();
|
||||
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
@ -229,14 +290,26 @@ describe('AuthHttpInterceptor', () => {
|
||||
false
|
||||
);
|
||||
|
||||
const response = await firstValueFrom(httpClient.get(actionUrl));
|
||||
expect(response).toBeTruthy();
|
||||
const test$ = httpClient.get(actionUrl).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const httpRequest = httpTestingController.expectOne(actionUrl);
|
||||
|
||||
expect(httpRequest.request.headers.has('Authorization')).toEqual(false);
|
||||
|
||||
httpRequest.flush('something');
|
||||
|
||||
const response = await firstValueFrom(test$);
|
||||
expect(response).toBeTruthy();
|
||||
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
@ -260,14 +333,25 @@ describe('AuthHttpInterceptor', () => {
|
||||
matchingConfig: null,
|
||||
});
|
||||
|
||||
const response = await firstValueFrom(httpClient.get(actionUrl));
|
||||
expect(response).toBeTruthy();
|
||||
const test$ = httpClient.get(actionUrl).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const httpRequest = httpTestingController.expectOne(actionUrl);
|
||||
|
||||
expect(httpRequest.request.headers.has('Authorization')).toEqual(false);
|
||||
|
||||
httpRequest.flush('something');
|
||||
|
||||
const response = await firstValueFrom(test$);
|
||||
expect(response).toBeTruthy();
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
@ -286,11 +370,25 @@ describe('AuthHttpInterceptor', () => {
|
||||
true
|
||||
);
|
||||
|
||||
let response = await firstValueFrom(httpClient.get(actionUrl));
|
||||
expect(response).toBeTruthy();
|
||||
const test$ = httpClient.get(actionUrl).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
const test2$ = httpClient.get(actionUrl2).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
response = await firstValueFrom(httpClient.get(actionUrl2));
|
||||
expect(response).toBeTruthy();
|
||||
test$.subscribe();
|
||||
test2$.subscribe();
|
||||
|
||||
const httpRequest = httpTestingController.expectOne(actionUrl);
|
||||
|
||||
@ -302,6 +400,14 @@ describe('AuthHttpInterceptor', () => {
|
||||
|
||||
httpRequest.flush('something');
|
||||
httpRequest2.flush('something');
|
||||
|
||||
const [response, response2] = await Promise.all([
|
||||
firstValueFrom(test$),
|
||||
firstValueFrom(test2$),
|
||||
]);
|
||||
expect(response).toBeTruthy();
|
||||
expect(response2).toBeTruthy();
|
||||
|
||||
httpTestingController.verify();
|
||||
});
|
||||
}
|
||||
|
@ -40,13 +40,14 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
}
|
||||
|
||||
export function authInterceptor(): HttpInterceptorFn {
|
||||
const deps = {
|
||||
configurationService: inject(ConfigurationService),
|
||||
authStateService: inject(AuthStateService),
|
||||
closestMatchingRouteService: inject(ClosestMatchingRouteService),
|
||||
loggerService: inject(LoggerService),
|
||||
};
|
||||
return (req, next) => {
|
||||
return interceptRequest(req, next, {
|
||||
configurationService: inject(ConfigurationService),
|
||||
authStateService: inject(AuthStateService),
|
||||
closestMatchingRouteService: inject(ClosestMatchingRouteService),
|
||||
loggerService: inject(LoggerService),
|
||||
});
|
||||
return interceptRequest(req, next, deps);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,21 @@
|
||||
import { HttpFeatureKind } from '@ngify/http';
|
||||
import { HttpClientTestingBackend } from '@ngify/http/testing';
|
||||
import { InjectionToken, type Provider } from 'injection-js';
|
||||
import { HTTP_BACKEND, provideHttpClient } from 'oidc-client-rx';
|
||||
|
||||
export function provideHttpClientTesting() {
|
||||
return {
|
||||
provide: HttpFeatureKind.Backend,
|
||||
useClass: HttpClientTestingBackend,
|
||||
};
|
||||
export const HTTP_CLIENT_TEST_CONTROLLER =
|
||||
new InjectionToken<HttpClientTestingBackend>('HTTP_CLIENT_TEST_CONTROLLER');
|
||||
|
||||
export function provideHttpClientTesting(): Provider[] {
|
||||
const backend = new HttpClientTestingBackend();
|
||||
return [
|
||||
{
|
||||
provide: HTTP_CLIENT_TEST_CONTROLLER,
|
||||
useValue: backend,
|
||||
},
|
||||
{
|
||||
provide: HTTP_BACKEND,
|
||||
useValue: backend,
|
||||
},
|
||||
...provideHttpClient(),
|
||||
];
|
||||
}
|
||||
|
@ -6,4 +6,4 @@ export {
|
||||
} from './spy';
|
||||
export { createRetriableStream } from './create-retriable-stream.helper';
|
||||
export { MockRouter, mockRouterProvider } from './router';
|
||||
export { provideHttpClientTesting } from './http';
|
||||
export { provideHttpClientTesting, HTTP_CLIENT_TEST_CONTROLLER } from './http';
|
||||
|
3
src/utils/types/index.ts
Normal file
3
src/utils/types/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type ArrayOrOne<T> = T[] | T;
|
||||
|
||||
export type ArrayOrNullableOne<T> = T[] | T | null | undefined;
|
Loading…
Reference in New Issue
Block a user