oidc-client-rx/src/flows/callback-handling/refresh-token-callback-handler.service.spec.ts
2025-02-02 00:45:46 +08:00

179 lines
6.1 KiB
TypeScript

import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { HttpErrorResponse, HttpHeaders } from '@ngify/http';
import { firstValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest';
import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { createRetriableStream } from '../../testing/create-retriable-stream.helper';
import { mockProvider } from '../../testing/mock';
import { UrlService } from '../../utils/url/url.service';
import type { CallbackContext } from '../callback-context';
import { RefreshTokenCallbackHandlerService } from './refresh-token-callback-handler.service';
describe('RefreshTokenCallbackHandlerService', () => {
let service: RefreshTokenCallbackHandlerService;
let storagePersistenceService: StoragePersistenceService;
let dataService: DataService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
RefreshTokenCallbackHandlerService,
mockProvider(UrlService),
mockProvider(LoggerService),
mockProvider(DataService),
mockProvider(StoragePersistenceService),
],
});
service = TestBed.inject(RefreshTokenCallbackHandlerService);
storagePersistenceService = TestBed.inject(StoragePersistenceService);
dataService = TestBed.inject(DataService);
});
it('should create', () => {
expect(service).toBeTruthy();
});
describe('refreshTokensRequestTokens', () => {
const HTTP_ERROR = new HttpErrorResponse({});
const CONNECTION_ERROR = new HttpErrorResponse({
error: new ProgressEvent('error'),
status: 0,
statusText: 'Unknown Error',
url: 'https://identity-server.test/openid-connect/token',
});
it('throws error if no tokenEndpoint is given', async () => {
try {
await firstValueFrom(
(service as any).refreshTokensRequestTokens({} as CallbackContext)
);
} catch (err: unknown) {
expect(err).toBeTruthy();
}
});
it('calls data service if all params are good', async () => {
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
await firstValueFrom(
service.refreshTokensRequestTokens({} as CallbackContext, {
configId: 'configId1',
})
);
expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'tokenEndpoint',
undefined,
{ configId: 'configId1' },
expect.any(HttpHeaders)
);
const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded'
);
});
it('calls data service with correct headers if all params are good', async () => {
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
await firstValueFrom(
service.refreshTokensRequestTokens({} as CallbackContext, {
configId: 'configId1',
})
);
const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded'
);
});
it('returns error in case of http error', async () => {
vi.spyOn(dataService, 'post').mockReturnValue(
throwError(() => HTTP_ERROR)
);
const config = { configId: 'configId1', authority: 'authority' };
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
try {
await firstValueFrom(
service.refreshTokensRequestTokens({} as CallbackContext, config)
);
} catch (err: any) {
expect(err).toBeTruthy();
}
});
it('retries request in case of no connection http error and succeeds', async () => {
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream(
throwError(() => CONNECTION_ERROR),
of({})
)
);
const config = { configId: 'configId1', authority: 'authority' };
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
try {
const res = await firstValueFrom(
service.refreshTokensRequestTokens({} as CallbackContext, config)
);
expect(res).toBeTruthy();
expect(postSpy).toHaveBeenCalledTimes(1);
} catch (err: any) {
expect(err).toBeFalsy();
}
});
it('retries request in case of no connection http error and fails because of http error afterwards', async () => {
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream(
throwError(() => CONNECTION_ERROR),
throwError(() => HTTP_ERROR)
)
);
const config = { configId: 'configId1', authority: 'authority' };
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
try {
const res = await firstValueFrom(
service.refreshTokensRequestTokens({} as CallbackContext, config)
);
expect(res).toBeFalsy();
} catch (err: any) {
expect(err).toBeTruthy();
expect(postSpy).toHaveBeenCalledTimes(1);
}
});
});
});