feat: init

This commit is contained in:
master 2025-01-30 20:02:28 +08:00
parent da0d9855da
commit 1785df25e2
125 changed files with 8601 additions and 4725 deletions

View File

@ -1,5 +1,7 @@
# Angular Lib for OpenID Connect & OAuth2 # Angular Lib for OpenID Connect & OAuth2
TODO
![Build Status](https://github.com/damienbod/oidc-client-rx/actions/workflows/build.yml/badge.svg?branch=main) [![npm](https://img.shields.io/npm/v/oidc-client-rx.svg)](https://www.npmjs.com/package/oidc-client-rx) [![npm](https://img.shields.io/npm/dm/oidc-client-rx.svg)](https://www.npmjs.com/package/oidc-client-rx) [![npm](https://img.shields.io/npm/l/oidc-client-rx.svg)](https://www.npmjs.com/package/oidc-client-rx) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![Coverage Status](https://coveralls.io/repos/github/damienbod/oidc-client-rx/badge.svg?branch=main)](https://coveralls.io/github/damienbod/oidc-client-rx?branch=main) ![Build Status](https://github.com/damienbod/oidc-client-rx/actions/workflows/build.yml/badge.svg?branch=main) [![npm](https://img.shields.io/npm/v/oidc-client-rx.svg)](https://www.npmjs.com/package/oidc-client-rx) [![npm](https://img.shields.io/npm/dm/oidc-client-rx.svg)](https://www.npmjs.com/package/oidc-client-rx) [![npm](https://img.shields.io/npm/l/oidc-client-rx.svg)](https://www.npmjs.com/package/oidc-client-rx) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![Coverage Status](https://coveralls.io/repos/github/damienbod/oidc-client-rx/badge.svg?branch=main)](https://coveralls.io/github/damienbod/oidc-client-rx?branch=main)
<p align="center"> <p align="center">
@ -155,7 +157,7 @@ const token = this.oidcSecurityService.getAccessToken().subscribe(...);
And then you can use it in the HttpHeaders And then you can use it in the HttpHeaders
```ts ```ts
import { HttpHeaders } from '@angular/common/http'; import { HttpHeaders } from '@ngify/http';
const token = this.oidcSecurityServices.getAccessToken().subscribe((token) => { const token = this.oidcSecurityServices.getAccessToken().subscribe((token) => {
const httpOptions = { const httpOptions = {

View File

@ -18,8 +18,14 @@
} }
}, },
"files": { "files": {
"ignore": [ "ignore": [".vscode/*.json"]
".vscode/*.json" },
] "overrides": [
} {
"include": ["src/**/*.spec.ts", "src/test.ts", "test"],
"javascript": {
"globals": ["describe", "beforeEach", "it", "expect"]
}
}
]
} }

View File

@ -1,50 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(
__dirname,
'../../coverage/oidc-client-rx'
),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'lcov' }],
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox'],
},
},
singleRun: false,
restartOnFileChange: true,
});
};

3006
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -22,9 +22,7 @@
"main": "./dist/index.cjs", "main": "./dist/index.cjs",
"module": "./dist/index.js", "module": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"files": [ "files": ["dist"],
"dist"
],
"scripts": { "scripts": {
"build": "rslib build", "build": "rslib build",
"dev": "rslib build --watch", "dev": "rslib build --watch",
@ -39,19 +37,24 @@
"dependencies": { "dependencies": {
"@ngify/http": "^2.0.4", "@ngify/http": "^2.0.4",
"injection-js": "git+https://github.com/mgechev/injection-js.git#81a10e0", "injection-js": "git+https://github.com/mgechev/injection-js.git#81a10e0",
"rxjs": ">=7.4.0" "reflect-metadata": "^0.2.2"
},
"peerDependencies": {
"rxjs": "^7.4.0||>=8.0.0"
}, },
"devDependencies": { "devDependencies": {
"@evilmartians/lefthook": "^1.0.3", "@evilmartians/lefthook": "^1.0.3",
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",
"@rslib/core": "^0.3.1", "@rslib/core": "^0.3.1",
"@types/jasmine": "^4.0.0", "@types/lodash-es": "^4.17.12",
"@types/node": "^22.10.1", "@types/node": "^22.10.1",
"@vitest/coverage-v8": "^3.0.1", "@vitest/coverage-v8": "^3.0.1",
"lodash-es": "^4.17.21",
"rfc4648": "^1.5.0", "rfc4648": "^1.5.0",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"ultracite": "^4.1.15", "ultracite": "^4.1.15",
"vitest": "^3.0.1" "vitest": "^3.0.1",
"rxjs": "^7.4.0"
}, },
"keywords": [ "keywords": [
"rxjs", "rxjs",

41
pnpm-lock.yaml generated
View File

@ -14,9 +14,9 @@ importers:
injection-js: injection-js:
specifier: git+https://github.com/mgechev/injection-js.git#81a10e0 specifier: git+https://github.com/mgechev/injection-js.git#81a10e0
version: https://codeload.github.com/mgechev/injection-js/tar.gz/81a10e0 version: https://codeload.github.com/mgechev/injection-js/tar.gz/81a10e0
rxjs: reflect-metadata:
specifier: '>=7.4.0' specifier: ^0.2.2
version: 7.8.1 version: 0.2.2
devDependencies: devDependencies:
'@evilmartians/lefthook': '@evilmartians/lefthook':
specifier: ^1.0.3 specifier: ^1.0.3
@ -27,18 +27,24 @@ importers:
'@rslib/core': '@rslib/core':
specifier: ^0.3.1 specifier: ^0.3.1
version: 0.3.1(typescript@5.7.3) version: 0.3.1(typescript@5.7.3)
'@types/jasmine': '@types/lodash-es':
specifier: ^4.0.0 specifier: ^4.17.12
version: 4.6.4 version: 4.17.12
'@types/node': '@types/node':
specifier: ^22.10.1 specifier: ^22.10.1
version: 22.10.7 version: 22.10.7
'@vitest/coverage-v8': '@vitest/coverage-v8':
specifier: ^3.0.1 specifier: ^3.0.1
version: 3.0.1(vitest@3.0.1(@types/node@22.10.7)) version: 3.0.1(vitest@3.0.1(@types/node@22.10.7))
lodash-es:
specifier: ^4.17.21
version: 4.17.21
rfc4648: rfc4648:
specifier: ^1.5.0 specifier: ^1.5.0
version: 1.5.4 version: 1.5.4
rxjs:
specifier: ^7.4.0
version: 7.8.1
typescript: typescript:
specifier: ^5.7.3 specifier: ^5.7.3
version: 5.7.3 version: 5.7.3
@ -469,8 +475,11 @@ packages:
'@types/estree@1.0.6': '@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
'@types/jasmine@4.6.4': '@types/lodash-es@4.17.12':
resolution: {integrity: sha512-qCw5sVW+ylTnrEhe5kfX4l6MgU9REXIVDa/lWEcvTOUmd+LqDYwyjovDq+Zk9blElaEHOj1URDQ/djEBVRf+pw==} resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
'@types/lodash@4.17.15':
resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==}
'@types/node@22.10.7': '@types/node@22.10.7':
resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==}
@ -677,6 +686,9 @@ packages:
jackspeak@3.4.3: jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
loupe@3.1.2: loupe@3.1.2:
resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==}
@ -748,6 +760,9 @@ packages:
resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
rfc4648@1.5.4: rfc4648@1.5.4:
resolution: {integrity: sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==} resolution: {integrity: sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==}
@ -1244,7 +1259,11 @@ snapshots:
'@types/estree@1.0.6': {} '@types/estree@1.0.6': {}
'@types/jasmine@4.6.4': {} '@types/lodash-es@4.17.12':
dependencies:
'@types/lodash': 4.17.15
'@types/lodash@4.17.15': {}
'@types/node@22.10.7': '@types/node@22.10.7':
dependencies: dependencies:
@ -1469,6 +1488,8 @@ snapshots:
optionalDependencies: optionalDependencies:
'@pkgjs/parseargs': 0.11.0 '@pkgjs/parseargs': 0.11.0
lodash-es@4.17.21: {}
loupe@3.1.2: {} loupe@3.1.2: {}
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
@ -1528,6 +1549,8 @@ snapshots:
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
reflect-metadata@0.2.2: {}
rfc4648@1.5.4: {} rfc4648@1.5.4: {}
rollup@4.30.1: rollup@4.30.1:

View File

@ -1,13 +1,15 @@
import { TestBed } from '@/testing/testbed';
import { import {
HttpHeaders, HttpHeaders,
provideHttpClient, provideHttpClient,
withInterceptorsFromDi, withInterceptorsFromDi,
} from '@angular/common/http'; } from '@ngify/http';
import { import {
HttpTestingController, HttpTestingController,
provideHttpClientTesting, provideHttpClientTesting,
} from '@angular/common/http/testing'; } from '@ngify/http/testing';
import { TestBed, waitForAsync } from '@angular/core/testing'; import { lastValueFrom } from 'rxjs';
import { vi } from 'vitest';
import { DataService } from './data.service'; import { DataService } from './data.service';
import { HttpBaseService } from './http-base.service'; import { HttpBaseService } from './http-base.service';
@ -25,9 +27,6 @@ describe('Data Service', () => {
provideHttpClientTesting(), provideHttpClientTesting(),
], ],
}); });
});
beforeEach(() => {
dataService = TestBed.inject(DataService); dataService = TestBed.inject(DataService);
httpMock = TestBed.inject(HttpTestingController); httpMock = TestBed.inject(HttpTestingController);
}); });
@ -37,7 +36,7 @@ describe('Data Service', () => {
}); });
describe('get', () => { describe('get', () => {
it('get call sets the accept header', waitForAsync(() => { it('get call sets the accept header', async () => {
const url = 'testurl'; const url = 'testurl';
dataService dataService
@ -53,9 +52,9 @@ describe('Data Service', () => {
req.flush('bodyData'); req.flush('bodyData');
httpMock.verify(); httpMock.verify();
})); });
it('get call with token the accept header and the token', waitForAsync(() => { it('get call with token the accept header and the token', async () => {
const url = 'testurl'; const url = 'testurl';
const token = 'token'; const token = 'token';
@ -68,14 +67,14 @@ describe('Data Service', () => {
expect(req.request.method).toBe('GET'); expect(req.request.method).toBe('GET');
expect(req.request.headers.get('Accept')).toBe('application/json'); expect(req.request.headers.get('Accept')).toBe('application/json');
expect(req.request.headers.get('Authorization')).toBe('Bearer ' + token); expect(req.request.headers.get('Authorization')).toBe(`Bearer ${token}`);
req.flush('bodyData'); req.flush('bodyData');
httpMock.verify(); httpMock.verify();
})); });
it('call without ngsw-bypass param by default', waitForAsync(() => { it('call without ngsw-bypass param by default', async () => {
const url = 'testurl'; const url = 'testurl';
dataService dataService
@ -92,9 +91,9 @@ describe('Data Service', () => {
req.flush('bodyData'); req.flush('bodyData');
httpMock.verify(); httpMock.verify();
})); });
it('call with ngsw-bypass param', waitForAsync(() => { it('call with ngsw-bypass param', async () => {
const url = 'testurl'; const url = 'testurl';
dataService dataService
@ -102,7 +101,7 @@ describe('Data Service', () => {
.subscribe((data: unknown) => { .subscribe((data: unknown) => {
expect(data).toBe('bodyData'); expect(data).toBe('bodyData');
}); });
const req = httpMock.expectOne(url + '?ngsw-bypass='); const req = httpMock.expectOne(`${url}?ngsw-bypass=`);
expect(req.request.method).toBe('GET'); expect(req.request.method).toBe('GET');
expect(req.request.headers.get('Accept')).toBe('application/json'); expect(req.request.headers.get('Accept')).toBe('application/json');
@ -111,11 +110,11 @@ describe('Data Service', () => {
req.flush('bodyData'); req.flush('bodyData');
httpMock.verify(); httpMock.verify();
})); });
}); });
describe('post', () => { describe('post', () => {
it('call sets the accept header when no other params given', waitForAsync(() => { it('call sets the accept header when no other params given', async () => {
const url = 'testurl'; const url = 'testurl';
dataService dataService
@ -128,18 +127,23 @@ describe('Data Service', () => {
req.flush('bodyData'); req.flush('bodyData');
httpMock.verify(); await httpMock.verify();
})); });
it('call sets custom headers ONLY (No ACCEPT header) when custom headers are given', waitForAsync(() => { it('call sets custom headers ONLY (No ACCEPT header) when custom headers are given', async () => {
const url = 'testurl'; const url = 'testurl';
let headers = new HttpHeaders(); let headers = new HttpHeaders();
headers = headers.set('X-MyHeader', 'Genesis'); headers = headers.set('X-MyHeader', 'Genesis');
dataService await lastValueFrom(
.post(url, { some: 'thing' }, { configId: 'configId1' }, headers) dataService.post(
.subscribe(); url,
{ some: 'thing' },
{ configId: 'configId1' },
headers
)
);
const req = httpMock.expectOne(url); const req = httpMock.expectOne(url);
expect(req.request.method).toBe('POST'); expect(req.request.method).toBe('POST');
@ -149,14 +153,14 @@ describe('Data Service', () => {
req.flush('bodyData'); req.flush('bodyData');
httpMock.verify(); httpMock.verify();
})); });
it('call without ngsw-bypass param by default', waitForAsync(() => { it('call without ngsw-bypass param by default', async () => {
const url = 'testurl'; const url = 'testurl';
dataService await lastValueFrom(
.post(url, { some: 'thing' }, { configId: 'configId1' }) dataService.post(url, { some: 'thing' }, { configId: 'configId1' })
.subscribe(); );
const req = httpMock.expectOne(url); const req = httpMock.expectOne(url);
expect(req.request.method).toBe('POST'); expect(req.request.method).toBe('POST');
@ -166,18 +170,19 @@ describe('Data Service', () => {
req.flush('bodyData'); req.flush('bodyData');
httpMock.verify(); httpMock.verify();
})); });
it('call with ngsw-bypass param', waitForAsync(() => { it('call with ngsw-bypass param', async () => {
const url = 'testurl'; const url = 'testurl';
dataService await lastValueFrom(
.post( dataService.post(
url, url,
{ some: 'thing' }, { some: 'thing' },
{ configId: 'configId1', ngswBypass: true } { configId: 'configId1', ngswBypass: true }
) )
.subscribe(); );
// biome-ignore lint/style/useTemplate: <explanation>
const req = httpMock.expectOne(url + '?ngsw-bypass='); const req = httpMock.expectOne(url + '?ngsw-bypass=');
expect(req.request.method).toBe('POST'); expect(req.request.method).toBe('POST');
@ -187,6 +192,6 @@ describe('Data Service', () => {
req.flush('bodyData'); req.flush('bodyData');
httpMock.verify(); httpMock.verify();
})); });
}); });
}); });

View File

@ -1,4 +1,4 @@
import { PassedInitialConfig, createStaticLoader } from './auth-config'; import { type PassedInitialConfig, createStaticLoader } from './auth-config';
describe('AuthConfig', () => { describe('AuthConfig', () => {
describe('createStaticLoader', () => { describe('createStaticLoader', () => {

View File

@ -1,9 +1,9 @@
import { InjectionToken, Provider } from 'injection-js'; import { InjectionToken, type Provider } from 'injection-js';
import { import {
StsConfigLoader, type StsConfigLoader,
StsConfigStaticLoader, StsConfigStaticLoader,
} from './config/loader/config-loader'; } from './config/loader/config-loader';
import { OpenIdConfiguration } from './config/openid-configuration'; import type { OpenIdConfiguration } from './config/openid-configuration';
export interface PassedInitialConfig { export interface PassedInitialConfig {
config?: OpenIdConfiguration | OpenIdConfiguration[]; config?: OpenIdConfiguration | OpenIdConfiguration[];

View File

@ -1,13 +1,14 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { EventTypes } from '../public-events/event-types'; import { EventTypes } from '../public-events/event-types';
import { PublicEventsService } from '../public-events/public-events.service'; import { PublicEventsService } from '../public-events/public-events.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockProvider } from '../testing/mock';
import { PlatformProvider } from '../utils/platform-provider/platform.provider'; import { PlatformProvider } from '../utils/platform-provider/platform.provider';
import { TokenValidationService } from '../validation/token-validation.service'; import { TokenValidationService } from '../validation/token-validation.service';
import { ValidationResult } from '../validation/validation-result'; import type { ValidationResult } from '../validation/validation-result';
import { AuthStateService } from './auth-state.service'; import { AuthStateService } from './auth-state.service';
describe('Auth State Service', () => { describe('Auth State Service', () => {
@ -27,9 +28,6 @@ describe('Auth State Service', () => {
mockProvider(StoragePersistenceService), mockProvider(StoragePersistenceService),
], ],
}); });
});
beforeEach(() => {
authStateService = TestBed.inject(AuthStateService); authStateService = TestBed.inject(AuthStateService);
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
eventsService = TestBed.inject(PublicEventsService); eventsService = TestBed.inject(PublicEventsService);
@ -41,12 +39,12 @@ describe('Auth State Service', () => {
}); });
it('public authorize$ is observable$', () => { it('public authorize$ is observable$', () => {
expect(authStateService.authenticated$).toEqual(jasmine.any(Observable)); expect(authStateService.authenticated$).toBeInstanceOf(Observable);
}); });
describe('setAuthorizedAndFireEvent', () => { describe('setAuthorizedAndFireEvent', () => {
it('throws correct event with single config', () => { it('throws correct event with single config', () => {
const spy = spyOn( const spy = vi.spyOn(
(authStateService as any).authenticatedInternal$, (authStateService as any).authenticatedInternal$,
'next' 'next'
); );
@ -55,7 +53,7 @@ describe('Auth State Service', () => {
{ configId: 'configId1' }, { configId: 'configId1' },
]); ]);
expect(spy).toHaveBeenCalledOnceWith({ expect(spy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: true, isAuthenticated: true,
allConfigsAuthenticated: [ allConfigsAuthenticated: [
{ configId: 'configId1', isAuthenticated: true }, { configId: 'configId1', isAuthenticated: true },
@ -64,7 +62,7 @@ describe('Auth State Service', () => {
}); });
it('throws correct event with multiple configs', () => { it('throws correct event with multiple configs', () => {
const spy = spyOn( const spy = vi.spyOn(
(authStateService as any).authenticatedInternal$, (authStateService as any).authenticatedInternal$,
'next' 'next'
); );
@ -74,7 +72,7 @@ describe('Auth State Service', () => {
{ configId: 'configId2' }, { configId: 'configId2' },
]); ]);
expect(spy).toHaveBeenCalledOnceWith({ expect(spy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
allConfigsAuthenticated: [ allConfigsAuthenticated: [
{ configId: 'configId1', isAuthenticated: false }, { configId: 'configId1', isAuthenticated: false },
@ -86,26 +84,34 @@ describe('Auth State Service', () => {
it('throws correct event with multiple configs, one is authenticated', () => { it('throws correct event with multiple configs, one is authenticated', () => {
const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }]; const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }];
spyOn(storagePersistenceService, 'getAccessToken') mockImplementationWhenArgsEqual(
.withArgs(allConfigs[0]) mockImplementationWhenArgsEqual(
.and.returnValue('someAccessToken') vi.spyOn(storagePersistenceService, 'getAccessToken'),
.withArgs(allConfigs[1]) [allConfigs[0]!],
.and.returnValue(''); () => 'someAccessToken'
),
[allConfigs[1]!],
() => ''
);
spyOn(storagePersistenceService, 'getIdToken') mockImplementationWhenArgsEqual(
.withArgs(allConfigs[0]) mockImplementationWhenArgsEqual(
.and.returnValue('someIdToken') vi.spyOn(storagePersistenceService, 'getIdToken'),
.withArgs(allConfigs[1]) [allConfigs[0]!],
.and.returnValue(''); () => 'someIdToken'
),
[allConfigs[1]!],
() => ''
);
const spy = spyOn( const spy = vi.spyOn(
(authStateService as any).authenticatedInternal$, (authStateService as any).authenticatedInternal$,
'next' 'next'
); );
authStateService.setAuthenticatedAndFireEvent(allConfigs); authStateService.setAuthenticatedAndFireEvent(allConfigs);
expect(spy).toHaveBeenCalledOnceWith({ expect(spy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
allConfigsAuthenticated: [ allConfigsAuthenticated: [
{ configId: 'configId1', isAuthenticated: true }, { configId: 'configId1', isAuthenticated: true },
@ -117,17 +123,20 @@ describe('Auth State Service', () => {
describe('setUnauthorizedAndFireEvent', () => { describe('setUnauthorizedAndFireEvent', () => {
it('persist AuthState In Storage', () => { it('persist AuthState In Storage', () => {
const spy = spyOn(storagePersistenceService, 'resetAuthStateInStorage'); const spy = vi.spyOn(
storagePersistenceService,
'resetAuthStateInStorage'
);
authStateService.setUnauthenticatedAndFireEvent( authStateService.setUnauthenticatedAndFireEvent(
{ configId: 'configId1' }, { configId: 'configId1' },
[{ configId: 'configId1' }] [{ configId: 'configId1' }]
); );
expect(spy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); expect(spy).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
}); });
it('throws correct event with single config', () => { it('throws correct event with single config', () => {
const spy = spyOn( const spy = vi.spyOn(
(authStateService as any).authenticatedInternal$, (authStateService as any).authenticatedInternal$,
'next' 'next'
); );
@ -137,7 +146,7 @@ describe('Auth State Service', () => {
[{ configId: 'configId1' }] [{ configId: 'configId1' }]
); );
expect(spy).toHaveBeenCalledOnceWith({ expect(spy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
allConfigsAuthenticated: [ allConfigsAuthenticated: [
{ configId: 'configId1', isAuthenticated: false }, { configId: 'configId1', isAuthenticated: false },
@ -146,7 +155,7 @@ describe('Auth State Service', () => {
}); });
it('throws correct event with multiple configs', () => { it('throws correct event with multiple configs', () => {
const spy = spyOn( const spy = vi.spyOn(
(authStateService as any).authenticatedInternal$, (authStateService as any).authenticatedInternal$,
'next' 'next'
); );
@ -156,7 +165,7 @@ describe('Auth State Service', () => {
[{ configId: 'configId1' }, { configId: 'configId2' }] [{ configId: 'configId1' }, { configId: 'configId2' }]
); );
expect(spy).toHaveBeenCalledOnceWith({ expect(spy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
allConfigsAuthenticated: [ allConfigsAuthenticated: [
{ configId: 'configId1', isAuthenticated: false }, { configId: 'configId1', isAuthenticated: false },
@ -166,19 +175,27 @@ describe('Auth State Service', () => {
}); });
it('throws correct event with multiple configs, one is authenticated', () => { it('throws correct event with multiple configs, one is authenticated', () => {
spyOn(storagePersistenceService, 'getAccessToken') mockImplementationWhenArgsEqual(
.withArgs({ configId: 'configId1' }) mockImplementationWhenArgsEqual(
.and.returnValue('someAccessToken') vi.spyOn(storagePersistenceService, 'getAccessToken'),
.withArgs({ configId: 'configId2' }) [{ configId: 'configId1' }],
.and.returnValue(''); () => 'someAccessToken'
),
[{ configId: 'configId2' }],
() => ''
);
spyOn(storagePersistenceService, 'getIdToken') mockImplementationWhenArgsEqual(
.withArgs({ configId: 'configId1' }) mockImplementationWhenArgsEqual(
.and.returnValue('someIdToken') vi.spyOn(storagePersistenceService, 'getIdToken'),
.withArgs({ configId: 'configId2' }) [{ configId: 'configId1' }],
.and.returnValue(''); () => 'someIdToken'
),
[{ configId: 'configId2' }],
() => ''
);
const spy = spyOn( const spy = vi.spyOn(
(authStateService as any).authenticatedInternal$, (authStateService as any).authenticatedInternal$,
'next' 'next'
); );
@ -188,7 +205,7 @@ describe('Auth State Service', () => {
[{ configId: 'configId1' }, { configId: 'configId2' }] [{ configId: 'configId1' }, { configId: 'configId2' }]
); );
expect(spy).toHaveBeenCalledOnceWith({ expect(spy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
allConfigsAuthenticated: [ allConfigsAuthenticated: [
{ configId: 'configId1', isAuthenticated: true }, { configId: 'configId1', isAuthenticated: true },
@ -200,24 +217,27 @@ describe('Auth State Service', () => {
describe('updateAndPublishAuthState', () => { describe('updateAndPublishAuthState', () => {
it('calls eventsService', () => { it('calls eventsService', () => {
spyOn(eventsService, 'fireEvent'); vi.spyOn(eventsService, 'fireEvent');
authStateService.updateAndPublishAuthState({ const arg = {
isAuthenticated: false, isAuthenticated: false,
isRenewProcess: false, isRenewProcess: false,
validationResult: {} as ValidationResult, validationResult: {} as ValidationResult,
}); };
expect(eventsService.fireEvent).toHaveBeenCalledOnceWith( authStateService.updateAndPublishAuthState(arg);
expect(eventsService.fireEvent).toHaveBeenCalledOnce();
expect(eventsService.fireEvent).toHaveBeenCalledExactlyOnceWith(
EventTypes.NewAuthenticationResult, EventTypes.NewAuthenticationResult,
jasmine.any(Object) arg
); );
}); });
}); });
describe('setAuthorizationData', () => { describe('setAuthorizationData', () => {
it('stores accessToken', () => { it('stores accessToken', () => {
const spy = spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
const authResult = { const authResult = {
id_token: 'idtoken', id_token: 'idtoken',
access_token: 'accesstoken', access_token: 'accesstoken',
@ -237,18 +257,18 @@ describe('Auth State Service', () => {
[{ configId: 'configId1' }] [{ configId: 'configId1' }]
); );
expect(spy).toHaveBeenCalledTimes(2); expect(spy).toHaveBeenCalledTimes(2);
expect(spy.calls.allArgs()).toEqual([ expect(spy).toHaveBeenCalledWith([
['authzData', 'accesstoken', { configId: 'configId1' }], ['authzData', 'accesstoken', { configId: 'configId1' }],
[ [
'access_token_expires_at', 'access_token_expires_at',
jasmine.any(Number), expect.any(Number),
{ configId: 'configId1' }, { configId: 'configId1' },
], ],
]); ]);
}); });
it('does not crash and store accessToken when authResult is null', () => { it('does not crash and store accessToken when authResult is null', () => {
const spy = spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
const authResult = null; const authResult = null;
authStateService.setAuthorizationData( authStateService.setAuthorizationData(
@ -262,7 +282,7 @@ describe('Auth State Service', () => {
}); });
it('calls setAuthenticatedAndFireEvent() method', () => { it('calls setAuthenticatedAndFireEvent() method', () => {
const spy = spyOn(authStateService, 'setAuthenticatedAndFireEvent'); const spy = vi.spyOn(authStateService, 'setAuthenticatedAndFireEvent');
const authResult = { const authResult = {
id_token: 'idtoken', id_token: 'idtoken',
access_token: 'accesstoken', access_token: 'accesstoken',
@ -288,28 +308,29 @@ describe('Auth State Service', () => {
describe('getAccessToken', () => { describe('getAccessToken', () => {
it('isAuthorized is false returns null', () => { it('isAuthorized is false returns null', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
const result = authStateService.getAccessToken({ configId: 'configId1' }); const result = authStateService.getAccessToken({ configId: 'configId1' });
expect(result).toBe(''); expect(result).toBe('');
}); });
it('returns false if storagePersistenceService returns something falsy but authorized', () => { it('returns false if storagePersistenceService returns something falsy but authorized', () => {
spyOn(authStateService, 'isAuthenticated').and.returnValue(true); vi.spyOn(authStateService, 'isAuthenticated').mockReturnValue(true);
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
const result = authStateService.getAccessToken({ configId: 'configId1' }); const result = authStateService.getAccessToken({ configId: 'configId1' });
expect(result).toBe(''); expect(result).toBe('');
}); });
it('isAuthorized is true returns decodeURIComponent(token)', () => { it('isAuthorized is true returns decodeURIComponent(token)', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
'HenloLegger' 'HenloLegger'
); );
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'HenloFuriend' 'HenloFuriend'
); );
const result = authStateService.getAccessToken({ configId: 'configId1' }); const result = authStateService.getAccessToken({ configId: 'configId1' });
expect(result).toBe(decodeURIComponent('HenloLegger')); expect(result).toBe(decodeURIComponent('HenloLegger'));
@ -318,12 +339,14 @@ describe('Auth State Service', () => {
describe('getAuthenticationResult', () => { describe('getAuthenticationResult', () => {
it('isAuthorized is false returns null', () => { it('isAuthorized is false returns null', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
spyOn(storagePersistenceService, 'getAuthenticationResult') mockImplementationWhenArgsEqual(
.withArgs({ configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'getAuthenticationResult'),
.and.returnValue({}); [{ configId: 'configId1' }],
() => ({})
);
const result = authStateService.getAuthenticationResult({ const result = authStateService.getAuthenticationResult({
configId: 'configId1', configId: 'configId1',
@ -333,10 +356,13 @@ describe('Auth State Service', () => {
}); });
it('returns false if storagePersistenceService returns something falsy but authorized', () => { it('returns false if storagePersistenceService returns something falsy but authorized', () => {
spyOn(authStateService, 'isAuthenticated').and.returnValue(true); vi.spyOn(authStateService, 'isAuthenticated').mockReturnValue(true);
spyOn(storagePersistenceService, 'getAuthenticationResult')
.withArgs({ configId: 'configId1' }) mockImplementationWhenArgsEqual(
.and.returnValue({}); vi.spyOn(storagePersistenceService, 'getAuthenticationResult'),
[{ configId: 'configId1' }],
() => ({})
);
const result = authStateService.getAuthenticationResult({ const result = authStateService.getAuthenticationResult({
configId: 'configId1', configId: 'configId1',
@ -346,15 +372,18 @@ describe('Auth State Service', () => {
}); });
it('isAuthorized is true returns object', () => { it('isAuthorized is true returns object', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
'HenloLegger' 'HenloLegger'
); );
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'HenloFuriend' 'HenloFuriend'
); );
spyOn(storagePersistenceService, 'getAuthenticationResult')
.withArgs({ configId: 'configId1' }) mockImplementationWhenArgsEqual(
.and.returnValue({ scope: 'HenloFuriend' }); vi.spyOn(storagePersistenceService, 'getAuthenticationResult'),
[{ configId: 'configId1' }],
() => ({ scope: 'HenloFuriend' })
);
const result = authStateService.getAuthenticationResult({ const result = authStateService.getAuthenticationResult({
configId: 'configId1', configId: 'configId1',
@ -366,18 +395,18 @@ describe('Auth State Service', () => {
describe('getIdToken', () => { describe('getIdToken', () => {
it('isAuthorized is false returns null', () => { it('isAuthorized is false returns null', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
const result = authStateService.getIdToken({ configId: 'configId1' }); const result = authStateService.getIdToken({ configId: 'configId1' });
expect(result).toBe(''); expect(result).toBe('');
}); });
it('isAuthorized is true returns decodeURIComponent(token)', () => { it('isAuthorized is true returns decodeURIComponent(token)', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
'HenloLegger' 'HenloLegger'
); );
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'HenloFuriend' 'HenloFuriend'
); );
const result = authStateService.getIdToken({ configId: 'configId1' }); const result = authStateService.getIdToken({ configId: 'configId1' });
@ -388,8 +417,8 @@ describe('Auth State Service', () => {
describe('getRefreshToken', () => { describe('getRefreshToken', () => {
it('isAuthorized is false returns null', () => { it('isAuthorized is false returns null', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
const result = authStateService.getRefreshToken({ const result = authStateService.getRefreshToken({
configId: 'configId1', configId: 'configId1',
}); });
@ -398,13 +427,13 @@ describe('Auth State Service', () => {
}); });
it('isAuthorized is true returns decodeURIComponent(token)', () => { it('isAuthorized is true returns decodeURIComponent(token)', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
'HenloLegger' 'HenloLegger'
); );
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'HenloFuriend' 'HenloFuriend'
); );
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
'HenloRefreshLegger' 'HenloRefreshLegger'
); );
const result = authStateService.getRefreshToken({ const result = authStateService.getRefreshToken({
@ -417,105 +446,105 @@ describe('Auth State Service', () => {
describe('areAuthStorageTokensValid', () => { describe('areAuthStorageTokensValid', () => {
it('isAuthorized is false returns false', () => { it('isAuthorized is false returns false', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
const result = authStateService.areAuthStorageTokensValid({ const result = authStateService.areAuthStorageTokensValid({
configId: 'configId1', configId: 'configId1',
}); });
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('isAuthorized is true and id_token is expired returns true', () => { it('isAuthorized is true and id_token is expired returns true', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
'HenloLegger' 'HenloLegger'
); );
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'HenloFuriend' 'HenloFuriend'
); );
spyOn( vi.spyOn(
authStateService as any, authStateService as any,
'hasIdTokenExpiredAndRenewCheckIsEnabled' 'hasIdTokenExpiredAndRenewCheckIsEnabled'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authStateService as any, authStateService as any,
'hasAccessTokenExpiredIfExpiryExists' 'hasAccessTokenExpiredIfExpiryExists'
).and.returnValue(false); ).mockReturnValue(false);
const result = authStateService.areAuthStorageTokensValid({ const result = authStateService.areAuthStorageTokensValid({
configId: 'configId1', configId: 'configId1',
}); });
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('isAuthorized is true and access_token is expired returns true', () => { it('isAuthorized is true and access_token is expired returns true', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
'HenloLegger' 'HenloLegger'
); );
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'HenloFuriend' 'HenloFuriend'
); );
spyOn( vi.spyOn(
authStateService as any, authStateService as any,
'hasIdTokenExpiredAndRenewCheckIsEnabled' 'hasIdTokenExpiredAndRenewCheckIsEnabled'
).and.returnValue(false); ).mockReturnValue(false);
spyOn( vi.spyOn(
authStateService as any, authStateService as any,
'hasAccessTokenExpiredIfExpiryExists' 'hasAccessTokenExpiredIfExpiryExists'
).and.returnValue(true); ).mockReturnValue(true);
const result = authStateService.areAuthStorageTokensValid({ const result = authStateService.areAuthStorageTokensValid({
configId: 'configId1', configId: 'configId1',
}); });
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('isAuthorized is true and id_token is not expired returns true', () => { it('isAuthorized is true and id_token is not expired returns true', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
'HenloLegger' 'HenloLegger'
); );
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'HenloFuriend' 'HenloFuriend'
); );
spyOn( vi.spyOn(
authStateService as any, authStateService as any,
'hasIdTokenExpiredAndRenewCheckIsEnabled' 'hasIdTokenExpiredAndRenewCheckIsEnabled'
).and.returnValue(false); ).mockReturnValue(false);
spyOn( vi.spyOn(
authStateService as any, authStateService as any,
'hasAccessTokenExpiredIfExpiryExists' 'hasAccessTokenExpiredIfExpiryExists'
).and.returnValue(false); ).mockReturnValue(false);
const result = authStateService.areAuthStorageTokensValid({ const result = authStateService.areAuthStorageTokensValid({
configId: 'configId1', configId: 'configId1',
}); });
expect(result).toBeTrue(); expect(result).toBeTruthy();
}); });
it('authState is AuthorizedState.Authorized and id_token is not expired fires event', () => { it('authState is AuthorizedState.Authorized and id_token is not expired fires event', () => {
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
'HenloLegger' 'HenloLegger'
); );
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'HenloFuriend' 'HenloFuriend'
); );
spyOn( vi.spyOn(
authStateService as any, authStateService as any,
'hasIdTokenExpiredAndRenewCheckIsEnabled' 'hasIdTokenExpiredAndRenewCheckIsEnabled'
).and.returnValue(false); ).mockReturnValue(false);
spyOn( vi.spyOn(
authStateService as any, authStateService as any,
'hasAccessTokenExpiredIfExpiryExists' 'hasAccessTokenExpiredIfExpiryExists'
).and.returnValue(false); ).mockReturnValue(false);
const result = authStateService.areAuthStorageTokensValid({ const result = authStateService.areAuthStorageTokensValid({
configId: 'configId1', configId: 'configId1',
}); });
expect(result).toBeTrue(); expect(result).toBeTruthy();
}); });
}); });
@ -527,56 +556,65 @@ describe('Auth State Service', () => {
triggerRefreshWhenIdTokenExpired: true, triggerRefreshWhenIdTokenExpired: true,
}; };
spyOn(storagePersistenceService, 'getIdToken') mockImplementationWhenArgsEqual(
.withArgs(config) vi.spyOn(storagePersistenceService, 'getIdToken'),
.and.returnValue('idToken'); [config],
const spy = spyOn( () => 'idToken'
tokenValidationService, );
'hasIdTokenExpired' const spy = vi
).and.callFake((_a, _b) => true); .spyOn(tokenValidationService, 'hasIdTokenExpired')
.mockImplementation((_a, _b) => true);
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config); authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
expect(spy).toHaveBeenCalledOnceWith('idToken', config, 30); expect(spy).toHaveBeenCalledExactlyOnceWith('idToken', config, 30);
}); });
it('fires event if idToken is expired', () => { it('fires event if idToken is expired', () => {
spyOn(tokenValidationService, 'hasIdTokenExpired').and.callFake( vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockImplementation(
(_a, _b) => true (_a, _b) => true
); );
const spy = spyOn(eventsService, 'fireEvent'); const spy = vi.spyOn(eventsService, 'fireEvent');
const config = { const config = {
configId: 'configId1', configId: 'configId1',
renewTimeBeforeTokenExpiresInSeconds: 30, renewTimeBeforeTokenExpiresInSeconds: 30,
triggerRefreshWhenIdTokenExpired: true, triggerRefreshWhenIdTokenExpired: true,
}; };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authnResult', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue('idToken'); ['authnResult', config],
() => 'idToken'
);
const result = const result =
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config); authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
expect(result).toBe(true); expect(result).toBe(true);
expect(spy).toHaveBeenCalledOnceWith(EventTypes.IdTokenExpired, true); expect(spy).toHaveBeenCalledExactlyOnceWith(
EventTypes.IdTokenExpired,
true
);
}); });
it('does NOT fire event if idToken is NOT expired', () => { it('does NOT fire event if idToken is NOT expired', () => {
spyOn(tokenValidationService, 'hasIdTokenExpired').and.callFake( vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockImplementation(
(_a, _b) => false (_a, _b) => false
); );
const spy = spyOn(eventsService, 'fireEvent'); const spy = vi.spyOn(eventsService, 'fireEvent');
const config = { const config = {
configId: 'configId1', configId: 'configId1',
renewTimeBeforeTokenExpiresInSeconds: 30, renewTimeBeforeTokenExpiresInSeconds: 30,
}; };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authnResult', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue('idToken'); ['authnResult', config],
() => 'idToken'
);
const result = const result =
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config); authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
@ -595,41 +633,45 @@ describe('Auth State Service', () => {
renewTimeBeforeTokenExpiresInSeconds: 5, renewTimeBeforeTokenExpiresInSeconds: 5,
}; };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('access_token_expires_at', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(date); ['access_token_expires_at', config],
const spy = spyOn( () => date
tokenValidationService, );
'validateAccessTokenNotExpired' const spy = vi
).and.returnValue(validateAccessTokenNotExpiredResult); .spyOn(tokenValidationService, 'validateAccessTokenNotExpired')
.mockReturnValue(validateAccessTokenNotExpiredResult);
const result = const result =
authStateService.hasAccessTokenExpiredIfExpiryExists(config); authStateService.hasAccessTokenExpiredIfExpiryExists(config);
expect(spy).toHaveBeenCalledOnceWith(date, config, 5); expect(spy).toHaveBeenCalledExactlyOnceWith(date, config, 5);
expect(result).toEqual(expectedResult); expect(result).toEqual(expectedResult);
}); });
it('throws event when token is expired', () => { it('throws event when token is expired', () => {
const validateAccessTokenNotExpiredResult = false; const validateAccessTokenNotExpiredResult = false;
const expectedResult = !validateAccessTokenNotExpiredResult; const expectedResult = !validateAccessTokenNotExpiredResult;
// spyOn(configurationProvider, 'getOpenIDConfiguration').and.returnValue({ renewTimeBeforeTokenExpiresInSeconds: 5 }); // vi.spyOn(configurationProvider, 'getOpenIDConfiguration').mockReturnValue({ renewTimeBeforeTokenExpiresInSeconds: 5 });
const date = new Date(new Date().toUTCString()); const date = new Date(new Date().toUTCString());
const config = { const config = {
configId: 'configId1', configId: 'configId1',
renewTimeBeforeTokenExpiresInSeconds: 5, renewTimeBeforeTokenExpiresInSeconds: 5,
}; };
spyOn(eventsService, 'fireEvent'); vi.spyOn(eventsService, 'fireEvent');
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('access_token_expires_at', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(date); ['access_token_expires_at', config],
spyOn( () => date
);
vi.spyOn(
tokenValidationService, tokenValidationService,
'validateAccessTokenNotExpired' 'validateAccessTokenNotExpired'
).and.returnValue(validateAccessTokenNotExpiredResult); ).mockReturnValue(validateAccessTokenNotExpiredResult);
authStateService.hasAccessTokenExpiredIfExpiryExists(config); authStateService.hasAccessTokenExpiredIfExpiryExists(config);
expect(eventsService.fireEvent).toHaveBeenCalledOnceWith( expect(eventsService.fireEvent).toHaveBeenCalledExactlyOnceWith(
EventTypes.TokenExpired, EventTypes.TokenExpired,
expectedResult expectedResult
); );

View File

@ -1,15 +1,15 @@
import { Injectable, inject } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { BehaviorSubject, Observable, throwError } from 'rxjs'; import { BehaviorSubject, type Observable, throwError } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators'; import { distinctUntilChanged } from 'rxjs/operators';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { AuthResult } from '../flows/callback-context'; import type { AuthResult } from '../flows/callback-context';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { EventTypes } from '../public-events/event-types'; import { EventTypes } from '../public-events/event-types';
import { PublicEventsService } from '../public-events/public-events.service'; import { PublicEventsService } from '../public-events/public-events.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { TokenValidationService } from '../validation/token-validation.service'; import { TokenValidationService } from '../validation/token-validation.service';
import { AuthenticatedResult } from './auth-result'; import type { AuthenticatedResult } from './auth-result';
import { AuthStateResult } from './auth-state'; import type { AuthStateResult } from './auth-state';
const DEFAULT_AUTHRESULT = { const DEFAULT_AUTHRESULT = {
isAuthenticated: false, isAuthenticated: false,
@ -293,7 +293,7 @@ export class AuthStateService {
}; };
} }
return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs); return this.checkallConfigsIfTheyAreAuthenticated(allConfigs);
} }
private composeUnAuthenticatedResult( private composeUnAuthenticatedResult(
@ -310,10 +310,10 @@ export class AuthStateService {
}; };
} }
return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs); return this.checkallConfigsIfTheyAreAuthenticated(allConfigs);
} }
private checkAllConfigsIfTheyAreAuthenticated( private checkallConfigsIfTheyAreAuthenticated(
allConfigs: OpenIdConfiguration[] allConfigs: OpenIdConfiguration[]
): AuthenticatedResult { ): AuthenticatedResult {
const allConfigsAuthenticated = allConfigs.map((config) => ({ const allConfigsAuthenticated = allConfigs.map((config) => ({

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,15 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { forkJoin, Observable, of, throwError } from 'rxjs'; import { type Observable, forkJoin, of, throwError } from 'rxjs';
import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { catchError, map, switchMap, tap } from 'rxjs/operators';
import { AutoLoginService } from '../auto-login/auto-login.service'; import { AutoLoginService } from '../auto-login/auto-login.service';
import { CallbackService } from '../callback/callback.service'; import { CallbackService } from '../callback/callback.service';
import { PeriodicallyTokenCheckService } from '../callback/periodically-token-check.service'; import { PeriodicallyTokenCheckService } from '../callback/periodically-token-check.service';
import { RefreshSessionService } from '../callback/refresh-session.service'; import { RefreshSessionService } from '../callback/refresh-session.service';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CheckSessionService } from '../iframe/check-session.service'; import { CheckSessionService } from '../iframe/check-session.service';
import { SilentRenewService } from '../iframe/silent-renew.service'; import { SilentRenewService } from '../iframe/silent-renew.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { LoginResponse } from '../login/login-response'; import type { LoginResponse } from '../login/login-response';
import { PopUpService } from '../login/popup/popup.service'; import { PopUpService } from '../login/popup/popup.service';
import { EventTypes } from '../public-events/event-types'; import { EventTypes } from '../public-events/event-types';
import { PublicEventsService } from '../public-events/public-events.service'; import { PublicEventsService } from '../public-events/public-events.service';

View File

@ -1,6 +1,6 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../test/auto-mock'; import { vi } from 'vitest';
import { PASSED_CONFIG } from './auth-config'; import { PASSED_CONFIG } from './auth-config';
import { AuthModule } from './auth.module'; import { AuthModule } from './auth.module';
import { ConfigurationService } from './config/config.service'; import { ConfigurationService } from './config/config.service';
@ -9,19 +9,20 @@ import {
StsConfigLoader, StsConfigLoader,
StsConfigStaticLoader, StsConfigStaticLoader,
} from './config/loader/config-loader'; } from './config/loader/config-loader';
import { mockProvider } from './testing/mock';
describe('AuthModule', () => { describe('AuthModule', () => {
describe('APP_CONFIG', () => { describe('APP_CONFIG', () => {
beforeEach(waitForAsync(() => { beforeEach(async () => {
TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [AuthModule.forRoot({ config: { authority: 'something' } })], imports: [AuthModule.forRoot({ config: { authority: 'something' } })],
providers: [mockProvider(ConfigurationService)], providers: [mockProvider(ConfigurationService)],
}).compileComponents(); }).compileComponents();
})); });
it('should create', () => { it('should create', () => {
expect(AuthModule).toBeDefined(); expect(AuthModule).toBeDefined();
expect(AuthModule.forRoot({})).toBeDefined(); expect(new AuthModule()).toBeDefined();
}); });
it('should provide config', () => { it('should provide config', () => {
@ -38,8 +39,8 @@ describe('AuthModule', () => {
}); });
describe('StsConfigHttpLoader', () => { describe('StsConfigHttpLoader', () => {
beforeEach(waitForAsync(() => { beforeEach(async () => {
TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [ imports: [
AuthModule.forRoot({ AuthModule.forRoot({
loader: { loader: {
@ -50,7 +51,7 @@ describe('AuthModule', () => {
], ],
providers: [mockProvider(ConfigurationService)], providers: [mockProvider(ConfigurationService)],
}).compileComponents(); }).compileComponents();
})); });
it('should create StsConfigStaticLoader if config is passed', () => { it('should create StsConfigStaticLoader if config is passed', () => {
const configLoader = TestBed.inject(StsConfigLoader); const configLoader = TestBed.inject(StsConfigLoader);

View File

@ -1,25 +1,41 @@
import { CommonModule } from '@angular/common';
import { import {
provideHttpClient, type InjectionToken,
withInterceptorsFromDi, Injector,
} from '@ngify/http'; ReflectiveInjector,
import { ModuleWithProviders, NgModule } from 'injection-js'; type Type,
import { PassedInitialConfig } from './auth-config'; } from 'injection-js';
import type { PassedInitialConfig } from './auth-config';
import type { Module } from './injection';
import { _provideAuth } from './provide-auth'; import { _provideAuth } from './provide-auth';
@NgModule({ export interface AuthModuleOptions {
declarations: [], passedConfig: PassedInitialConfig;
exports: [], parentInjector?: ReflectiveInjector;
imports: [CommonModule], }
providers: [provideHttpClient(withInterceptorsFromDi())],
}) export class AuthModule extends Injector {
export class AuthModule { passedConfig: PassedInitialConfig;
static forRoot( injector: ReflectiveInjector;
passedConfig: PassedInitialConfig parentInjector?: Injector;
): ModuleWithProviders<AuthModule> {
return { constructor(passedConfig?: PassedInitialConfig, parentInjector?: Injector) {
ngModule: AuthModule, super();
providers: [..._provideAuth(passedConfig)], this.passedConfig = passedConfig ?? {};
}; this.parentInjector = parentInjector;
this.injector = ReflectiveInjector.resolveAndCreate(
[..._provideAuth(this.passedConfig)],
this.parentInjector
);
}
static forRoot(passedConfig?: PassedInitialConfig): Module {
return (parentInjector?: Injector) =>
new AuthModule(passedConfig, parentInjector);
}
get<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T): T;
get(token: any, notFoundValue?: any);
get(token: unknown, notFoundValue?: unknown): any {
return this.injector.get(token, notFoundValue);
} }
} }

View File

@ -1,17 +1,17 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed, mockRouterProvider } from '@/testing';
import { import {
ActivatedRouteSnapshot, AbstractRouter,
Router, type ActivatedRouteSnapshot,
RouterStateSnapshot, type RouterStateSnapshot,
} from '@angular/router'; } from 'oidc-client-rx';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { CheckAuthService } from '../auth-state/check-auth.service'; import { CheckAuthService } from '../auth-state/check-auth.service';
import { ConfigurationService } from '../config/config.service'; import { ConfigurationService } from '../config/config.service';
import { LoginService } from '../login/login.service'; import { LoginService } from '../login/login.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockProvider } from '../testing/mock';
import { import {
AutoLoginPartialRoutesGuard, AutoLoginPartialRoutesGuard,
autoLoginPartialRoutesGuard, autoLoginPartialRoutesGuard,
@ -19,11 +19,12 @@ import {
} from './auto-login-partial-routes.guard'; } from './auto-login-partial-routes.guard';
import { AutoLoginService } from './auto-login.service'; import { AutoLoginService } from './auto-login.service';
describe(`AutoLoginPartialRoutesGuard`, () => { describe('AutoLoginPartialRoutesGuard', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [RouterTestingModule], imports: [],
providers: [ providers: [
mockRouterProvider(),
AutoLoginService, AutoLoginService,
mockProvider(AuthStateService), mockProvider(AuthStateService),
mockProvider(LoginService), mockProvider(LoginService),
@ -41,7 +42,7 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
let storagePersistenceService: StoragePersistenceService; let storagePersistenceService: StoragePersistenceService;
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let autoLoginService: AutoLoginService; let autoLoginService: AutoLoginService;
let router: Router; let router: AbstractRouter;
beforeEach(() => { beforeEach(() => {
authStateService = TestBed.inject(AuthStateService); authStateService = TestBed.inject(AuthStateService);
@ -49,15 +50,16 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
configurationService = TestBed.inject(ConfigurationService); configurationService = TestBed.inject(ConfigurationService);
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
of({ configId: 'configId1' }) of({ configId: 'configId1' })
); );
guard = TestBed.inject(AutoLoginPartialRoutesGuard); guard = TestBed.inject(AutoLoginPartialRoutesGuard);
autoLoginService = TestBed.inject(AutoLoginService); autoLoginService = TestBed.inject(AutoLoginService);
router = TestBed.inject(Router); router = TestBed.inject(AbstractRouter);
}); });
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
afterEach(() => { afterEach(() => {
storagePersistenceService.clear({}); storagePersistenceService.clear({});
}); });
@ -67,19 +69,19 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
}); });
describe('canActivate', () => { describe('canActivate', () => {
it('should save current route and call `login` if not authenticated already', waitForAsync(() => { it('should save current route and call `login` if not authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
guard guard
.canActivate( .canActivate(
@ -87,32 +89,32 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
{ url: 'some-url1' } as RouterStateSnapshot { url: 'some-url1' } as RouterStateSnapshot
) )
.subscribe(() => { .subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'some-url1' 'some-url1'
); );
expect(loginSpy).toHaveBeenCalledOnceWith({ expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
configId: 'configId1', configId: 'configId1',
}); });
expect( expect(
checkSavedRedirectRouteAndNavigateSpy checkSavedRedirectRouteAndNavigateSpy
).not.toHaveBeenCalled(); ).not.toHaveBeenCalled();
}); });
})); });
it('should save current route and call `login` if not authenticated already and add custom params', waitForAsync(() => { it('should save current route and call `login` if not authenticated already and add custom params', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
guard guard
.canActivate( .canActivate(
@ -120,11 +122,11 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
{ url: 'some-url1' } as RouterStateSnapshot { url: 'some-url1' } as RouterStateSnapshot
) )
.subscribe(() => { .subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'some-url1' 'some-url1'
); );
expect(loginSpy).toHaveBeenCalledOnceWith( expect(loginSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
{ customParams: { custom: 'param' } } { customParams: { custom: 'param' } }
); );
@ -132,21 +134,21 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
checkSavedRedirectRouteAndNavigateSpy checkSavedRedirectRouteAndNavigateSpy
).not.toHaveBeenCalled(); ).not.toHaveBeenCalled();
}); });
})); });
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => { it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
true true
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
guard guard
.canActivate( .canActivate(
@ -158,25 +160,25 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
expect(loginSpy).not.toHaveBeenCalled(); expect(loginSpy).not.toHaveBeenCalled();
expect( expect(
checkSavedRedirectRouteAndNavigateSpy checkSavedRedirectRouteAndNavigateSpy
).toHaveBeenCalledOnceWith({ configId: 'configId1' }); ).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
}); });
})); });
}); });
describe('canActivateChild', () => { describe('canActivateChild', () => {
it('should save current route and call `login` if not authenticated already', waitForAsync(() => { it('should save current route and call `login` if not authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
guard guard
.canActivateChild( .canActivateChild(
@ -184,32 +186,32 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
{ url: 'some-url1' } as RouterStateSnapshot { url: 'some-url1' } as RouterStateSnapshot
) )
.subscribe(() => { .subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'some-url1' 'some-url1'
); );
expect(loginSpy).toHaveBeenCalledOnceWith({ expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
configId: 'configId1', configId: 'configId1',
}); });
expect( expect(
checkSavedRedirectRouteAndNavigateSpy checkSavedRedirectRouteAndNavigateSpy
).not.toHaveBeenCalled(); ).not.toHaveBeenCalled();
}); });
})); });
it('should save current route and call `login` if not authenticated already with custom params', waitForAsync(() => { it('should save current route and call `login` if not authenticated already with custom params', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
guard guard
.canActivateChild( .canActivateChild(
@ -217,11 +219,11 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
{ url: 'some-url1' } as RouterStateSnapshot { url: 'some-url1' } as RouterStateSnapshot
) )
.subscribe(() => { .subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'some-url1' 'some-url1'
); );
expect(loginSpy).toHaveBeenCalledOnceWith( expect(loginSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
{ customParams: { custom: 'param' } } { customParams: { custom: 'param' } }
); );
@ -229,21 +231,21 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
checkSavedRedirectRouteAndNavigateSpy checkSavedRedirectRouteAndNavigateSpy
).not.toHaveBeenCalled(); ).not.toHaveBeenCalled();
}); });
})); });
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => { it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
true true
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
guard guard
.canActivateChild( .canActivateChild(
@ -255,51 +257,53 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
expect(loginSpy).not.toHaveBeenCalled(); expect(loginSpy).not.toHaveBeenCalled();
expect( expect(
checkSavedRedirectRouteAndNavigateSpy checkSavedRedirectRouteAndNavigateSpy
).toHaveBeenCalledOnceWith({ configId: 'configId1' }); ).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
}); });
})); });
}); });
describe('canLoad', () => { describe('canLoad', () => {
it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => { it('should save current route (empty) and call `login` if not authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
guard.canLoad().subscribe(() => { guard.canLoad().subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'' ''
); );
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
configId: 'configId1',
});
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
}); });
})); });
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', waitForAsync(() => { it('should save current route (with router extractedUrl) and call `login` if not authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
spyOn(router, 'getCurrentNavigation').and.returnValue({ vi.spyOn(router, 'getCurrentNavigation').mockReturnValue({
extractedUrl: router.parseUrl( extractedUrl: router.parseUrl(
'some-url12/with/some-param?queryParam=true' 'some-url12/with/some-param?queryParam=true'
), ),
@ -311,37 +315,39 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
}); });
guard.canLoad().subscribe(() => { guard.canLoad().subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'some-url12/with/some-param?queryParam=true' 'some-url12/with/some-param?queryParam=true'
); );
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
configId: 'configId1',
});
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
}); });
})); });
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => { it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
true true
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
guard.canLoad().subscribe(() => { guard.canLoad().subscribe(() => {
expect(saveRedirectRouteSpy).not.toHaveBeenCalled(); expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
expect(loginSpy).not.toHaveBeenCalled(); expect(loginSpy).not.toHaveBeenCalled();
expect( expect(
checkSavedRedirectRouteAndNavigateSpy checkSavedRedirectRouteAndNavigateSpy
).toHaveBeenCalledOnceWith({ configId: 'configId1' }); ).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
}); });
})); });
}); });
}); });
@ -352,7 +358,7 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
let storagePersistenceService: StoragePersistenceService; let storagePersistenceService: StoragePersistenceService;
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let autoLoginService: AutoLoginService; let autoLoginService: AutoLoginService;
let router: Router; let router: AbstractRouter;
beforeEach(() => { beforeEach(() => {
authStateService = TestBed.inject(AuthStateService); authStateService = TestBed.inject(AuthStateService);
@ -360,48 +366,52 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
configurationService = TestBed.inject(ConfigurationService); configurationService = TestBed.inject(ConfigurationService);
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( vi.spyOn(
of({ configId: 'configId1' }) configurationService,
); 'getOpenIDConfiguration'
).mockReturnValue(of({ configId: 'configId1' }));
autoLoginService = TestBed.inject(AutoLoginService); autoLoginService = TestBed.inject(AutoLoginService);
router = TestBed.inject(Router); router = TestBed.inject(AbstractRouter);
}); });
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
afterEach(() => { afterEach(() => {
storagePersistenceService.clear({}); storagePersistenceService.clear({});
}); });
it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => { it('should save current route (empty) and call `login` if not authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
const guard$ = TestBed.runInInjectionContext( const guard$ = TestBed.runInInjectionContext(
autoLoginPartialRoutesGuard autoLoginPartialRoutesGuard
); );
guard$.subscribe(() => { guard$.subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'' ''
); );
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
configId: 'configId1',
});
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
}); });
})); });
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', waitForAsync(() => { it('should save current route (with router extractedUrl) and call `login` if not authenticated already', async () => {
spyOn(router, 'getCurrentNavigation').and.returnValue({ vi.spyOn(router, 'getCurrentNavigation').mockReturnValue({
extractedUrl: router.parseUrl( extractedUrl: router.parseUrl(
'some-url12/with/some-param?queryParam=true' 'some-url12/with/some-param?queryParam=true'
), ),
@ -412,46 +422,48 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
trigger: 'imperative', trigger: 'imperative',
}); });
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
const guard$ = TestBed.runInInjectionContext( const guard$ = TestBed.runInInjectionContext(
autoLoginPartialRoutesGuard autoLoginPartialRoutesGuard
); );
guard$.subscribe(() => { guard$.subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'some-url12/with/some-param?queryParam=true' 'some-url12/with/some-param?queryParam=true'
); );
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
configId: 'configId1',
});
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
}); });
})); });
it('should save current route and call `login` if not authenticated already and add custom params', waitForAsync(() => { it('should save current route and call `login` if not authenticated already and add custom params', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
const guard$ = TestBed.runInInjectionContext(() => const guard$ = TestBed.runInInjectionContext(() =>
autoLoginPartialRoutesGuard({ autoLoginPartialRoutesGuard({
@ -460,31 +472,31 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
); );
guard$.subscribe(() => { guard$.subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'' ''
); );
expect(loginSpy).toHaveBeenCalledOnceWith( expect(loginSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
{ customParams: { custom: 'param' } } { customParams: { custom: 'param' } }
); );
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
}); });
})); });
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => { it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
true true
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
const guard$ = TestBed.runInInjectionContext( const guard$ = TestBed.runInInjectionContext(
autoLoginPartialRoutesGuard autoLoginPartialRoutesGuard
@ -495,9 +507,9 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
expect(loginSpy).not.toHaveBeenCalled(); expect(loginSpy).not.toHaveBeenCalled();
expect( expect(
checkSavedRedirectRouteAndNavigateSpy checkSavedRedirectRouteAndNavigateSpy
).toHaveBeenCalledOnceWith({ configId: 'configId1' }); ).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
}); });
})); });
}); });
describe('autoLoginPartialRoutesGuardWithConfig', () => { describe('autoLoginPartialRoutesGuardWithConfig', () => {
@ -513,44 +525,48 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
configurationService = TestBed.inject(ConfigurationService); configurationService = TestBed.inject(ConfigurationService);
spyOn(configurationService, 'getOpenIDConfiguration').and.callFake( vi.spyOn(
(configId) => of({ configId }) configurationService,
); 'getOpenIDConfiguration'
).mockImplementation((configId) => of({ configId }));
autoLoginService = TestBed.inject(AutoLoginService); autoLoginService = TestBed.inject(AutoLoginService);
}); });
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
afterEach(() => { afterEach(() => {
storagePersistenceService.clear({}); storagePersistenceService.clear({});
}); });
it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => { it('should save current route (empty) and call `login` if not authenticated already', async () => {
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const checkSavedRedirectRouteAndNavigateSpy = spyOn( const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
autoLoginService, autoLoginService,
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
const saveRedirectRouteSpy = spyOn( const saveRedirectRouteSpy = vi.spyOn(
autoLoginService, autoLoginService,
'saveRedirectRoute' 'saveRedirectRoute'
); );
const loginSpy = spyOn(loginService, 'login'); const loginSpy = vi.spyOn(loginService, 'login');
const guard$ = TestBed.runInInjectionContext( const guard$ = TestBed.runInInjectionContext(
autoLoginPartialRoutesGuardWithConfig('configId1') autoLoginPartialRoutesGuardWithConfig('configId1')
); );
guard$.subscribe(() => { guard$.subscribe(() => {
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'' ''
); );
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
configId: 'configId1',
});
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
}); });
})); });
}); });
}); });
}); });

View File

@ -1,15 +1,16 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { import type { Observable } from 'rxjs';
ActivatedRouteSnapshot,
Router,
RouterStateSnapshot,
} from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { AuthOptions } from '../auth-options'; import type { AuthOptions } from '../auth-options';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { ConfigurationService } from '../config/config.service'; import { ConfigurationService } from '../config/config.service';
import { injectAbstractType } from '../injection';
import { LoginService } from '../login/login.service'; import { LoginService } from '../login/login.service';
import {
AbstractRouter,
type ActivatedRouteSnapshot,
type RouterStateSnapshot,
} from '../router';
import { AutoLoginService } from './auto-login.service'; import { AutoLoginService } from './auto-login.service';
@Injectable() @Injectable()
@ -22,7 +23,7 @@ export class AutoLoginPartialRoutesGuard {
private readonly configurationService = inject(ConfigurationService); private readonly configurationService = inject(ConfigurationService);
private readonly router = inject(Router); private readonly router = injectAbstractType(AbstractRouter);
canLoad(): Observable<boolean> { canLoad(): Observable<boolean> {
const url = const url =
@ -79,14 +80,14 @@ export class AutoLoginPartialRoutesGuard {
export function autoLoginPartialRoutesGuard( export function autoLoginPartialRoutesGuard(
route?: ActivatedRouteSnapshot, route?: ActivatedRouteSnapshot,
state?: RouterStateSnapshot, _state?: RouterStateSnapshot,
configId?: string configId?: string
): Observable<boolean> { ): Observable<boolean> {
const configurationService = inject(ConfigurationService); const configurationService = inject(ConfigurationService);
const authStateService = inject(AuthStateService); const authStateService = inject(AuthStateService);
const loginService = inject(LoginService); const loginService = inject(LoginService);
const autoLoginService = inject(AutoLoginService); const autoLoginService = inject(AutoLoginService);
const router = inject(Router); const router = injectAbstractType(AbstractRouter);
const authOptions: AuthOptions | undefined = route?.data const authOptions: AuthOptions | undefined = route?.data
? { customParams: route.data } ? { customParams: route.data }
: undefined; : undefined;

View File

@ -1,8 +1,9 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockProvider } from '../testing/mock';
import { AutoLoginService } from './auto-login.service'; import { AutoLoginService } from './auto-login.service';
describe('AutoLoginService ', () => { describe('AutoLoginService ', () => {
@ -29,11 +30,11 @@ describe('AutoLoginService ', () => {
describe('checkSavedRedirectRouteAndNavigate', () => { describe('checkSavedRedirectRouteAndNavigate', () => {
it('if not route is saved, router and delete are not called', () => { it('if not route is saved, router and delete are not called', () => {
const deleteSpy = spyOn(storagePersistenceService, 'remove'); const deleteSpy = vi.spyOn(storagePersistenceService, 'remove');
const routerSpy = spyOn(router, 'navigateByUrl'); const routerSpy = vi.spyOn(router, 'navigateByUrl');
const readSpy = spyOn(storagePersistenceService, 'read').and.returnValue( const readSpy = vi
null .spyOn(storagePersistenceService, 'read')
); .mockReturnValue(null);
autoLoginService.checkSavedRedirectRouteAndNavigate({ autoLoginService.checkSavedRedirectRouteAndNavigate({
configId: 'configId1', configId: 'configId1',
@ -41,27 +42,27 @@ describe('AutoLoginService ', () => {
expect(deleteSpy).not.toHaveBeenCalled(); expect(deleteSpy).not.toHaveBeenCalled();
expect(routerSpy).not.toHaveBeenCalled(); expect(routerSpy).not.toHaveBeenCalled();
expect(readSpy).toHaveBeenCalledOnceWith('redirect', { expect(readSpy).toHaveBeenCalledExactlyOnceWith('redirect', {
configId: 'configId1', configId: 'configId1',
}); });
}); });
it('if route is saved, router and delete are called', () => { it('if route is saved, router and delete are called', () => {
const deleteSpy = spyOn(storagePersistenceService, 'remove'); const deleteSpy = vi.spyOn(storagePersistenceService, 'remove');
const routerSpy = spyOn(router, 'navigateByUrl'); const routerSpy = vi.spyOn(router, 'navigateByUrl');
const readSpy = spyOn(storagePersistenceService, 'read').and.returnValue( const readSpy = vi
'saved-route' .spyOn(storagePersistenceService, 'read')
); .mockReturnValue('saved-route');
autoLoginService.checkSavedRedirectRouteAndNavigate({ autoLoginService.checkSavedRedirectRouteAndNavigate({
configId: 'configId1', configId: 'configId1',
}); });
expect(deleteSpy).toHaveBeenCalledOnceWith('redirect', { expect(deleteSpy).toHaveBeenCalledExactlyOnceWith('redirect', {
configId: 'configId1', configId: 'configId1',
}); });
expect(routerSpy).toHaveBeenCalledOnceWith('saved-route'); expect(routerSpy).toHaveBeenCalledExactlyOnceWith('saved-route');
expect(readSpy).toHaveBeenCalledOnceWith('redirect', { expect(readSpy).toHaveBeenCalledExactlyOnceWith('redirect', {
configId: 'configId1', configId: 'configId1',
}); });
}); });
@ -69,16 +70,20 @@ describe('AutoLoginService ', () => {
describe('saveRedirectRoute', () => { describe('saveRedirectRoute', () => {
it('calls storageService with correct params', () => { it('calls storageService with correct params', () => {
const writeSpy = spyOn(storagePersistenceService, 'write'); const writeSpy = vi.spyOn(storagePersistenceService, 'write');
autoLoginService.saveRedirectRoute( autoLoginService.saveRedirectRoute(
{ configId: 'configId1' }, { configId: 'configId1' },
'some-route' 'some-route'
); );
expect(writeSpy).toHaveBeenCalledOnceWith('redirect', 'some-route', { expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', 'redirect',
}); 'some-route',
{
configId: 'configId1',
}
);
}); });
}); });
}); });

View File

@ -1,7 +1,8 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { Observable, of } from 'rxjs'; import { Observable, lastValueFrom, of } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { mockProvider } from '../testing/mock';
import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
import { UrlService } from '../utils/url/url.service'; import { UrlService } from '../utils/url/url.service';
import { CallbackService } from './callback.service'; import { CallbackService } from './callback.service';
@ -26,9 +27,6 @@ describe('CallbackService ', () => {
mockProvider(CodeFlowCallbackService), mockProvider(CodeFlowCallbackService),
], ],
}); });
});
beforeEach(() => {
callbackService = TestBed.inject(CallbackService); callbackService = TestBed.inject(CallbackService);
flowHelper = TestBed.inject(FlowHelper); flowHelper = TestBed.inject(FlowHelper);
implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService); implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService);
@ -38,10 +36,13 @@ describe('CallbackService ', () => {
describe('isCallback', () => { describe('isCallback', () => {
it('calls urlService.isCallbackFromSts with passed url', () => { it('calls urlService.isCallbackFromSts with passed url', () => {
const urlServiceSpy = spyOn(urlService, 'isCallbackFromSts'); const urlServiceSpy = vi.spyOn(urlService, 'isCallbackFromSts');
callbackService.isCallback('anyUrl'); callbackService.isCallback('anyUrl');
expect(urlServiceSpy).toHaveBeenCalledOnceWith('anyUrl', undefined); expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith(
'anyUrl',
undefined
);
}); });
}); });
@ -52,93 +53,98 @@ describe('CallbackService ', () => {
}); });
describe('handleCallbackAndFireEvents', () => { describe('handleCallbackAndFireEvents', () => {
it('calls authorizedCallbackWithCode if current flow is code flow', waitForAsync(() => { it('calls authorizedCallbackWithCode if current flow is code flow', async () => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const authorizedCallbackWithCodeSpy = spyOn( const authorizedCallbackWithCodeSpy = vi
codeFlowCallbackService, .spyOn(codeFlowCallbackService, 'authenticatedCallbackWithCode')
'authenticatedCallbackWithCode' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext));
callbackService await lastValueFrom(
.handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [ callbackService.handleCallbackAndFireEvents(
'anyUrl',
{ configId: 'configId1' }, { configId: 'configId1' },
]) [{ configId: 'configId1' }]
.subscribe(() => { )
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledOnceWith( );
'anyUrl',
{ configId: 'configId1' },
[{ configId: 'configId1' }]
);
});
}));
it('calls authorizedImplicitFlowCallback without hash if current flow is implicit flow and callbackurl does not include a hash', waitForAsync(() => { expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledExactlyOnceWith(
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); 'anyUrl',
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true); { configId: 'configId1' },
const authorizedCallbackWithCodeSpy = spyOn( [{ configId: 'configId1' }]
implicitFlowCallbackService, );
'authenticatedImplicitFlowCallback' });
).and.returnValue(of({} as CallbackContext));
callbackService it('calls authorizedImplicitFlowCallback without hash if current flow is implicit flow and callbackurl does not include a hash', async () => {
.handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [ vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
true
);
const authorizedCallbackWithCodeSpy = vi
.spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback')
.mockReturnValue(of({} as CallbackContext));
await lastValueFrom(
callbackService.handleCallbackAndFireEvents(
'anyUrl',
{ configId: 'configId1' }, { configId: 'configId1' },
]) [{ configId: 'configId1' }]
.subscribe(() => { )
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith( );
{ configId: 'configId1' }, expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith(
[{ configId: 'configId1' }] { configId: 'configId1' },
); [{ configId: 'configId1' }]
}); );
})); });
it('calls authorizedImplicitFlowCallback with hash if current flow is implicit flow and callbackurl does include a hash', waitForAsync(() => { it('calls authorizedImplicitFlowCallback with hash if current flow is implicit flow and callbackurl does include a hash', async () => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true); vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
const authorizedCallbackWithCodeSpy = spyOn( true
implicitFlowCallbackService, );
'authenticatedImplicitFlowCallback' const authorizedCallbackWithCodeSpy = vi
).and.returnValue(of({} as CallbackContext)); .spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback')
.mockReturnValue(of({} as CallbackContext));
callbackService await lastValueFrom(
.handleCallbackAndFireEvents( callbackService.handleCallbackAndFireEvents(
'anyUrlWithAHash#some-string', 'anyUrlWithAHash#some-string',
{ configId: 'configId1' }, { configId: 'configId1' },
[{ configId: 'configId1' }] [{ configId: 'configId1' }]
) )
.subscribe(() => { );
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith(
{ configId: 'configId1' },
[{ configId: 'configId1' }],
'some-string'
);
});
}));
it('emits callbackinternal no matter which flow it is', waitForAsync(() => { expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith(
const callbackSpy = spyOn( { configId: 'configId1' },
[{ configId: 'configId1' }],
'some-string'
);
});
it('emits callbackinternal no matter which flow it is', async () => {
const callbackSpy = vi.spyOn(
(callbackService as any).stsCallbackInternal$, (callbackService as any).stsCallbackInternal$,
'next' 'next'
); );
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const authenticatedCallbackWithCodeSpy = spyOn( const authenticatedCallbackWithCodeSpy = vi
codeFlowCallbackService, .spyOn(codeFlowCallbackService, 'authenticatedCallbackWithCode')
'authenticatedCallbackWithCode' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext));
callbackService await lastValueFrom(
.handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [ callbackService.handleCallbackAndFireEvents(
'anyUrl',
{ configId: 'configId1' }, { configId: 'configId1' },
]) [{ configId: 'configId1' }]
.subscribe(() => { )
expect(authenticatedCallbackWithCodeSpy).toHaveBeenCalledOnceWith( );
'anyUrl',
{ configId: 'configId1' }, expect(authenticatedCallbackWithCodeSpy).toHaveBeenCalledExactlyOnceWith(
[{ configId: 'configId1' }] 'anyUrl',
); { configId: 'configId1' },
expect(callbackSpy).toHaveBeenCalled(); [{ configId: 'configId1' }]
}); );
})); expect(callbackSpy).toHaveBeenCalled();
});
}); });
}); });

View File

@ -1,8 +1,8 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
import { UrlService } from '../utils/url/url.service'; import { UrlService } from '../utils/url/url.service';
import { CodeFlowCallbackService } from './code-flow-callback.service'; import { CodeFlowCallbackService } from './code-flow-callback.service';

View File

@ -1,11 +1,11 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed, mockRouterProvider } from '@/testing';
import { Router } from '@angular/router'; import { AbstractRouter } from 'oidc-client-rx';
import { RouterTestingModule } from '@angular/router/testing';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { FlowsService } from '../flows/flows.service'; import { FlowsService } from '../flows/flows.service';
import { mockProvider } from '../testing/mock';
import { CodeFlowCallbackService } from './code-flow-callback.service'; import { CodeFlowCallbackService } from './code-flow-callback.service';
import { IntervalService } from './interval.service'; import { IntervalService } from './interval.service';
@ -14,26 +14,24 @@ describe('CodeFlowCallbackService ', () => {
let intervalService: IntervalService; let intervalService: IntervalService;
let flowsService: FlowsService; let flowsService: FlowsService;
let flowsDataService: FlowsDataService; let flowsDataService: FlowsDataService;
let router: Router; let router: AbstractRouter;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [RouterTestingModule], imports: [],
providers: [ providers: [
mockRouterProvider(),
CodeFlowCallbackService, CodeFlowCallbackService,
mockProvider(FlowsService), mockProvider(FlowsService),
mockProvider(FlowsDataService), mockProvider(FlowsDataService),
mockProvider(IntervalService), mockProvider(IntervalService),
], ],
}); });
});
beforeEach(() => {
codeFlowCallbackService = TestBed.inject(CodeFlowCallbackService); codeFlowCallbackService = TestBed.inject(CodeFlowCallbackService);
intervalService = TestBed.inject(IntervalService); intervalService = TestBed.inject(IntervalService);
flowsDataService = TestBed.inject(FlowsDataService); flowsDataService = TestBed.inject(FlowsDataService);
flowsService = TestBed.inject(FlowsService); flowsService = TestBed.inject(FlowsService);
router = TestBed.inject(Router); router = TestBed.inject(AbstractRouter);
}); });
it('should create', () => { it('should create', () => {
@ -42,11 +40,10 @@ describe('CodeFlowCallbackService ', () => {
describe('authenticatedCallbackWithCode', () => { describe('authenticatedCallbackWithCode', () => {
it('calls flowsService.processCodeFlowCallback with correct url', () => { it('calls flowsService.processCodeFlowCallback with correct url', () => {
const spy = spyOn( const spy = vi
flowsService, .spyOn(flowsService, 'processCodeFlowCallback')
'processCodeFlowCallback' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext)); //spyOn(configurationProvider, 'getOpenIDConfiguration').mockReturnValue({ triggerAuthorizationResultEvent: true });
//spyOn(configurationProvider, 'getOpenIDConfiguration').and.returnValue({ triggerAuthorizationResultEvent: true });
const config = { const config = {
configId: 'configId1', configId: 'configId1',
@ -58,10 +55,12 @@ describe('CodeFlowCallbackService ', () => {
config, config,
[config] [config]
); );
expect(spy).toHaveBeenCalledOnceWith('some-url1', config, [config]); expect(spy).toHaveBeenCalledExactlyOnceWith('some-url1', config, [
config,
]);
}); });
it('does only call resetCodeFlowInProgress if triggerAuthorizationResultEvent is true and isRenewProcess is true', waitForAsync(() => { it('does only call resetCodeFlowInProgress if triggerAuthorizationResultEvent is true and isRenewProcess is true', async () => {
const callbackContext = { const callbackContext = {
code: '', code: '',
refreshToken: '', refreshToken: '',
@ -73,12 +72,14 @@ describe('CodeFlowCallbackService ', () => {
validationResult: null, validationResult: null,
existingIdToken: '', existingIdToken: '',
}; };
const spy = spyOn( const spy = vi
flowsService, .spyOn(flowsService, 'processCodeFlowCallback')
'processCodeFlowCallback' .mockReturnValue(of(callbackContext));
).and.returnValue(of(callbackContext)); const flowsDataSpy = vi.spyOn(
const flowsDataSpy = spyOn(flowsDataService, 'resetCodeFlowInProgress'); flowsDataService,
const routerSpy = spyOn(router, 'navigateByUrl'); 'resetCodeFlowInProgress'
);
const routerSpy = vi.spyOn(router, 'navigateByUrl');
const config = { const config = {
configId: 'configId1', configId: 'configId1',
triggerAuthorizationResultEvent: true, triggerAuthorizationResultEvent: true,
@ -87,13 +88,15 @@ describe('CodeFlowCallbackService ', () => {
codeFlowCallbackService codeFlowCallbackService
.authenticatedCallbackWithCode('some-url2', config, [config]) .authenticatedCallbackWithCode('some-url2', config, [config])
.subscribe(() => { .subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith('some-url2', config, [config]); expect(spy).toHaveBeenCalledExactlyOnceWith('some-url2', config, [
config,
]);
expect(routerSpy).not.toHaveBeenCalled(); expect(routerSpy).not.toHaveBeenCalled();
expect(flowsDataSpy).toHaveBeenCalled(); expect(flowsDataSpy).toHaveBeenCalled();
}); });
})); });
it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', waitForAsync(() => { it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => {
const callbackContext = { const callbackContext = {
code: '', code: '',
refreshToken: '', refreshToken: '',
@ -105,12 +108,14 @@ describe('CodeFlowCallbackService ', () => {
validationResult: null, validationResult: null,
existingIdToken: '', existingIdToken: '',
}; };
const spy = spyOn( const spy = vi
flowsService, .spyOn(flowsService, 'processCodeFlowCallback')
'processCodeFlowCallback' .mockReturnValue(of(callbackContext));
).and.returnValue(of(callbackContext)); const flowsDataSpy = vi.spyOn(
const flowsDataSpy = spyOn(flowsDataService, 'resetCodeFlowInProgress'); flowsDataService,
const routerSpy = spyOn(router, 'navigateByUrl'); 'resetCodeFlowInProgress'
);
const routerSpy = vi.spyOn(router, 'navigateByUrl');
const config = { const config = {
configId: 'configId1', configId: 'configId1',
triggerAuthorizationResultEvent: false, triggerAuthorizationResultEvent: false,
@ -120,25 +125,27 @@ describe('CodeFlowCallbackService ', () => {
codeFlowCallbackService codeFlowCallbackService
.authenticatedCallbackWithCode('some-url3', config, [config]) .authenticatedCallbackWithCode('some-url3', config, [config])
.subscribe(() => { .subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith('some-url3', config, [config]); expect(spy).toHaveBeenCalledExactlyOnceWith('some-url3', config, [
expect(routerSpy).toHaveBeenCalledOnceWith('postLoginRoute'); config,
]);
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
expect(flowsDataSpy).toHaveBeenCalled(); expect(flowsDataSpy).toHaveBeenCalled();
}); });
})); });
it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', waitForAsync(() => { it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', async () => {
spyOn(flowsService, 'processCodeFlowCallback').and.returnValue( vi.spyOn(flowsService, 'processCodeFlowCallback').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
const resetSilentRenewRunningSpy = spyOn( const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService, flowsDataService,
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
const resetCodeFlowInProgressSpy = spyOn( const resetCodeFlowInProgressSpy = vi.spyOn(
flowsDataService, flowsDataService,
'resetCodeFlowInProgress' 'resetCodeFlowInProgress'
); );
const stopPeriodicallTokenCheckSpy = spyOn( const stopPeriodicallTokenCheckSpy = vi.spyOn(
intervalService, intervalService,
'stopPeriodicTokenCheck' 'stopPeriodicTokenCheck'
); );
@ -159,23 +166,23 @@ describe('CodeFlowCallbackService ', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it(`navigates to unauthorizedRoute in case of error and in case of error and it(`navigates to unauthorizedRoute in case of error and in case of error and
triggerAuthorizationResultEvent is false`, waitForAsync(() => { triggerAuthorizationResultEvent is false`, async () => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn(flowsService, 'processCodeFlowCallback').and.returnValue( vi.spyOn(flowsService, 'processCodeFlowCallback').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
const resetSilentRenewRunningSpy = spyOn( const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService, flowsDataService,
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
const stopPeriodicallTokenCheckSpy = spyOn( const stopPeriodicallTokenCheckSpy = vi.spyOn(
intervalService, intervalService,
'stopPeriodicTokenCheck' 'stopPeriodicTokenCheck'
); );
const routerSpy = spyOn(router, 'navigateByUrl'); const routerSpy = vi.spyOn(router, 'navigateByUrl');
const config = { const config = {
configId: 'configId1', configId: 'configId1',
@ -190,9 +197,11 @@ describe('CodeFlowCallbackService ', () => {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(routerSpy).toHaveBeenCalledOnceWith('unauthorizedRoute'); expect(routerSpy).toHaveBeenCalledExactlyOnceWith(
'unauthorizedRoute'
);
}, },
}); });
})); });
}); });
}); });

View File

@ -1,9 +1,9 @@
import { inject, Injectable } from 'injection-js';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Observable, throwError } from 'rxjs'; import { Injectable, inject } from 'injection-js';
import { type Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators'; import { catchError, tap } from 'rxjs/operators';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { FlowsService } from '../flows/flows.service'; import { FlowsService } from '../flows/flows.service';
import { IntervalService } from './interval.service'; import { IntervalService } from './interval.service';

View File

@ -1,11 +1,11 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed, mockRouterProvider } from '@/testing';
import { Router } from '@angular/router'; import { AbstractRouter } from 'oidc-client-rx';
import { RouterTestingModule } from '@angular/router/testing';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { FlowsService } from '../flows/flows.service'; import { FlowsService } from '../flows/flows.service';
import { mockProvider } from '../testing/mock';
import { ImplicitFlowCallbackService } from './implicit-flow-callback.service'; import { ImplicitFlowCallbackService } from './implicit-flow-callback.service';
import { IntervalService } from './interval.service'; import { IntervalService } from './interval.service';
@ -14,25 +14,23 @@ describe('ImplicitFlowCallbackService ', () => {
let intervalService: IntervalService; let intervalService: IntervalService;
let flowsService: FlowsService; let flowsService: FlowsService;
let flowsDataService: FlowsDataService; let flowsDataService: FlowsDataService;
let router: Router; let router: AbstractRouter;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [RouterTestingModule], imports: [],
providers: [ providers: [
mockRouterProvider(),
mockProvider(FlowsService), mockProvider(FlowsService),
mockProvider(FlowsDataService), mockProvider(FlowsDataService),
mockProvider(IntervalService), mockProvider(IntervalService),
], ],
}); });
});
beforeEach(() => {
implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService); implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService);
intervalService = TestBed.inject(IntervalService); intervalService = TestBed.inject(IntervalService);
flowsDataService = TestBed.inject(FlowsDataService); flowsDataService = TestBed.inject(FlowsDataService);
flowsService = TestBed.inject(FlowsService); flowsService = TestBed.inject(FlowsService);
router = TestBed.inject(Router); router = TestBed.inject(AbstractRouter);
}); });
it('should create', () => { it('should create', () => {
@ -41,10 +39,9 @@ describe('ImplicitFlowCallbackService ', () => {
describe('authorizedImplicitFlowCallback', () => { describe('authorizedImplicitFlowCallback', () => {
it('calls flowsService.processImplicitFlowCallback with hash if given', () => { it('calls flowsService.processImplicitFlowCallback with hash if given', () => {
const spy = spyOn( const spy = vi
flowsService, .spyOn(flowsService, 'processImplicitFlowCallback')
'processImplicitFlowCallback' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext));
const config = { const config = {
configId: 'configId1', configId: 'configId1',
triggerAuthorizationResultEvent: true, triggerAuthorizationResultEvent: true,
@ -56,10 +53,14 @@ describe('ImplicitFlowCallbackService ', () => {
'some-hash' 'some-hash'
); );
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash'); expect(spy).toHaveBeenCalledExactlyOnceWith(
config,
[config],
'some-hash'
);
}); });
it('does nothing if triggerAuthorizationResultEvent is true and isRenewProcess is true', waitForAsync(() => { it('does nothing if triggerAuthorizationResultEvent is true and isRenewProcess is true', async () => {
const callbackContext = { const callbackContext = {
code: '', code: '',
refreshToken: '', refreshToken: '',
@ -71,11 +72,10 @@ describe('ImplicitFlowCallbackService ', () => {
validationResult: null, validationResult: null,
existingIdToken: '', existingIdToken: '',
}; };
const spy = spyOn( const spy = vi
flowsService, .spyOn(flowsService, 'processImplicitFlowCallback')
'processImplicitFlowCallback' .mockReturnValue(of(callbackContext));
).and.returnValue(of(callbackContext)); const routerSpy = vi.spyOn(router, 'navigateByUrl');
const routerSpy = spyOn(router, 'navigateByUrl');
const config = { const config = {
configId: 'configId1', configId: 'configId1',
triggerAuthorizationResultEvent: true, triggerAuthorizationResultEvent: true,
@ -84,12 +84,16 @@ describe('ImplicitFlowCallbackService ', () => {
implicitFlowCallbackService implicitFlowCallbackService
.authenticatedImplicitFlowCallback(config, [config], 'some-hash') .authenticatedImplicitFlowCallback(config, [config], 'some-hash')
.subscribe(() => { .subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash'); expect(spy).toHaveBeenCalledExactlyOnceWith(
config,
[config],
'some-hash'
);
expect(routerSpy).not.toHaveBeenCalled(); expect(routerSpy).not.toHaveBeenCalled();
}); });
})); });
it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', waitForAsync(() => { it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => {
const callbackContext = { const callbackContext = {
code: '', code: '',
refreshToken: '', refreshToken: '',
@ -101,11 +105,10 @@ describe('ImplicitFlowCallbackService ', () => {
validationResult: null, validationResult: null,
existingIdToken: '', existingIdToken: '',
}; };
const spy = spyOn( const spy = vi
flowsService, .spyOn(flowsService, 'processImplicitFlowCallback')
'processImplicitFlowCallback' .mockReturnValue(of(callbackContext));
).and.returnValue(of(callbackContext)); const routerSpy = vi.spyOn(router, 'navigateByUrl');
const routerSpy = spyOn(router, 'navigateByUrl');
const config = { const config = {
configId: 'configId1', configId: 'configId1',
triggerAuthorizationResultEvent: false, triggerAuthorizationResultEvent: false,
@ -115,20 +118,24 @@ describe('ImplicitFlowCallbackService ', () => {
implicitFlowCallbackService implicitFlowCallbackService
.authenticatedImplicitFlowCallback(config, [config], 'some-hash') .authenticatedImplicitFlowCallback(config, [config], 'some-hash')
.subscribe(() => { .subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash'); expect(spy).toHaveBeenCalledExactlyOnceWith(
expect(routerSpy).toHaveBeenCalledOnceWith('postLoginRoute'); config,
[config],
'some-hash'
);
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
}); });
})); });
it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', waitForAsync(() => { it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', async () => {
spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue( vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
const resetSilentRenewRunningSpy = spyOn( const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService, flowsDataService,
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
const stopPeriodicallyTokenCheckSpy = spyOn( const stopPeriodicallyTokenCheckSpy = vi.spyOn(
intervalService, intervalService,
'stopPeriodicTokenCheck' 'stopPeriodicTokenCheck'
); );
@ -147,23 +154,23 @@ describe('ImplicitFlowCallbackService ', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it(`navigates to unauthorizedRoute in case of error and in case of error and it(`navigates to unauthorizedRoute in case of error and in case of error and
triggerAuthorizationResultEvent is false`, waitForAsync(() => { triggerAuthorizationResultEvent is false`, async () => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue( vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
const resetSilentRenewRunningSpy = spyOn( const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService, flowsDataService,
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
const stopPeriodicallTokenCheckSpy = spyOn( const stopPeriodicallTokenCheckSpy = vi.spyOn(
intervalService, intervalService,
'stopPeriodicTokenCheck' 'stopPeriodicTokenCheck'
); );
const routerSpy = spyOn(router, 'navigateByUrl'); const routerSpy = vi.spyOn(router, 'navigateByUrl');
const config = { const config = {
configId: 'configId1', configId: 'configId1',
triggerAuthorizationResultEvent: false, triggerAuthorizationResultEvent: false,
@ -177,9 +184,11 @@ describe('ImplicitFlowCallbackService ', () => {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(routerSpy).toHaveBeenCalledOnceWith('unauthorizedRoute'); expect(routerSpy).toHaveBeenCalledExactlyOnceWith(
'unauthorizedRoute'
);
}, },
}); });
})); });
}); });
}); });

View File

@ -1,9 +1,9 @@
import { inject, Injectable } from 'injection-js';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Observable, throwError } from 'rxjs'; import { Injectable, inject } from 'injection-js';
import { type Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators'; import { catchError, tap } from 'rxjs/operators';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { FlowsService } from '../flows/flows.service'; import { FlowsService } from '../flows/flows.service';
import { IntervalService } from './interval.service'; import { IntervalService } from './interval.service';

View File

@ -1,5 +1,6 @@
import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { TestBed, fakeAsync, tick } from '@/testing';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { vi } from 'vitest';
import { IntervalService } from './interval.service'; import { IntervalService } from './interval.service';
describe('IntervalService', () => { describe('IntervalService', () => {
@ -31,7 +32,7 @@ describe('IntervalService', () => {
describe('stopPeriodicTokenCheck', () => { describe('stopPeriodicTokenCheck', () => {
it('calls unsubscribe and sets to null', () => { it('calls unsubscribe and sets to null', () => {
intervalService.runTokenValidationRunning = new Subscription(); intervalService.runTokenValidationRunning = new Subscription();
const spy = spyOn( const spy = vi.spyOn(
intervalService.runTokenValidationRunning, intervalService.runTokenValidationRunning,
'unsubscribe' 'unsubscribe'
); );
@ -44,7 +45,7 @@ describe('IntervalService', () => {
it('does nothing if `runTokenValidationRunning` is null', () => { it('does nothing if `runTokenValidationRunning` is null', () => {
intervalService.runTokenValidationRunning = new Subscription(); intervalService.runTokenValidationRunning = new Subscription();
const spy = spyOn( const spy = vi.spyOn(
intervalService.runTokenValidationRunning, intervalService.runTokenValidationRunning,
'unsubscribe' 'unsubscribe'
); );
@ -57,7 +58,7 @@ describe('IntervalService', () => {
}); });
describe('startPeriodicTokenCheck', () => { describe('startPeriodicTokenCheck', () => {
it('starts check after correct milliseconds', fakeAsync(() => { it('starts check after correct milliseconds', async () => {
const periodicCheck = intervalService.startPeriodicTokenCheck(0.5); const periodicCheck = intervalService.startPeriodicTokenCheck(0.5);
const spy = jasmine.createSpy(); const spy = jasmine.createSpy();
const sub = periodicCheck.subscribe(() => { const sub = periodicCheck.subscribe(() => {
@ -71,6 +72,6 @@ describe('IntervalService', () => {
expect(spy).toHaveBeenCalledTimes(2); expect(spy).toHaveBeenCalledTimes(2);
sub.unsubscribe(); sub.unsubscribe();
})); });
}); });
}); });

View File

@ -1,11 +1,9 @@
import { Injectable, NgZone, inject } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, Subscription } from 'rxjs'; import { type Observable, type Subscription, interval } from 'rxjs';
import { DOCUMENT } from '../../dom'; import { DOCUMENT } from '../dom';
@Injectable() @Injectable()
export class IntervalService { export class IntervalService {
private readonly zone = inject(NgZone);
private readonly document = inject(DOCUMENT); private readonly document = inject(DOCUMENT);
runTokenValidationRunning: Subscription | null = null; runTokenValidationRunning: Subscription | null = null;
@ -24,19 +22,6 @@ export class IntervalService {
startPeriodicTokenCheck(repeatAfterSeconds: number): Observable<unknown> { startPeriodicTokenCheck(repeatAfterSeconds: number): Observable<unknown> {
const millisecondsDelayBetweenTokenCheck = repeatAfterSeconds * 1000; const millisecondsDelayBetweenTokenCheck = repeatAfterSeconds * 1000;
return new Observable((subscriber) => { return interval(millisecondsDelayBetweenTokenCheck);
let intervalId: number | undefined;
this.zone.runOutsideAngular(() => {
intervalId = this.document?.defaultView?.setInterval(
() => this.zone.run(() => subscriber.next()),
millisecondsDelayBetweenTokenCheck
);
});
return (): void => {
clearInterval(intervalId);
};
});
} }
} }

View File

@ -1,10 +1,10 @@
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of, throwError } from 'rxjs'; import { lastValueFrom, of, throwError } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { ConfigurationService } from '../config/config.service'; import { ConfigurationService } from '../config/config.service';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service'; import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
@ -12,6 +12,7 @@ import { LoggerService } from '../logging/logger.service';
import { EventTypes } from '../public-events/event-types'; import { EventTypes } from '../public-events/event-types';
import { PublicEventsService } from '../public-events/public-events.service'; import { PublicEventsService } from '../public-events/public-events.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockProvider } from '../testing/mock';
import { UserService } from '../user-data/user.service'; import { UserService } from '../user-data/user.service';
import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
import { IntervalService } from './interval.service'; import { IntervalService } from './interval.service';
@ -49,9 +50,6 @@ describe('PeriodicallyTokenCheckService', () => {
mockProvider(ConfigurationService), mockProvider(ConfigurationService),
], ],
}); });
});
beforeEach(() => {
periodicallyTokenCheckService = TestBed.inject( periodicallyTokenCheckService = TestBed.inject(
PeriodicallyTokenCheckService PeriodicallyTokenCheckService
); );
@ -68,11 +66,14 @@ describe('PeriodicallyTokenCheckService', () => {
publicEventsService = TestBed.inject(PublicEventsService); publicEventsService = TestBed.inject(PublicEventsService);
configurationService = TestBed.inject(ConfigurationService); configurationService = TestBed.inject(ConfigurationService);
spyOn(intervalService, 'startPeriodicTokenCheck').and.returnValue(of(null)); vi.spyOn(intervalService, 'startPeriodicTokenCheck').mockReturnValue(
of(null)
);
}); });
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
afterEach(() => { afterEach(() => {
if (!!intervalService.runTokenValidationRunning?.unsubscribe) { if (intervalService.runTokenValidationRunning?.unsubscribe) {
intervalService.runTokenValidationRunning.unsubscribe(); intervalService.runTokenValidationRunning.unsubscribe();
intervalService.runTokenValidationRunning = null; intervalService.runTokenValidationRunning = null;
} }
@ -83,164 +84,172 @@ describe('PeriodicallyTokenCheckService', () => {
}); });
describe('startTokenValidationPeriodically', () => { describe('startTokenValidationPeriodically', () => {
it('returns if no config has silentrenew enabled', waitForAsync(() => { it('returns if no config has silentrenew enabled', async () => {
const configs = [ const configs = [
{ silentRenew: false, configId: 'configId1' }, { silentRenew: false, configId: 'configId1' },
{ silentRenew: false, configId: 'configId2' }, { silentRenew: false, configId: 'configId2' },
]; ];
const result = const result = await lastValueFrom(
periodicallyTokenCheckService.startTokenValidationPeriodically( periodicallyTokenCheckService.startTokenValidationPeriodically(
configs, configs,
configs[0] configs[0]!
); )
);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
})); });
it('returns if runTokenValidationRunning', waitForAsync(() => { it('returns if runTokenValidationRunning', async () => {
const configs = [{ silentRenew: true, configId: 'configId1' }]; const configs = [{ silentRenew: true, configId: 'configId1' }];
spyOn(intervalService, 'isTokenValidationRunning').and.returnValue(true); vi.spyOn(intervalService, 'isTokenValidationRunning').mockReturnValue(
true
);
const result = const result = await lastValueFrom(
periodicallyTokenCheckService.startTokenValidationPeriodically( periodicallyTokenCheckService.startTokenValidationPeriodically(
configs, configs,
configs[0] configs[0]!
); )
);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
})); });
it('interval calls resetSilentRenewRunning when current flow is CodeFlowWithRefreshTokens', fakeAsync(() => { it('interval calls resetSilentRenewRunning when current flow is CodeFlowWithRefreshTokens', async () => {
const configs = [ const configs = [
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 }, { silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
]; ];
spyOn( vi.spyOn(
periodicallyTokenCheckService as any, periodicallyTokenCheckService as any,
'shouldStartPeriodicallyCheckForConfig' 'shouldStartPeriodicallyCheckForConfig'
).and.returnValue(true); ).mockReturnValue(true);
const isCurrentFlowCodeFlowWithRefreshTokensSpy = spyOn( const isCurrentFlowCodeFlowWithRefreshTokensSpy = vi
flowHelper, .spyOn(flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens')
'isCurrentFlowCodeFlowWithRefreshTokens' .mockReturnValue(true);
).and.returnValue(true); const resetSilentRenewRunningSpy = vi.spyOn(
const resetSilentRenewRunningSpy = spyOn(
flowsDataService, flowsDataService,
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
spyOn( vi.spyOn(
refreshSessionRefreshTokenService, refreshSessionRefreshTokenService,
'refreshSessionWithRefreshTokens' 'refreshSessionWithRefreshTokens'
).and.returnValue(of({} as CallbackContext)); ).mockReturnValue(of({} as CallbackContext));
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
of(configs[0]) of(configs[0]!)
); );
periodicallyTokenCheckService.startTokenValidationPeriodically( periodicallyTokenCheckService.startTokenValidationPeriodically(
configs, configs,
configs[0] configs[0]!
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
intervalService.runTokenValidationRunning?.unsubscribe(); intervalService.runTokenValidationRunning?.unsubscribe();
intervalService.runTokenValidationRunning = null; intervalService.runTokenValidationRunning = null;
expect(isCurrentFlowCodeFlowWithRefreshTokensSpy).toHaveBeenCalled(); expect(isCurrentFlowCodeFlowWithRefreshTokensSpy).toHaveBeenCalled();
expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
})); });
it('interval calls resetSilentRenewRunning in case of error when current flow is CodeFlowWithRefreshTokens', fakeAsync(() => { it('interval calls resetSilentRenewRunning in case of error when current flow is CodeFlowWithRefreshTokens', async () => {
const configs = [ const configs = [
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 }, { silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
]; ];
spyOn( vi.spyOn(
periodicallyTokenCheckService as any, periodicallyTokenCheckService as any,
'shouldStartPeriodicallyCheckForConfig' 'shouldStartPeriodicallyCheckForConfig'
).and.returnValue(true); ).mockReturnValue(true);
const resetSilentRenewRunning = spyOn( const resetSilentRenewRunning = vi.spyOn(
flowsDataService, flowsDataService,
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
refreshSessionRefreshTokenService, refreshSessionRefreshTokenService,
'refreshSessionWithRefreshTokens' 'refreshSessionWithRefreshTokens'
).and.returnValue(throwError(() => new Error('error'))); ).mockReturnValue(throwError(() => new Error('error')));
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
of(configs[0]) of(configs[0]!)
); );
periodicallyTokenCheckService.startTokenValidationPeriodically( periodicallyTokenCheckService.startTokenValidationPeriodically(
configs, configs,
configs[0] configs[0]!
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
expect( expect(
periodicallyTokenCheckService.startTokenValidationPeriodically periodicallyTokenCheckService.startTokenValidationPeriodically
).toThrowError(); ).toThrowError();
expect(resetSilentRenewRunning).toHaveBeenCalledOnceWith(configs[0]); expect(resetSilentRenewRunning).toHaveBeenCalledExactlyOnceWith(
})); configs[0]
);
});
it('interval throws silent renew failed event with data in case of an error', fakeAsync(() => { it('interval throws silent renew failed event with data in case of an error', async () => {
const configs = [ const configs = [
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 }, { silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
]; ];
spyOn( vi.spyOn(
periodicallyTokenCheckService as any, periodicallyTokenCheckService as any,
'shouldStartPeriodicallyCheckForConfig' 'shouldStartPeriodicallyCheckForConfig'
).and.returnValue(true); ).mockReturnValue(true);
spyOn(flowsDataService, 'resetSilentRenewRunning'); vi.spyOn(flowsDataService, 'resetSilentRenewRunning');
const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent'); const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent');
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
refreshSessionRefreshTokenService, refreshSessionRefreshTokenService,
'refreshSessionWithRefreshTokens' 'refreshSessionWithRefreshTokens'
).and.returnValue(throwError(() => new Error('error'))); ).mockReturnValue(throwError(() => new Error('error')));
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
of(configs[0]) of(configs[0]!)
); );
periodicallyTokenCheckService.startTokenValidationPeriodically( periodicallyTokenCheckService.startTokenValidationPeriodically(
configs, configs,
configs[0] configs[0]!
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
expect( expect(
periodicallyTokenCheckService.startTokenValidationPeriodically periodicallyTokenCheckService.startTokenValidationPeriodically
).toThrowError(); ).toThrowError();
expect(publicEventsServiceSpy.calls.allArgs()).toEqual([ expect(publicEventsServiceSpy).toBeCalledWith([
[EventTypes.SilentRenewStarted], [EventTypes.SilentRenewStarted],
[EventTypes.SilentRenewFailed, new Error('error')], [EventTypes.SilentRenewFailed, new Error('error')],
]); ]);
})); });
it('calls resetAuthorizationData and returns if no silent renew is configured', fakeAsync(() => { it('calls resetAuthorizationData and returns if no silent renew is configured', async () => {
const configs = [ const configs = [
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 }, { silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
]; ];
spyOn( vi.spyOn(
periodicallyTokenCheckService as any, periodicallyTokenCheckService as any,
'shouldStartPeriodicallyCheckForConfig' 'shouldStartPeriodicallyCheckForConfig'
).and.returnValue(true); ).mockReturnValue(true);
const configSpy = spyOn(configurationService, 'getOpenIDConfiguration'); const configSpy = vi.spyOn(
configurationService,
'getOpenIDConfiguration'
);
const configWithoutSilentRenew = { const configWithoutSilentRenew = {
silentRenew: false, silentRenew: false,
configId: 'configId1', configId: 'configId1',
@ -248,68 +257,70 @@ describe('PeriodicallyTokenCheckService', () => {
}; };
const configWithoutSilentRenew$ = of(configWithoutSilentRenew); const configWithoutSilentRenew$ = of(configWithoutSilentRenew);
configSpy.and.returnValue(configWithoutSilentRenew$); configSpy.mockReturnValue(configWithoutSilentRenew$);
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
periodicallyTokenCheckService.startTokenValidationPeriodically( periodicallyTokenCheckService.startTokenValidationPeriodically(
configs, configs,
configs[0] configs[0]!
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
intervalService.runTokenValidationRunning?.unsubscribe(); intervalService.runTokenValidationRunning?.unsubscribe();
intervalService.runTokenValidationRunning = null; intervalService.runTokenValidationRunning = null;
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect(resetAuthorizationDataSpy).toHaveBeenCalledOnceWith( expect(resetAuthorizationDataSpy).toHaveBeenCalledExactlyOnceWith(
configWithoutSilentRenew, configWithoutSilentRenew,
configs configs
); );
})); });
it('calls refreshSessionWithRefreshTokens if current flow is Code flow with refresh tokens', fakeAsync(() => { it('calls refreshSessionWithRefreshTokens if current flow is Code flow with refresh tokens', async () => {
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
periodicallyTokenCheckService as any, periodicallyTokenCheckService as any,
'shouldStartPeriodicallyCheckForConfig' 'shouldStartPeriodicallyCheckForConfig'
).and.returnValue(true); ).mockReturnValue(true);
spyOn(storagePersistenceService, 'read').and.returnValue({}); vi.spyOn(storagePersistenceService, 'read').mockReturnValue({});
const configs = [ const configs = [
{ configId: 'configId1', silentRenew: true, tokenRefreshInSeconds: 1 }, { configId: 'configId1', silentRenew: true, tokenRefreshInSeconds: 1 },
]; ];
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
of(configs[0] as OpenIdConfiguration) of(configs[0] as OpenIdConfiguration)
); );
const refreshSessionWithRefreshTokensSpy = spyOn( const refreshSessionWithRefreshTokensSpy = vi
refreshSessionRefreshTokenService, .spyOn(
'refreshSessionWithRefreshTokens' refreshSessionRefreshTokenService,
).and.returnValue(of({} as CallbackContext)); 'refreshSessionWithRefreshTokens'
)
.mockReturnValue(of({} as CallbackContext));
periodicallyTokenCheckService.startTokenValidationPeriodically( periodicallyTokenCheckService.startTokenValidationPeriodically(
configs, configs,
configs[0] configs[0]!
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
intervalService.runTokenValidationRunning?.unsubscribe(); intervalService.runTokenValidationRunning?.unsubscribe();
intervalService.runTokenValidationRunning = null; intervalService.runTokenValidationRunning = null;
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled(); expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
})); });
}); });
describe('shouldStartPeriodicallyCheckForConfig', () => { describe('shouldStartPeriodicallyCheckForConfig', () => {
it('returns false when there is no IdToken', () => { it('returns false when there is no IdToken', () => {
spyOn(authStateService, 'getIdToken').and.returnValue(''); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('');
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn(userService, 'getUserDataFromStore').and.returnValue( vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
'some-userdata' 'some-userdata'
); );
@ -317,13 +328,13 @@ describe('PeriodicallyTokenCheckService', () => {
periodicallyTokenCheckService as any periodicallyTokenCheckService as any
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('returns false when silent renew is running', () => { it('returns false when silent renew is running', () => {
spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
spyOn(userService, 'getUserDataFromStore').and.returnValue( vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
'some-userdata' 'some-userdata'
); );
@ -331,14 +342,14 @@ describe('PeriodicallyTokenCheckService', () => {
periodicallyTokenCheckService as any periodicallyTokenCheckService as any
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('returns false when code flow is in progress', () => { it('returns false when code flow is in progress', () => {
spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn(flowsDataService, 'isCodeFlowInProgress').and.returnValue(true); vi.spyOn(flowsDataService, 'isCodeFlowInProgress').mockReturnValue(true);
spyOn(userService, 'getUserDataFromStore').and.returnValue( vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
'some-userdata' 'some-userdata'
); );
@ -346,87 +357,87 @@ describe('PeriodicallyTokenCheckService', () => {
periodicallyTokenCheckService as any periodicallyTokenCheckService as any
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('returns false when there is no userdata from the store', () => { it('returns false when there is no userdata from the store', () => {
spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
spyOn(userService, 'getUserDataFromStore').and.returnValue(null); vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(null);
const result = ( const result = (
periodicallyTokenCheckService as any periodicallyTokenCheckService as any
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('returns true when there is userDataFromStore, silentrenew is not running and there is an idtoken', () => { it('returns true when there is userDataFromStore, silentrenew is not running and there is an idtoken', () => {
spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn(userService, 'getUserDataFromStore').and.returnValue( vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
'some-userdata' 'some-userdata'
); );
spyOn( vi.spyOn(
authStateService, authStateService,
'hasIdTokenExpiredAndRenewCheckIsEnabled' 'hasIdTokenExpiredAndRenewCheckIsEnabled'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authStateService, authStateService,
'hasAccessTokenExpiredIfExpiryExists' 'hasAccessTokenExpiredIfExpiryExists'
).and.returnValue(true); ).mockReturnValue(true);
const result = ( const result = (
periodicallyTokenCheckService as any periodicallyTokenCheckService as any
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
expect(result).toBeTrue(); expect(result).toBeTruthy();
}); });
it('returns false if tokens are not expired', () => { it('returns false if tokens are not expired', () => {
spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn(userService, 'getUserDataFromStore').and.returnValue( vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
'some-userdata' 'some-userdata'
); );
spyOn( vi.spyOn(
authStateService, authStateService,
'hasIdTokenExpiredAndRenewCheckIsEnabled' 'hasIdTokenExpiredAndRenewCheckIsEnabled'
).and.returnValue(false); ).mockReturnValue(false);
spyOn( vi.spyOn(
authStateService, authStateService,
'hasAccessTokenExpiredIfExpiryExists' 'hasAccessTokenExpiredIfExpiryExists'
).and.returnValue(false); ).mockReturnValue(false);
const result = ( const result = (
periodicallyTokenCheckService as any periodicallyTokenCheckService as any
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('returns true if tokens are expired', () => { it('returns true if tokens are expired', () => {
spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn(userService, 'getUserDataFromStore').and.returnValue( vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
'some-userdata' 'some-userdata'
); );
spyOn( vi.spyOn(
authStateService, authStateService,
'hasIdTokenExpiredAndRenewCheckIsEnabled' 'hasIdTokenExpiredAndRenewCheckIsEnabled'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authStateService, authStateService,
'hasAccessTokenExpiredIfExpiryExists' 'hasAccessTokenExpiredIfExpiryExists'
).and.returnValue(true); ).mockReturnValue(true);
const result = ( const result = (
periodicallyTokenCheckService as any periodicallyTokenCheckService as any
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
expect(result).toBeTrue(); expect(result).toBeTruthy();
}); });
}); });
}); });

View File

@ -1,10 +1,10 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { forkJoin, Observable, of, throwError } from 'rxjs'; import { type Observable, forkJoin, of, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators'; import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { ConfigurationService } from '../config/config.service'; import { ConfigurationService } from '../config/config.service';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service'; import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
@ -52,7 +52,7 @@ export class PeriodicallyTokenCheckService {
startTokenValidationPeriodically( startTokenValidationPeriodically(
allConfigs: OpenIdConfiguration[], allConfigs: OpenIdConfiguration[],
currentConfig: OpenIdConfiguration currentConfig: OpenIdConfiguration
): void { ): Observable<void> {
const configsWithSilentRenewEnabled = const configsWithSilentRenewEnabled =
this.getConfigsWithSilentRenewEnabled(allConfigs); this.getConfigsWithSilentRenewEnabled(allConfigs);
@ -75,46 +75,51 @@ export class PeriodicallyTokenCheckService {
[id: string]: Observable<boolean | CallbackContext | null>; [id: string]: Observable<boolean | CallbackContext | null>;
} = {}; } = {};
configsWithSilentRenewEnabled.forEach((config) => { for (const config of configsWithSilentRenewEnabled) {
const identifier = config.configId as string; const identifier = config.configId as string;
const refreshEvent = this.getRefreshEvent(config, allConfigs); const refreshEvent = this.getRefreshEvent(config, allConfigs);
objectWithConfigIdsAndRefreshEvent[identifier] = refreshEvent; objectWithConfigIdsAndRefreshEvent[identifier] = refreshEvent;
}); }
return forkJoin(objectWithConfigIdsAndRefreshEvent); return forkJoin(objectWithConfigIdsAndRefreshEvent);
}) })
); );
this.intervalService.runTokenValidationRunning = periodicallyCheck$ const o$ = periodicallyCheck$.pipe(
.pipe(catchError((error) => throwError(() => new Error(error)))) catchError((error) => throwError(() => new Error(error))),
.subscribe({ map((objectWithConfigIds) => {
next: (objectWithConfigIds) => { for (const [configId, _] of Object.entries(objectWithConfigIds)) {
for (const [configId, _] of Object.entries(objectWithConfigIds)) { this.configurationService
this.configurationService .getOpenIDConfiguration(configId)
.getOpenIDConfiguration(configId) .subscribe((config) => {
.subscribe((config) => { this.loggerService.logDebug(
this.loggerService.logDebug( config,
config, 'silent renew, periodic check finished!'
'silent renew, periodic check finished!' );
);
if ( if (
this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config) this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config)
) { ) {
this.flowsDataService.resetSilentRenewRunning(config); this.flowsDataService.resetSilentRenewRunning(config);
} }
}); });
} }
}, }),
error: (error) => { catchError((error) => {
this.loggerService.logError( this.loggerService.logError(
currentConfig, currentConfig,
'silent renew failed!', 'silent renew failed!',
error error
); );
}, return throwError(() => error);
}); }),
shareReplay(1)
);
this.intervalService.runTokenValidationRunning = o$.subscribe();
return o$;
} }
private getRefreshEvent( private getRefreshEvent(

View File

@ -1,10 +1,11 @@
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsService } from '../flows/flows.service'; import { FlowsService } from '../flows/flows.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { IntervalService } from './interval.service'; import { IntervalService } from './interval.service';
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service'; import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
@ -25,9 +26,6 @@ describe('RefreshSessionRefreshTokenService', () => {
mockProvider(IntervalService), mockProvider(IntervalService),
], ],
}); });
});
beforeEach(() => {
flowsService = TestBed.inject(FlowsService); flowsService = TestBed.inject(FlowsService);
refreshSessionRefreshTokenService = TestBed.inject( refreshSessionRefreshTokenService = TestBed.inject(
RefreshSessionRefreshTokenService RefreshSessionRefreshTokenService
@ -41,10 +39,10 @@ describe('RefreshSessionRefreshTokenService', () => {
}); });
describe('refreshSessionWithRefreshTokens', () => { describe('refreshSessionWithRefreshTokens', () => {
it('calls flowsService.processRefreshToken()', waitForAsync(() => { it('calls flowsService.processRefreshToken()', async () => {
const spy = spyOn(flowsService, 'processRefreshToken').and.returnValue( const spy = vi
of({} as CallbackContext) .spyOn(flowsService, 'processRefreshToken')
); .mockReturnValue(of({} as CallbackContext));
refreshSessionRefreshTokenService refreshSessionRefreshTokenService
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [ .refreshSessionWithRefreshTokens({ configId: 'configId1' }, [
@ -53,13 +51,13 @@ describe('RefreshSessionRefreshTokenService', () => {
.subscribe(() => { .subscribe(() => {
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
}); });
})); });
it('resetAuthorizationData in case of error', waitForAsync(() => { it('resetAuthorizationData in case of error', async () => {
spyOn(flowsService, 'processRefreshToken').and.returnValue( vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
const resetSilentRenewRunningSpy = spyOn( const resetSilentRenewRunningSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
@ -74,13 +72,13 @@ describe('RefreshSessionRefreshTokenService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('finalize with stopPeriodicTokenCheck in case of error', fakeAsync(() => { it('finalize with stopPeriodicTokenCheck in case of error', async () => {
spyOn(flowsService, 'processRefreshToken').and.returnValue( vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
const stopPeriodicallyTokenCheckSpy = spyOn( const stopPeriodicallyTokenCheckSpy = vi.spyOn(
intervalService, intervalService,
'stopPeriodicTokenCheck' 'stopPeriodicTokenCheck'
); );
@ -94,8 +92,8 @@ describe('RefreshSessionRefreshTokenService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
tick(); await vi.advanceTimersByTimeAsync(0);
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled(); expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
})); });
}); });
}); });

View File

@ -1,8 +1,8 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, throwError } from 'rxjs'; import { type Observable, throwError } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators'; import { catchError, finalize } from 'rxjs/operators';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsService } from '../flows/flows.service'; import { FlowsService } from '../flows/flows.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';

View File

@ -1,17 +1,18 @@
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { TestBed, fakeAsync, tick } from '@/testing';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { delay } from 'rxjs/operators'; import { delay } from 'rxjs/operators';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service'; import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
import { SilentRenewService } from '../iframe/silent-renew.service'; import { SilentRenewService } from '../iframe/silent-renew.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { LoginResponse } from '../login/login-response'; import type { LoginResponse } from '../login/login-response';
import { PublicEventsService } from '../public-events/public-events.service'; import { PublicEventsService } from '../public-events/public-events.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockProvider } from '../testing/mock';
import { UserService } from '../user-data/user.service'; import { UserService } from '../user-data/user.service';
import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service'; import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
@ -70,19 +71,19 @@ describe('RefreshSessionService ', () => {
}); });
describe('userForceRefreshSession', () => { describe('userForceRefreshSession', () => {
it('should persist params refresh when extra custom params given and useRefreshToken is true', waitForAsync(() => { it('should persist params refresh when extra custom params given and useRefreshToken is true', async () => {
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
true true
); );
const writeSpy = spyOn(storagePersistenceService, 'write'); const writeSpy = vi.spyOn(storagePersistenceService, 'write');
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -94,26 +95,26 @@ describe('RefreshSessionService ', () => {
const extraCustomParams = { extra: 'custom' }; const extraCustomParams = { extra: 'custom' };
refreshSessionService refreshSessionService
.userForceRefreshSession(allConfigs[0], allConfigs, extraCustomParams) .userForceRefreshSession(allConfigs[0]!, allConfigs, extraCustomParams)
.subscribe(() => { .subscribe(() => {
expect(writeSpy).toHaveBeenCalledOnceWith( expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
'storageCustomParamsRefresh', 'storageCustomParamsRefresh',
extraCustomParams, extraCustomParams,
allConfigs[0] allConfigs[0]
); );
}); });
})); });
it('should persist storageCustomParamsAuthRequest when extra custom params given and useRefreshToken is false', waitForAsync(() => { it('should persist storageCustomParamsAuthRequest when extra custom params given and useRefreshToken is false', async () => {
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
true true
); );
const allConfigs = [ const allConfigs = [
@ -123,31 +124,31 @@ describe('RefreshSessionService ', () => {
silentRenewTimeoutInSeconds: 10, silentRenewTimeoutInSeconds: 10,
}, },
]; ];
const writeSpy = spyOn(storagePersistenceService, 'write'); const writeSpy = vi.spyOn(storagePersistenceService, 'write');
const extraCustomParams = { extra: 'custom' }; const extraCustomParams = { extra: 'custom' };
refreshSessionService refreshSessionService
.userForceRefreshSession(allConfigs[0], allConfigs, extraCustomParams) .userForceRefreshSession(allConfigs[0]!, allConfigs, extraCustomParams)
.subscribe(() => { .subscribe(() => {
expect(writeSpy).toHaveBeenCalledOnceWith( expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
'storageCustomParamsAuthRequest', 'storageCustomParamsAuthRequest',
extraCustomParams, extraCustomParams,
allConfigs[0] allConfigs[0]
); );
}); });
})); });
it('should NOT persist customparams if no customparams are given', waitForAsync(() => { it('should NOT persist customparams if no customparams are given', async () => {
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
true true
); );
const allConfigs = [ const allConfigs = [
@ -157,20 +158,20 @@ describe('RefreshSessionService ', () => {
silentRenewTimeoutInSeconds: 10, silentRenewTimeoutInSeconds: 10,
}, },
]; ];
const writeSpy = spyOn(storagePersistenceService, 'write'); const writeSpy = vi.spyOn(storagePersistenceService, 'write');
refreshSessionService refreshSessionService
.userForceRefreshSession(allConfigs[0], allConfigs) .userForceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe(() => { .subscribe(() => {
expect(writeSpy).not.toHaveBeenCalled(); expect(writeSpy).not.toHaveBeenCalled();
}); });
})); });
it('should call resetSilentRenewRunning in case of an error', waitForAsync(() => { it('should call resetSilentRenewRunning in case of an error', async () => {
spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue( vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
spyOn(flowsDataService, 'resetSilentRenewRunning'); vi.spyOn(flowsDataService, 'resetSilentRenewRunning');
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -180,7 +181,7 @@ describe('RefreshSessionService ', () => {
]; ];
refreshSessionService refreshSessionService
.userForceRefreshSession(allConfigs[0], allConfigs) .userForceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe({ .subscribe({
next: () => { next: () => {
fail('It should not return any result.'); fail('It should not return any result.');
@ -191,16 +192,16 @@ describe('RefreshSessionService ', () => {
complete: () => { complete: () => {
expect( expect(
flowsDataService.resetSilentRenewRunning flowsDataService.resetSilentRenewRunning
).toHaveBeenCalledOnceWith(allConfigs[0]); ).toHaveBeenCalledExactlyOnceWith(allConfigs[0]);
}, },
}); });
})); });
it('should call resetSilentRenewRunning in case of no error', waitForAsync(() => { it('should call resetSilentRenewRunning in case of no error', async () => {
spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue( vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue(
of({} as LoginResponse) of({} as LoginResponse)
); );
spyOn(flowsDataService, 'resetSilentRenewRunning'); vi.spyOn(flowsDataService, 'resetSilentRenewRunning');
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -210,7 +211,7 @@ describe('RefreshSessionService ', () => {
]; ];
refreshSessionService refreshSessionService
.userForceRefreshSession(allConfigs[0], allConfigs) .userForceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe({ .subscribe({
error: () => { error: () => {
fail('It should not return any error.'); fail('It should not return any error.');
@ -218,27 +219,29 @@ describe('RefreshSessionService ', () => {
complete: () => { complete: () => {
expect( expect(
flowsDataService.resetSilentRenewRunning flowsDataService.resetSilentRenewRunning
).toHaveBeenCalledOnceWith(allConfigs[0]); ).toHaveBeenCalledExactlyOnceWith(allConfigs[0]);
}, },
}); });
})); });
}); });
describe('forceRefreshSession', () => { describe('forceRefreshSession', () => {
it('only calls start refresh session and returns idToken and accessToken if auth is true', waitForAsync(() => { it('only calls start refresh session and returns idToken and accessToken if auth is true', async () => {
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
true true
); );
spyOn(authStateService, 'getIdToken').and.returnValue('id-token'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('id-token');
spyOn(authStateService, 'getAccessToken').and.returnValue('access-token'); vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
'access-token'
);
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -247,23 +250,23 @@ describe('RefreshSessionService ', () => {
]; ];
refreshSessionService refreshSessionService
.forceRefreshSession(allConfigs[0], allConfigs) .forceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe((result) => { .subscribe((result) => {
expect(result.idToken).toEqual('id-token'); expect(result.idToken).toEqual('id-token');
expect(result.accessToken).toEqual('access-token'); expect(result.accessToken).toEqual('access-token');
}); });
})); });
it('only calls start refresh session and returns null if auth is false', waitForAsync(() => { it('only calls start refresh session and returns null if auth is false', async () => {
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const allConfigs = [ const allConfigs = [
@ -274,7 +277,7 @@ describe('RefreshSessionService ', () => {
]; ];
refreshSessionService refreshSessionService
.forceRefreshSession(allConfigs[0], allConfigs) .forceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe((result) => { .subscribe((result) => {
expect(result).toEqual({ expect(result).toEqual({
isAuthenticated: false, isAuthenticated: false,
@ -285,24 +288,24 @@ describe('RefreshSessionService ', () => {
configId: 'configId1', configId: 'configId1',
}); });
}); });
})); });
it('calls start refresh session and waits for completed, returns idtoken and accesstoken if auth is true', waitForAsync(() => { it('calls start refresh session and waits for completed, returns idtoken and accesstoken if auth is true', async () => {
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(false); ).mockReturnValue(false);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
true true
); );
spyOnProperty( vi.spyOnProperty(
silentRenewService, silentRenewService,
'refreshSessionWithIFrameCompleted$' 'refreshSessionWithIFrameCompleted$'
).and.returnValue( ).mockReturnValue(
of({ of({
authResult: { authResult: {
id_token: 'some-id_token', id_token: 'some-id_token',
@ -318,29 +321,29 @@ describe('RefreshSessionService ', () => {
]; ];
refreshSessionService refreshSessionService
.forceRefreshSession(allConfigs[0], allConfigs) .forceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe((result) => { .subscribe((result) => {
expect(result.idToken).toBeDefined(); expect(result.idToken).toBeDefined();
expect(result.accessToken).toBeDefined(); expect(result.accessToken).toBeDefined();
}); });
})); });
it('calls start refresh session and waits for completed, returns LoginResponse if auth is false', waitForAsync(() => { it('calls start refresh session and waits for completed, returns LoginResponse if auth is false', async () => {
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(false); ).mockReturnValue(false);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
spyOnProperty( vi.spyOnProperty(
silentRenewService, silentRenewService,
'refreshSessionWithIFrameCompleted$' 'refreshSessionWithIFrameCompleted$'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -349,7 +352,7 @@ describe('RefreshSessionService ', () => {
]; ];
refreshSessionService refreshSessionService
.forceRefreshSession(allConfigs[0], allConfigs) .forceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe((result) => { .subscribe((result) => {
expect(result).toEqual({ expect(result).toEqual({
isAuthenticated: false, isAuthenticated: false,
@ -360,23 +363,23 @@ describe('RefreshSessionService ', () => {
configId: 'configId1', configId: 'configId1',
}); });
}); });
})); });
it('occurs timeout error and retry mechanism exhausted max retry count throws error', fakeAsync(() => { it('occurs timeout error and retry mechanism exhausted max retry count throws error', async () => {
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(false); ).mockReturnValue(false);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOnProperty( vi.spyOnProperty(
silentRenewService, silentRenewService,
'refreshSessionWithIFrameCompleted$' 'refreshSessionWithIFrameCompleted$'
).and.returnValue(of(null).pipe(delay(11000))); ).mockReturnValue(of(null).pipe(delay(11000)));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const allConfigs = [ const allConfigs = [
@ -386,14 +389,14 @@ describe('RefreshSessionService ', () => {
}, },
]; ];
const resetSilentRenewRunningSpy = spyOn( const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService, flowsDataService,
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
const expectedInvokeCount = MAX_RETRY_ATTEMPTS; const expectedInvokeCount = MAX_RETRY_ATTEMPTS;
refreshSessionService refreshSessionService
.forceRefreshSession(allConfigs[0], allConfigs) .forceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe({ .subscribe({
next: () => { next: () => {
fail('It should not return any result.'); fail('It should not return any result.');
@ -407,9 +410,9 @@ describe('RefreshSessionService ', () => {
}); });
tick(allConfigs[0].silentRenewTimeoutInSeconds * 10000); tick(allConfigs[0].silentRenewTimeoutInSeconds * 10000);
})); });
it('occurs unknown error throws it to subscriber', fakeAsync(() => { it('occurs unknown error throws it to subscriber', async () => {
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -419,29 +422,29 @@ describe('RefreshSessionService ', () => {
const expectedErrorMessage = 'Test error message'; const expectedErrorMessage = 'Test error message';
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(false); ).mockReturnValue(false);
spyOnProperty( vi.spyOnProperty(
silentRenewService, silentRenewService,
'refreshSessionWithIFrameCompleted$' 'refreshSessionWithIFrameCompleted$'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(throwError(() => new Error(expectedErrorMessage))); ).mockReturnValue(throwError(() => new Error(expectedErrorMessage)));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
const resetSilentRenewRunningSpy = spyOn( const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService, flowsDataService,
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
refreshSessionService refreshSessionService
.forceRefreshSession(allConfigs[0], allConfigs) .forceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe({ .subscribe({
next: () => { next: () => {
fail('It should not return any result.'); fail('It should not return any result.');
@ -452,10 +455,10 @@ describe('RefreshSessionService ', () => {
expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled();
}, },
}); });
})); });
describe('NOT isCurrentFlowCodeFlowWithRefreshTokens', () => { describe('NOT isCurrentFlowCodeFlowWithRefreshTokens', () => {
it('does return null when not authenticated', waitForAsync(() => { it('does return null when not authenticated', async () => {
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -463,24 +466,24 @@ describe('RefreshSessionService ', () => {
}, },
]; ];
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(false); ).mockReturnValue(false);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
false false
); );
spyOnProperty( vi.spyOnProperty(
silentRenewService, silentRenewService,
'refreshSessionWithIFrameCompleted$' 'refreshSessionWithIFrameCompleted$'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
refreshSessionService refreshSessionService
.forceRefreshSession(allConfigs[0], allConfigs) .forceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe((result) => { .subscribe((result) => {
expect(result).toEqual({ expect(result).toEqual({
isAuthenticated: false, isAuthenticated: false,
@ -491,9 +494,9 @@ describe('RefreshSessionService ', () => {
configId: 'configId1', configId: 'configId1',
}); });
}); });
})); });
it('return value only returns once', waitForAsync(() => { it('return value only returns once', async () => {
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -501,18 +504,18 @@ describe('RefreshSessionService ', () => {
}, },
]; ];
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(false); ).mockReturnValue(false);
spyOn( vi.spyOn(
refreshSessionService as any, refreshSessionService as any,
'startRefreshSession' 'startRefreshSession'
).and.returnValue(of(null)); ).mockReturnValue(of(null));
spyOnProperty( vi.spyOnProperty(
silentRenewService, silentRenewService,
'refreshSessionWithIFrameCompleted$' 'refreshSessionWithIFrameCompleted$'
).and.returnValue( ).mockReturnValue(
of({ of({
authResult: { authResult: {
id_token: 'some-id_token', id_token: 'some-id_token',
@ -520,13 +523,12 @@ describe('RefreshSessionService ', () => {
}, },
} as CallbackContext) } as CallbackContext)
); );
const spyInsideMap = spyOn( const spyInsideMap = vi
authStateService, .spyOn(authStateService, 'areAuthStorageTokensValid')
'areAuthStorageTokensValid' .mockReturnValue(true);
).and.returnValue(true);
refreshSessionService refreshSessionService
.forceRefreshSession(allConfigs[0], allConfigs) .forceRefreshSession(allConfigs[0]!, allConfigs)
.subscribe((result) => { .subscribe((result) => {
expect(result).toEqual({ expect(result).toEqual({
idToken: 'some-id_token', idToken: 'some-id_token',
@ -537,33 +539,33 @@ describe('RefreshSessionService ', () => {
}); });
expect(spyInsideMap).toHaveBeenCalledTimes(1); expect(spyInsideMap).toHaveBeenCalledTimes(1);
}); });
})); });
}); });
}); });
describe('startRefreshSession', () => { describe('startRefreshSession', () => {
it('returns null if no auth well known endpoint defined', waitForAsync(() => { it('returns null if no auth well known endpoint defined', async () => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
(refreshSessionService as any) (refreshSessionService as any)
.startRefreshSession() .startRefreshSession()
.subscribe((result: any) => { .subscribe((result: any) => {
expect(result).toBe(null); expect(result).toBe(null);
}); });
})); });
it('returns null if silent renew Is running', waitForAsync(() => { it('returns null if silent renew Is running', async () => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
(refreshSessionService as any) (refreshSessionService as any)
.startRefreshSession() .startRefreshSession()
.subscribe((result: any) => { .subscribe((result: any) => {
expect(result).toBe(null); expect(result).toBe(null);
}); });
})); });
it('calls `setSilentRenewRunning` when should be executed', waitForAsync(() => { it('calls `setSilentRenewRunning` when should be executed', async () => {
const setSilentRenewRunningSpy = spyOn( const setSilentRenewRunningSpy = vi.spyOn(
flowsDataService, flowsDataService,
'setSilentRenewRunning' 'setSilentRenewRunning'
); );
@ -574,30 +576,30 @@ describe('RefreshSessionService ', () => {
}, },
]; ];
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
refreshSessionRefreshTokenService, refreshSessionRefreshTokenService,
'refreshSessionWithRefreshTokens' 'refreshSessionWithRefreshTokens'
).and.returnValue(of({} as CallbackContext)); ).mockReturnValue(of({} as CallbackContext));
(refreshSessionService as any) (refreshSessionService as any)
.startRefreshSession(allConfigs[0], allConfigs) .startRefreshSession(allConfigs[0]!, allConfigs)
.subscribe(() => { .subscribe(() => {
expect(setSilentRenewRunningSpy).toHaveBeenCalled(); expect(setSilentRenewRunningSpy).toHaveBeenCalled();
}); });
})); });
it('calls refreshSessionWithRefreshTokens when current flow is codeflow with refresh tokens', waitForAsync(() => { it('calls refreshSessionWithRefreshTokens when current flow is codeflow with refresh tokens', async () => {
spyOn(flowsDataService, 'setSilentRenewRunning'); vi.spyOn(flowsDataService, 'setSilentRenewRunning');
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -605,30 +607,32 @@ describe('RefreshSessionService ', () => {
}, },
]; ];
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(true); ).mockReturnValue(true);
const refreshSessionWithRefreshTokensSpy = spyOn( const refreshSessionWithRefreshTokensSpy = vi
refreshSessionRefreshTokenService, .spyOn(
'refreshSessionWithRefreshTokens' refreshSessionRefreshTokenService,
).and.returnValue(of({} as CallbackContext)); 'refreshSessionWithRefreshTokens'
)
.mockReturnValue(of({} as CallbackContext));
(refreshSessionService as any) (refreshSessionService as any)
.startRefreshSession(allConfigs[0], allConfigs) .startRefreshSession(allConfigs[0]!, allConfigs)
.subscribe(() => { .subscribe(() => {
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled(); expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
}); });
})); });
it('calls refreshSessionWithIframe when current flow is NOT codeflow with refresh tokens', waitForAsync(() => { it('calls refreshSessionWithIframe when current flow is NOT codeflow with refresh tokens', async () => {
spyOn(flowsDataService, 'setSilentRenewRunning'); vi.spyOn(flowsDataService, 'setSilentRenewRunning');
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -636,32 +640,33 @@ describe('RefreshSessionService ', () => {
}, },
]; ];
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn( vi.spyOn(
flowHelper, flowHelper,
'isCurrentFlowCodeFlowWithRefreshTokens' 'isCurrentFlowCodeFlowWithRefreshTokens'
).and.returnValue(false); ).mockReturnValue(false);
const refreshSessionWithRefreshTokensSpy = spyOn( const refreshSessionWithRefreshTokensSpy = vi
refreshSessionRefreshTokenService, .spyOn(
'refreshSessionWithRefreshTokens' refreshSessionRefreshTokenService,
).and.returnValue(of({} as CallbackContext)); 'refreshSessionWithRefreshTokens'
)
.mockReturnValue(of({} as CallbackContext));
const refreshSessionWithIframeSpy = spyOn( const refreshSessionWithIframeSpy = vi
refreshSessionIframeService, .spyOn(refreshSessionIframeService, 'refreshSessionWithIframe')
'refreshSessionWithIframe' .mockReturnValue(of(false));
).and.returnValue(of(false));
(refreshSessionService as any) (refreshSessionService as any)
.startRefreshSession(allConfigs[0], allConfigs) .startRefreshSession(allConfigs[0]!, allConfigs)
.subscribe(() => { .subscribe(() => {
expect(refreshSessionWithRefreshTokensSpy).not.toHaveBeenCalled(); expect(refreshSessionWithRefreshTokensSpy).not.toHaveBeenCalled();
expect(refreshSessionWithIframeSpy).toHaveBeenCalled(); expect(refreshSessionWithIframeSpy).toHaveBeenCalled();
}); });
})); });
}); });
}); });

View File

@ -1,10 +1,10 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { import {
type Observable,
TimeoutError,
forkJoin, forkJoin,
Observable,
of, of,
throwError, throwError,
TimeoutError,
timer, timer,
} from 'rxjs'; } from 'rxjs';
import { import {
@ -18,13 +18,13 @@ import {
} from 'rxjs/operators'; } from 'rxjs/operators';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service'; import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
import { SilentRenewService } from '../iframe/silent-renew.service'; import { SilentRenewService } from '../iframe/silent-renew.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { LoginResponse } from '../login/login-response'; import type { LoginResponse } from '../login/login-response';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { UserService } from '../user-data/user.service'; import { UserService } from '../user-data/user.service';
import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service';

View File

@ -1,11 +1,12 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { createRetriableStream } from '../../testing/create-retriable-stream.helper';
import { mockProvider } from '../../testing/mock';
import { AuthWellKnownDataService } from './auth-well-known-data.service'; import { AuthWellKnownDataService } from './auth-well-known-data.service';
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints'; import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
const DUMMY_WELL_KNOWN_DOCUMENT = { const DUMMY_WELL_KNOWN_DOCUMENT = {
issuer: 'https://identity-server.test/realms/main', issuer: 'https://identity-server.test/realms/main',
@ -51,56 +52,65 @@ describe('AuthWellKnownDataService', () => {
}); });
describe('getWellKnownDocument', () => { describe('getWellKnownDocument', () => {
it('should add suffix if it does not exist on current URL', waitForAsync(() => { it('should add suffix if it does not exist on current URL', async () => {
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue( const dataServiceSpy = vi
of(null) .spyOn(dataService, 'get')
); .mockReturnValue(of(null));
const urlWithoutSuffix = 'myUrl'; const urlWithoutSuffix = 'myUrl';
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/openid-configuration`; const urlWithSuffix = `${urlWithoutSuffix}/.well-known/openid-configuration`;
(service as any) (service as any)
.getWellKnownDocument(urlWithoutSuffix, { configId: 'configId1' }) .getWellKnownDocument(urlWithoutSuffix, { configId: 'configId1' })
.subscribe(() => { .subscribe(() => {
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, { expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', urlWithSuffix,
}); {
configId: 'configId1',
}
);
}); });
})); });
it('should not add suffix if it does exist on current url', waitForAsync(() => { it('should not add suffix if it does exist on current url', async () => {
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue( const dataServiceSpy = vi
of(null) .spyOn(dataService, 'get')
); .mockReturnValue(of(null));
const urlWithSuffix = `myUrl/.well-known/openid-configuration`; const urlWithSuffix = `myUrl/.well-known/openid-configuration`;
(service as any) (service as any)
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' }) .getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
.subscribe(() => { .subscribe(() => {
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, { expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', urlWithSuffix,
}); {
configId: 'configId1',
}
);
}); });
})); });
it('should not add suffix if it does exist in the middle of current url', waitForAsync(() => { it('should not add suffix if it does exist in the middle of current url', async () => {
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue( const dataServiceSpy = vi
of(null) .spyOn(dataService, 'get')
); .mockReturnValue(of(null));
const urlWithSuffix = `myUrl/.well-known/openid-configuration/and/some/more/stuff`; const urlWithSuffix = `myUrl/.well-known/openid-configuration/and/some/more/stuff`;
(service as any) (service as any)
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' }) .getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
.subscribe(() => { .subscribe(() => {
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, { expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', urlWithSuffix,
}); {
configId: 'configId1',
}
);
}); });
})); });
it('should use the custom suffix provided in the config', waitForAsync(() => { it('should use the custom suffix provided in the config', async () => {
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue( const dataServiceSpy = vi
of(null) .spyOn(dataService, 'get')
); .mockReturnValue(of(null));
const urlWithoutSuffix = `myUrl`; const urlWithoutSuffix = `myUrl`;
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`; const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`;
@ -110,15 +120,18 @@ describe('AuthWellKnownDataService', () => {
authWellknownUrlSuffix: '/.well-known/test-openid-configuration', authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
}) })
.subscribe(() => { .subscribe(() => {
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, { expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', urlWithSuffix,
authWellknownUrlSuffix: '/.well-known/test-openid-configuration', {
}); configId: 'configId1',
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
}
);
}); });
})); });
it('should retry once', waitForAsync(() => { it('should retry once', async () => {
spyOn(dataService, 'get').and.returnValue( vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('one')), throwError(() => new Error('one')),
of(DUMMY_WELL_KNOWN_DOCUMENT) of(DUMMY_WELL_KNOWN_DOCUMENT)
@ -133,10 +146,10 @@ describe('AuthWellKnownDataService', () => {
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
}, },
}); });
})); });
it('should retry twice', waitForAsync(() => { it('should retry twice', async () => {
spyOn(dataService, 'get').and.returnValue( vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('one')), throwError(() => new Error('one')),
throwError(() => new Error('two')), throwError(() => new Error('two')),
@ -152,10 +165,10 @@ describe('AuthWellKnownDataService', () => {
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
}, },
}); });
})); });
it('should fail after three tries', waitForAsync(() => { it('should fail after three tries', async () => {
spyOn(dataService, 'get').and.returnValue( vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('one')), throwError(() => new Error('one')),
throwError(() => new Error('two')), throwError(() => new Error('two')),
@ -169,17 +182,16 @@ describe('AuthWellKnownDataService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
}); });
describe('getWellKnownEndPointsForConfig', () => { describe('getWellKnownEndPointsForConfig', () => {
it('calling internal getWellKnownDocument and maps', waitForAsync(() => { it('calling internal getWellKnownDocument and maps', async () => {
spyOn(dataService, 'get').and.returnValue(of({ jwks_uri: 'jwks_uri' })); vi.spyOn(dataService, 'get').mockReturnValue(
of({ jwks_uri: 'jwks_uri' })
);
const spy = spyOn( const spy = vi.spyOn(service as any, 'getWellKnownDocument')();
service as any,
'getWellKnownDocument'
).and.callThrough();
service service
.getWellKnownEndPointsForConfig({ .getWellKnownEndPointsForConfig({
@ -191,10 +203,10 @@ describe('AuthWellKnownDataService', () => {
expect((result as any).jwks_uri).toBeUndefined(); expect((result as any).jwks_uri).toBeUndefined();
expect(result.jwksUri).toBe('jwks_uri'); expect(result.jwksUri).toBe('jwks_uri');
}); });
})); });
it('throws error and logs if no authwellknownUrl is given', waitForAsync(() => { it('throws error and logs if no authwellknownUrl is given', async () => {
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const config = { const config = {
configId: 'configId1', configId: 'configId1',
authWellknownEndpointUrl: undefined, authWellknownEndpointUrl: undefined,
@ -202,17 +214,19 @@ describe('AuthWellKnownDataService', () => {
service.getWellKnownEndPointsForConfig(config).subscribe({ service.getWellKnownEndPointsForConfig(config).subscribe({
error: (error) => { error: (error) => {
expect(loggerSpy).toHaveBeenCalledOnceWith( expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
config, config,
'no authWellknownEndpoint given!' 'no authWellknownEndpoint given!'
); );
expect(error.message).toEqual('no authWellknownEndpoint given!'); expect(error.message).toEqual('no authWellknownEndpoint given!');
}, },
}); });
})); });
it('should merge the mapped endpoints with the provided endpoints', waitForAsync(() => { it('should merge the mapped endpoints with the provided endpoints', async () => {
spyOn(dataService, 'get').and.returnValue(of(DUMMY_WELL_KNOWN_DOCUMENT)); vi.spyOn(dataService, 'get').mockReturnValue(
of(DUMMY_WELL_KNOWN_DOCUMENT)
);
const expected: AuthWellKnownEndpoints = { const expected: AuthWellKnownEndpoints = {
endSessionEndpoint: 'config-endSessionEndpoint', endSessionEndpoint: 'config-endSessionEndpoint',
@ -232,6 +246,6 @@ describe('AuthWellKnownDataService', () => {
.subscribe((result) => { .subscribe((result) => {
expect(result).toEqual(jasmine.objectContaining(expected)); expect(result).toEqual(jasmine.objectContaining(expected));
}); });
})); });
}); });
}); });

View File

@ -1,9 +1,10 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { EventTypes } from '../../public-events/event-types'; import { EventTypes } from '../../public-events/event-types';
import { PublicEventsService } from '../../public-events/public-events.service'; import { PublicEventsService } from '../../public-events/public-events.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { mockProvider } from '../../testing/mock';
import { AuthWellKnownDataService } from './auth-well-known-data.service'; import { AuthWellKnownDataService } from './auth-well-known-data.service';
import { AuthWellKnownService } from './auth-well-known.service'; import { AuthWellKnownService } from './auth-well-known.service';
@ -22,9 +23,6 @@ describe('AuthWellKnownService', () => {
mockProvider(StoragePersistenceService), mockProvider(StoragePersistenceService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(AuthWellKnownService); service = TestBed.inject(AuthWellKnownService);
dataService = TestBed.inject(AuthWellKnownDataService); dataService = TestBed.inject(AuthWellKnownDataService);
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
@ -36,7 +34,7 @@ describe('AuthWellKnownService', () => {
}); });
describe('getAuthWellKnownEndPoints', () => { describe('getAuthWellKnownEndPoints', () => {
it('getAuthWellKnownEndPoints throws an error if not config provided', waitForAsync(() => { it('getAuthWellKnownEndPoints throws an error if not config provided', async () => {
service.queryAndStoreAuthWellKnownEndPoints(null).subscribe({ service.queryAndStoreAuthWellKnownEndPoints(null).subscribe({
error: (error) => { error: (error) => {
expect(error).toEqual( expect(error).toEqual(
@ -46,17 +44,18 @@ describe('AuthWellKnownService', () => {
); );
}, },
}); });
})); });
it('getAuthWellKnownEndPoints calls always dataservice', waitForAsync(() => { it('getAuthWellKnownEndPoints calls always dataservice', async () => {
const dataServiceSpy = spyOn( const dataServiceSpy = vi
dataService, .spyOn(dataService, 'getWellKnownEndPointsForConfig')
'getWellKnownEndPointsForConfig' .mockReturnValue(of({ issuer: 'anything' }));
).and.returnValue(of({ issuer: 'anything' }));
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ issuer: 'anything' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ issuer: 'anything' })
);
service service
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' }) .queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
@ -65,18 +64,19 @@ describe('AuthWellKnownService', () => {
expect(dataServiceSpy).toHaveBeenCalled(); expect(dataServiceSpy).toHaveBeenCalled();
expect(result).toEqual({ issuer: 'anything' }); expect(result).toEqual({ issuer: 'anything' });
}); });
})); });
it('getAuthWellKnownEndPoints stored the result if http call is made', waitForAsync(() => { it('getAuthWellKnownEndPoints stored the result if http call is made', async () => {
const dataServiceSpy = spyOn( const dataServiceSpy = vi
dataService, .spyOn(dataService, 'getWellKnownEndPointsForConfig')
'getWellKnownEndPointsForConfig' .mockReturnValue(of({ issuer: 'anything' }));
).and.returnValue(of({ issuer: 'anything' }));
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(null); ['authWellKnownEndPoints', { configId: 'configId1' }],
const storeSpy = spyOn(service, 'storeWellKnownEndpoints'); () => null
);
const storeSpy = vi.spyOn(service, 'storeWellKnownEndpoints');
service service
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' }) .queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
@ -85,13 +85,13 @@ describe('AuthWellKnownService', () => {
expect(storeSpy).toHaveBeenCalled(); expect(storeSpy).toHaveBeenCalled();
expect(result).toEqual({ issuer: 'anything' }); expect(result).toEqual({ issuer: 'anything' });
}); });
})); });
it('throws `ConfigLoadingFailed` event when error happens from http', waitForAsync(() => { it('throws `ConfigLoadingFailed` event when error happens from http', async () => {
spyOn(dataService, 'getWellKnownEndPointsForConfig').and.returnValue( vi.spyOn(dataService, 'getWellKnownEndPointsForConfig').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent'); const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent');
service service
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' }) .queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
@ -99,12 +99,12 @@ describe('AuthWellKnownService', () => {
error: (err) => { error: (err) => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1); expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1);
expect(publicEventsServiceSpy).toHaveBeenCalledOnceWith( expect(publicEventsServiceSpy).toHaveBeenCalledExactlyOnceWith(
EventTypes.ConfigLoadingFailed, EventTypes.ConfigLoadingFailed,
null null
); );
}, },
}); });
})); });
}); });
}); });

View File

@ -1,15 +1,16 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockAbstractProvider, mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { EventTypes } from '../public-events/event-types'; import { EventTypes } from '../public-events/event-types';
import { PublicEventsService } from '../public-events/public-events.service'; import { PublicEventsService } from '../public-events/public-events.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockAbstractProvider, mockProvider } from '../testing/mock';
import { PlatformProvider } from '../utils/platform-provider/platform.provider'; import { PlatformProvider } from '../utils/platform-provider/platform.provider';
import { AuthWellKnownService } from './auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from './auth-well-known/auth-well-known.service';
import { ConfigurationService } from './config.service'; import { ConfigurationService } from './config.service';
import { StsConfigLoader, StsConfigStaticLoader } from './loader/config-loader'; import { StsConfigLoader, StsConfigStaticLoader } from './loader/config-loader';
import { OpenIdConfiguration } from './openid-configuration'; import type { OpenIdConfiguration } from './openid-configuration';
import { ConfigValidationService } from './validation/config-validation.service'; import { ConfigValidationService } from './validation/config-validation.service';
describe('Configuration Service', () => { describe('Configuration Service', () => {
@ -34,9 +35,6 @@ describe('Configuration Service', () => {
mockAbstractProvider(StsConfigLoader, StsConfigStaticLoader), mockAbstractProvider(StsConfigLoader, StsConfigStaticLoader),
], ],
}); });
});
beforeEach(() => {
configService = TestBed.inject(ConfigurationService); configService = TestBed.inject(ConfigurationService);
publicEventsService = TestBed.inject(PublicEventsService); publicEventsService = TestBed.inject(PublicEventsService);
authWellKnownService = TestBed.inject(AuthWellKnownService); authWellKnownService = TestBed.inject(AuthWellKnownService);
@ -88,47 +86,53 @@ describe('Configuration Service', () => {
}); });
describe('getOpenIDConfiguration', () => { describe('getOpenIDConfiguration', () => {
it(`if config is already saved 'loadConfigs' is not called`, waitForAsync(() => { it(`if config is already saved 'loadConfigs' is not called`, async () => {
(configService as any).configsInternal = { (configService as any).configsInternal = {
configId1: { configId: 'configId1' }, configId1: { configId: 'configId1' },
configId2: { configId: 'configId2' }, configId2: { configId: 'configId2' },
}; };
const spy = spyOn(configService as any, 'loadConfigs'); const spy = vi.spyOn(configService as any, 'loadConfigs');
configService.getOpenIDConfiguration('configId1').subscribe((config) => { configService.getOpenIDConfiguration('configId1').subscribe((config) => {
expect(config).toBeTruthy(); expect(config).toBeTruthy();
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
}); });
})); });
it(`if config is NOT already saved 'loadConfigs' is called`, waitForAsync(() => { it(`if config is NOT already saved 'loadConfigs' is called`, async () => {
const configs = [{ configId: 'configId1' }, { configId: 'configId2' }]; const configs = [{ configId: 'configId1' }, { configId: 'configId2' }];
const spy = spyOn(configService as any, 'loadConfigs').and.returnValue( const spy = vi
of(configs) .spyOn(configService as any, 'loadConfigs')
); .mockReturnValue(of(configs));
spyOn(configValidationService, 'validateConfig').and.returnValue(true); vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
configService.getOpenIDConfiguration('configId1').subscribe((config) => { configService.getOpenIDConfiguration('configId1').subscribe((config) => {
expect(config).toBeTruthy(); expect(config).toBeTruthy();
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
}); });
})); });
it(`returns null if config is not valid`, waitForAsync(() => { it('returns null if config is not valid', async () => {
const configs = [{ configId: 'configId1' }]; const configs = [{ configId: 'configId1' }];
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs)); vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
spyOn(configValidationService, 'validateConfig').and.returnValue(false); of(configs)
const consoleSpy = spyOn(console, 'warn'); );
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(
false
);
const consoleSpy = vi.spyOn(console, 'warn');
configService.getOpenIDConfiguration('configId1').subscribe((config) => { configService.getOpenIDConfiguration('configId1').subscribe((config) => {
expect(config).toBeNull(); expect(config).toBeNull();
expect(consoleSpy).toHaveBeenCalledOnceWith(`[oidc-client-rx] No configuration found for config id 'configId1'.`) expect(consoleSpy).toHaveBeenCalledExactlyOnceWith(
`[oidc-client-rx] No configuration found for config id 'configId1'.`
);
}); });
})); });
it(`returns null if configs are stored but not existing ID is passed`, waitForAsync(() => { it('returns null if configs are stored but not existing ID is passed', async () => {
(configService as any).configsInternal = { (configService as any).configsInternal = {
configId1: { configId: 'configId1' }, configId1: { configId: 'configId1' },
configId2: { configId: 'configId2' }, configId2: { configId: 'configId2' },
@ -139,16 +143,18 @@ describe('Configuration Service', () => {
.subscribe((config) => { .subscribe((config) => {
expect(config).toBeNull(); expect(config).toBeNull();
}); });
})); });
it(`sets authWellKnownEndPoints on config if authWellKnownEndPoints is stored`, waitForAsync(() => { it('sets authWellKnownEndPoints on config if authWellKnownEndPoints is stored', async () => {
const configs = [{ configId: 'configId1' }]; const configs = [{ configId: 'configId1' }];
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs)); vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
spyOn(configValidationService, 'validateConfig').and.returnValue(true); of(configs)
const consoleSpy = spyOn(console, 'warn'); );
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
const consoleSpy = vi.spyOn(console, 'warn');
spyOn(storagePersistenceService, 'read').and.returnValue({ vi.spyOn(storagePersistenceService, 'read').mockReturnValue({
issuer: 'auth-well-known', issuer: 'auth-well-known',
}); });
@ -156,30 +162,32 @@ describe('Configuration Service', () => {
expect(config?.authWellknownEndpoints).toEqual({ expect(config?.authWellknownEndpoints).toEqual({
issuer: 'auth-well-known', issuer: 'auth-well-known',
}); });
expect(consoleSpy).not.toHaveBeenCalled() expect(consoleSpy).not.toHaveBeenCalled();
}); });
})); });
it(`fires ConfigLoaded if authWellKnownEndPoints is stored`, waitForAsync(() => { it('fires ConfigLoaded if authWellKnownEndPoints is stored', async () => {
const configs = [{ configId: 'configId1' }]; const configs = [{ configId: 'configId1' }];
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs)); vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
spyOn(configValidationService, 'validateConfig').and.returnValue(true); of(configs)
spyOn(storagePersistenceService, 'read').and.returnValue({ );
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
vi.spyOn(storagePersistenceService, 'read').mockReturnValue({
issuer: 'auth-well-known', issuer: 'auth-well-known',
}); });
const spy = spyOn(publicEventsService, 'fireEvent'); const spy = vi.spyOn(publicEventsService, 'fireEvent');
configService.getOpenIDConfiguration('configId1').subscribe(() => { configService.getOpenIDConfiguration('configId1').subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
EventTypes.ConfigLoaded, EventTypes.ConfigLoaded,
jasmine.anything() expect.anything()
); );
}); });
})); });
it(`stores, uses and fires event when authwellknownendpoints are passed`, waitForAsync(() => { it('stores, uses and fires event when authwellknownendpoints are passed', async () => {
const configs = [ const configs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -187,58 +195,60 @@ describe('Configuration Service', () => {
}, },
]; ];
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs)); vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
spyOn(configValidationService, 'validateConfig').and.returnValue(true); of(configs)
spyOn(storagePersistenceService, 'read').and.returnValue(null); );
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null);
const fireEventSpy = spyOn(publicEventsService, 'fireEvent'); const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent');
const storeWellKnownEndpointsSpy = spyOn( const storeWellKnownEndpointsSpy = vi.spyOn(
authWellKnownService, authWellKnownService,
'storeWellKnownEndpoints' 'storeWellKnownEndpoints'
); );
configService.getOpenIDConfiguration('configId1').subscribe((config) => { configService.getOpenIDConfiguration('configId1').subscribe((config) => {
expect(config).toBeTruthy(); expect(config).toBeTruthy();
expect(fireEventSpy).toHaveBeenCalledOnceWith( expect(fireEventSpy).toHaveBeenCalledExactlyOnceWith(
EventTypes.ConfigLoaded, EventTypes.ConfigLoaded,
jasmine.anything() expect.anything()
); );
expect(storeWellKnownEndpointsSpy).toHaveBeenCalledOnceWith( expect(storeWellKnownEndpointsSpy).toHaveBeenCalledExactlyOnceWith(
config as OpenIdConfiguration, config as OpenIdConfiguration,
{ {
issuer: 'auth-well-known', issuer: 'auth-well-known',
} }
); );
}); });
})); });
}); });
describe('getOpenIDConfigurations', () => { describe('getOpenIDConfigurations', () => {
it(`returns correct result`, waitForAsync(() => { it('returns correct result', async () => {
spyOn(stsConfigLoader, 'loadConfigs').and.returnValue( vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue(
of([ of([
{ configId: 'configId1' } as OpenIdConfiguration, { configId: 'configId1' } as OpenIdConfiguration,
{ configId: 'configId2' } as OpenIdConfiguration, { configId: 'configId2' } as OpenIdConfiguration,
]) ])
); );
spyOn(configValidationService, 'validateConfig').and.returnValue(true); vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
configService.getOpenIDConfigurations('configId1').subscribe((result) => { configService.getOpenIDConfigurations('configId1').subscribe((result) => {
expect(result.allConfigs.length).toEqual(2); expect(result.allConfigs.length).toEqual(2);
expect(result.currentConfig).toBeTruthy(); expect(result.currentConfig).toBeTruthy();
}); });
})); });
it(`created configId when configId is not set`, waitForAsync(() => { it('created configId when configId is not set', async () => {
spyOn(stsConfigLoader, 'loadConfigs').and.returnValue( vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue(
of([ of([
{ clientId: 'clientId1' } as OpenIdConfiguration, { clientId: 'clientId1' } as OpenIdConfiguration,
{ clientId: 'clientId2' } as OpenIdConfiguration, { clientId: 'clientId2' } as OpenIdConfiguration,
]) ])
); );
spyOn(configValidationService, 'validateConfig').and.returnValue(true); vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
configService.getOpenIDConfigurations().subscribe((result) => { configService.getOpenIDConfigurations().subscribe((result) => {
expect(result.allConfigs.length).toEqual(2); expect(result.allConfigs.length).toEqual(2);
@ -249,17 +259,19 @@ describe('Configuration Service', () => {
expect(result.currentConfig).toBeTruthy(); expect(result.currentConfig).toBeTruthy();
expect(result.currentConfig?.configId).toBeTruthy(); expect(result.currentConfig?.configId).toBeTruthy();
}); });
})); });
it(`returns empty array if config is not valid`, waitForAsync(() => { it('returns empty array if config is not valid', async () => {
spyOn(stsConfigLoader, 'loadConfigs').and.returnValue( vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue(
of([ of([
{ configId: 'configId1' } as OpenIdConfiguration, { configId: 'configId1' } as OpenIdConfiguration,
{ configId: 'configId2' } as OpenIdConfiguration, { configId: 'configId2' } as OpenIdConfiguration,
]) ])
); );
spyOn(configValidationService, 'validateConfigs').and.returnValue(false); vi.spyOn(configValidationService, 'validateConfigs').mockReturnValue(
false
);
configService configService
.getOpenIDConfigurations() .getOpenIDConfigurations()
@ -267,12 +279,12 @@ describe('Configuration Service', () => {
expect(allConfigs).toEqual([]); expect(allConfigs).toEqual([]);
expect(currentConfig).toBeNull(); expect(currentConfig).toBeNull();
}); });
})); });
}); });
describe('setSpecialCases', () => { describe('setSpecialCases', () => {
it(`should set special cases when current platform is browser`, () => { it('should set special cases when current platform is browser', () => {
spyOn(platformProvider, 'isBrowser').and.returnValue(false); vi.spyOn(platformProvider, 'isBrowser').mockReturnValue(false);
const config = { configId: 'configId1' } as OpenIdConfiguration; const config = { configId: 'configId1' } as OpenIdConfiguration;

View File

@ -1,6 +1,7 @@
import {inject, Injectable, isDevMode} from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { forkJoin, Observable, of } from 'rxjs'; import { type Observable, forkJoin, of } from 'rxjs';
import { concatMap, map } from 'rxjs/operators'; import { concatMap, map } from 'rxjs/operators';
import { injectAbstractType } from '../injection/inject';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { EventTypes } from '../public-events/event-types'; import { EventTypes } from '../public-events/event-types';
import { PublicEventsService } from '../public-events/public-events.service'; import { PublicEventsService } from '../public-events/public-events.service';
@ -9,7 +10,7 @@ import { PlatformProvider } from '../utils/platform-provider/platform.provider';
import { AuthWellKnownService } from './auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from './auth-well-known/auth-well-known.service';
import { DEFAULT_CONFIG } from './default-config'; import { DEFAULT_CONFIG } from './default-config';
import { StsConfigLoader } from './loader/config-loader'; import { StsConfigLoader } from './loader/config-loader';
import { OpenIdConfiguration } from './openid-configuration'; import type { OpenIdConfiguration } from './openid-configuration';
import { ConfigValidationService } from './validation/config-validation.service'; import { ConfigValidationService } from './validation/config-validation.service';
@Injectable() @Injectable()
@ -26,7 +27,7 @@ export class ConfigurationService {
private readonly authWellKnownService = inject(AuthWellKnownService); private readonly authWellKnownService = inject(AuthWellKnownService);
private readonly loader = inject(StsConfigLoader); private readonly loader = injectAbstractType(StsConfigLoader);
private readonly configValidationService = inject(ConfigValidationService); private readonly configValidationService = inject(ConfigValidationService);
@ -84,11 +85,14 @@ export class ConfigurationService {
} }
private getConfig(configId?: string): OpenIdConfiguration | null { private getConfig(configId?: string): OpenIdConfiguration | null {
if (Boolean(configId)) { if (configId) {
const config = this.configsInternal[configId!]; const config = this.configsInternal[configId!];
if(!config && isDevMode()) { if (!config) {
console.warn(`[oidc-client-rx] No configuration found for config id '${configId}'.`); // biome-ignore lint/suspicious/noConsole: <explanation>
console.warn(
`[oidc-client-rx] No configuration found for config id '${configId}'.`
);
} }
return config || null; return config || null;
@ -165,7 +169,7 @@ export class ConfigurationService {
configuration configuration
); );
if (!!alreadyExistingAuthWellKnownEndpoints) { if (alreadyExistingAuthWellKnownEndpoints) {
configuration.authWellknownEndpoints = configuration.authWellknownEndpoints =
alreadyExistingAuthWellKnownEndpoints; alreadyExistingAuthWellKnownEndpoints;
@ -174,7 +178,7 @@ export class ConfigurationService {
const passedAuthWellKnownEndpoints = configuration.authWellknownEndpoints; const passedAuthWellKnownEndpoints = configuration.authWellknownEndpoints;
if (!!passedAuthWellKnownEndpoints) { if (passedAuthWellKnownEndpoints) {
this.authWellKnownService.storeWellKnownEndpoints( this.authWellKnownService.storeWellKnownEndpoints(
configuration, configuration,
passedAuthWellKnownEndpoints passedAuthWellKnownEndpoints

View File

@ -1,12 +1,12 @@
import { waitForAsync } from '@angular/core/testing'; import { waitForAsync } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { OpenIdConfiguration } from '../openid-configuration'; import type { OpenIdConfiguration } from '../openid-configuration';
import { StsConfigHttpLoader, StsConfigStaticLoader } from './config-loader'; import { StsConfigHttpLoader, StsConfigStaticLoader } from './config-loader';
describe('ConfigLoader', () => { describe('ConfigLoader', () => {
describe('StsConfigStaticLoader', () => { describe('StsConfigStaticLoader', () => {
describe('loadConfigs', () => { describe('loadConfigs', () => {
it('returns an array if an array is passed', waitForAsync(() => { it('returns an array if an array is passed', async () => {
const toPass = [ const toPass = [
{ configId: 'configId1' } as OpenIdConfiguration, { configId: 'configId1' } as OpenIdConfiguration,
{ configId: 'configId2' } as OpenIdConfiguration, { configId: 'configId2' } as OpenIdConfiguration,
@ -17,11 +17,11 @@ describe('ConfigLoader', () => {
const result$ = loader.loadConfigs(); const result$ = loader.loadConfigs();
result$.subscribe((result) => { result$.subscribe((result) => {
expect(Array.isArray(result)).toBeTrue(); expect(Array.isArray(result)).toBeTruthy();
}); });
})); });
it('returns an array if only one config is passed', waitForAsync(() => { it('returns an array if only one config is passed', async () => {
const loader = new StsConfigStaticLoader({ const loader = new StsConfigStaticLoader({
configId: 'configId1', configId: 'configId1',
} as OpenIdConfiguration); } as OpenIdConfiguration);
@ -29,15 +29,15 @@ describe('ConfigLoader', () => {
const result$ = loader.loadConfigs(); const result$ = loader.loadConfigs();
result$.subscribe((result) => { result$.subscribe((result) => {
expect(Array.isArray(result)).toBeTrue(); expect(Array.isArray(result)).toBeTruthy();
}); });
})); });
}); });
}); });
describe('StsConfigHttpLoader', () => { describe('StsConfigHttpLoader', () => {
describe('loadConfigs', () => { describe('loadConfigs', () => {
it('returns an array if an array of observables is passed', waitForAsync(() => { it('returns an array if an array of observables is passed', async () => {
const toPass = [ const toPass = [
of({ configId: 'configId1' } as OpenIdConfiguration), of({ configId: 'configId1' } as OpenIdConfiguration),
of({ configId: 'configId2' } as OpenIdConfiguration), of({ configId: 'configId2' } as OpenIdConfiguration),
@ -47,13 +47,13 @@ describe('ConfigLoader', () => {
const result$ = loader.loadConfigs(); const result$ = loader.loadConfigs();
result$.subscribe((result) => { result$.subscribe((result) => {
expect(Array.isArray(result)).toBeTrue(); expect(Array.isArray(result)).toBeTruthy();
expect(result[0].configId).toBe('configId1'); expect(result[0].configId).toBe('configId1');
expect(result[1].configId).toBe('configId2'); expect(result[1].configId).toBe('configId2');
}); });
})); });
it('returns an array if an observable with a config array is passed', waitForAsync(() => { it('returns an array if an observable with a config array is passed', async () => {
const toPass = of([ const toPass = of([
{ configId: 'configId1' } as OpenIdConfiguration, { configId: 'configId1' } as OpenIdConfiguration,
{ configId: 'configId2' } as OpenIdConfiguration, { configId: 'configId2' } as OpenIdConfiguration,
@ -63,13 +63,13 @@ describe('ConfigLoader', () => {
const result$ = loader.loadConfigs(); const result$ = loader.loadConfigs();
result$.subscribe((result) => { result$.subscribe((result) => {
expect(Array.isArray(result)).toBeTrue(); expect(Array.isArray(result)).toBeTruthy();
expect(result[0].configId).toBe('configId1'); expect(result[0].configId).toBe('configId1');
expect(result[1].configId).toBe('configId2'); expect(result[1].configId).toBe('configId2');
}); });
})); });
it('returns an array if only one config is passed', waitForAsync(() => { it('returns an array if only one config is passed', async () => {
const loader = new StsConfigHttpLoader( const loader = new StsConfigHttpLoader(
of({ configId: 'configId1' } as OpenIdConfiguration) of({ configId: 'configId1' } as OpenIdConfiguration)
); );
@ -77,10 +77,10 @@ describe('ConfigLoader', () => {
const result$ = loader.loadConfigs(); const result$ = loader.loadConfigs();
result$.subscribe((result) => { result$.subscribe((result) => {
expect(Array.isArray(result)).toBeTrue(); expect(Array.isArray(result)).toBeTruthy();
expect(result[0].configId).toBe('configId1'); expect(result[0].configId).toBe('configId1');
}); });
})); });
}); });
}); });
}); });

View File

@ -1,7 +1,7 @@
import { Provider } from 'injection-js'; import type { Provider } from 'injection-js';
import { forkJoin, Observable, of } from 'rxjs'; import { type Observable, forkJoin, of } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { OpenIdConfiguration } from '../openid-configuration'; import type { OpenIdConfiguration } from '../openid-configuration';
export class OpenIdConfigLoader { export class OpenIdConfigLoader {
loader?: Provider; loader?: Provider;
@ -13,6 +13,7 @@ export abstract class StsConfigLoader {
export class StsConfigStaticLoader implements StsConfigLoader { export class StsConfigStaticLoader implements StsConfigLoader {
constructor( constructor(
// biome-ignore lint/style/noParameterProperties: <explanation>
private readonly passedConfigs: OpenIdConfiguration | OpenIdConfiguration[] private readonly passedConfigs: OpenIdConfiguration | OpenIdConfiguration[]
) {} ) {}
@ -27,6 +28,7 @@ export class StsConfigStaticLoader implements StsConfigLoader {
export class StsConfigHttpLoader implements StsConfigLoader { export class StsConfigHttpLoader implements StsConfigLoader {
constructor( constructor(
// biome-ignore lint/style/noParameterProperties: <explanation>
private readonly configs$: private readonly configs$:
| Observable<OpenIdConfiguration> | Observable<OpenIdConfiguration>
| Observable<OpenIdConfiguration>[] | Observable<OpenIdConfiguration>[]

View File

@ -1,5 +1,5 @@
import { LogLevel } from '../logging/log-level'; import type { LogLevel } from '../logging/log-level';
import { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints'; import type { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints';
export interface OpenIdConfiguration { export interface OpenIdConfiguration {
/** /**
@ -207,5 +207,5 @@ export interface OpenIdConfiguration {
/** /**
* Disable cleaning up the popup when receiving invalid messages * Disable cleaning up the popup when receiving invalid messages
*/ */
disableCleaningPopupOnInvalidMessage?: boolean disableCleaningPopupOnInvalidMessage?: boolean;
} }

View File

@ -1,8 +1,9 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { LogLevel } from '../../logging/log-level'; import { LogLevel } from '../../logging/log-level';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { OpenIdConfiguration } from '../openid-configuration'; import { mockProvider } from '../../testing/mock';
import type { OpenIdConfiguration } from '../openid-configuration';
import { ConfigValidationService } from './config-validation.service'; import { ConfigValidationService } from './config-validation.service';
import { allMultipleConfigRules } from './rules'; import { allMultipleConfigRules } from './rules';
@ -14,6 +15,8 @@ describe('Config Validation Service', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ConfigValidationService, mockProvider(LoggerService)], providers: [ConfigValidationService, mockProvider(LoggerService)],
}); });
configValidationService = TestBed.inject(ConfigValidationService);
loggerService = TestBed.inject(LoggerService);
}); });
const VALID_CONFIG = { const VALID_CONFIG = {
@ -29,11 +32,6 @@ describe('Config Validation Service', () => {
logLevel: LogLevel.Debug, logLevel: LogLevel.Debug,
}; };
beforeEach(() => {
configValidationService = TestBed.inject(ConfigValidationService);
loggerService = TestBed.inject(LoggerService);
});
it('should create', () => { it('should create', () => {
expect(configValidationService).toBeTruthy(); expect(configValidationService).toBeTruthy();
}); });
@ -42,26 +40,27 @@ describe('Config Validation Service', () => {
const config = {}; const config = {};
const result = configValidationService.validateConfig(config); const result = configValidationService.validateConfig(config);
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('should return true for valid config', () => { it('should return true for valid config', () => {
const result = configValidationService.validateConfig(VALID_CONFIG); const result = configValidationService.validateConfig(VALID_CONFIG);
expect(result).toBeTrue(); expect(result).toBeTruthy();
}); });
it('calls `logWarning` if one rule has warning level', () => { it('calls `logWarning` if one rule has warning level', () => {
const loggerWarningSpy = spyOn(loggerService, 'logWarning'); const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
const messageTypeSpy = spyOn( const messageTypeSpy = vi.spyOn(
configValidationService as any, configValidationService as any,
'getAllMessagesOfType' 'getAllMessagesOfType'
); );
messageTypeSpy mockImplementationWhenArgsEqual(
.withArgs('warning', jasmine.any(Array)) messageTypeSpy,
.and.returnValue(['A warning message']); (arg1: any, arg2: any) => arg1 === 'warning' && Array.isArray(arg2),
messageTypeSpy.withArgs('error', jasmine.any(Array)).and.callThrough(); () => ['A warning message']
);
configValidationService.validateConfig(VALID_CONFIG); configValidationService.validateConfig(VALID_CONFIG);
expect(loggerWarningSpy).toHaveBeenCalled(); expect(loggerWarningSpy).toHaveBeenCalled();
@ -72,7 +71,7 @@ describe('Config Validation Service', () => {
const config = { ...VALID_CONFIG, clientId: '' } as OpenIdConfiguration; const config = { ...VALID_CONFIG, clientId: '' } as OpenIdConfiguration;
const result = configValidationService.validateConfig(config); const result = configValidationService.validateConfig(config);
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
}); });
@ -84,7 +83,7 @@ describe('Config Validation Service', () => {
} as OpenIdConfiguration; } as OpenIdConfiguration;
const result = configValidationService.validateConfig(config); const result = configValidationService.validateConfig(config);
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
}); });
@ -93,7 +92,7 @@ describe('Config Validation Service', () => {
const config = { ...VALID_CONFIG, redirectUrl: '' }; const config = { ...VALID_CONFIG, redirectUrl: '' };
const result = configValidationService.validateConfig(config); const result = configValidationService.validateConfig(config);
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
}); });
@ -107,7 +106,7 @@ describe('Config Validation Service', () => {
} as OpenIdConfiguration; } as OpenIdConfiguration;
const result = configValidationService.validateConfig(config); const result = configValidationService.validateConfig(config);
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
}); });
@ -120,12 +119,12 @@ describe('Config Validation Service', () => {
scopes: 'scope1 scope2 but_no_offline_access', scopes: 'scope1 scope2 but_no_offline_access',
}; };
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const loggerWarningSpy = spyOn(loggerService, 'logWarning'); const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
const result = configValidationService.validateConfig(config); const result = configValidationService.validateConfig(config);
expect(result).toBeTrue(); expect(result).toBeTruthy();
expect(loggerSpy).not.toHaveBeenCalled(); expect(loggerSpy).not.toHaveBeenCalled();
expect(loggerWarningSpy).toHaveBeenCalled(); expect(loggerWarningSpy).toHaveBeenCalled();
}); });
@ -146,47 +145,47 @@ describe('Config Validation Service', () => {
scopes: 'scope1 scope2 but_no_offline_access', scopes: 'scope1 scope2 but_no_offline_access',
}; };
const loggerErrorSpy = spyOn(loggerService, 'logError'); const loggerErrorSpy = vi.spyOn(loggerService, 'logError');
const loggerWarningSpy = spyOn(loggerService, 'logWarning'); const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
const result = configValidationService.validateConfigs([ const result = configValidationService.validateConfigs([
config1, config1,
config2, config2,
]); ]);
expect(result).toBeTrue(); expect(result).toBeTruthy();
expect(loggerErrorSpy).not.toHaveBeenCalled(); expect(loggerErrorSpy).not.toHaveBeenCalled();
expect(loggerWarningSpy.calls.argsFor(0)).toEqual([ expect(vi.mocked(loggerWarningSpy).mock.calls[0]).toEqual([
config1, config1,
'You added multiple configs with the same authority, clientId and scope', 'You added multiple configs with the same authority, clientId and scope',
]); ]);
expect(loggerWarningSpy.calls.argsFor(1)).toEqual([ expect(vi.mocked(loggerWarningSpy).mock.calls[1]).toEqual([
config2, config2,
'You added multiple configs with the same authority, clientId and scope', 'You added multiple configs with the same authority, clientId and scope',
]); ]);
}); });
it('should return false and a better error message when config is not passed as object with config property', () => { it('should return false and a better error message when config is not passed as object with config property', () => {
const loggerWarningSpy = spyOn(loggerService, 'logWarning'); const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
const result = configValidationService.validateConfigs([]); const result = configValidationService.validateConfigs([]);
expect(result).toBeFalse(); expect(result).toBeFalsy();
expect(loggerWarningSpy).not.toHaveBeenCalled(); expect(loggerWarningSpy).not.toHaveBeenCalled();
}); });
}); });
describe('validateConfigs', () => { describe('validateConfigs', () => {
it('calls internal method with empty array if something falsy is passed', () => { it('calls internal method with empty array if something falsy is passed', () => {
const spy = spyOn( const spy = vi.spyOn(
configValidationService as any, configValidationService as any,
'validateConfigsInternal' 'validateConfigsInternal'
).and.callThrough(); );
const result = configValidationService.validateConfigs([]); const result = configValidationService.validateConfigs([]);
expect(result).toBeFalse(); expect(result).toBeFalsy();
expect(spy).toHaveBeenCalledOnceWith([], allMultipleConfigRules); expect(spy).toHaveBeenCalledExactlyOnceWith([], allMultipleConfigRules);
}); });
}); });
}); });

View File

@ -1,4 +1,5 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { CryptoService } from '../utils/crypto/crypto.service'; import { CryptoService } from '../utils/crypto/crypto.service';
import { import {
JwkExtractor, JwkExtractor,

View File

@ -1,14 +1,15 @@
import { HttpErrorResponse, HttpHeaders } from '@angular/common/http'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { TestBed, waitForAsync } from '@angular/core/testing'; import { HttpErrorResponse, HttpHeaders } from '@ngify/http';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.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 { UrlService } from '../../utils/url/url.service';
import { TokenValidationService } from '../../validation/token-validation.service'; import { TokenValidationService } from '../../validation/token-validation.service';
import { CallbackContext } from '../callback-context'; import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
import { CodeFlowCallbackHandlerService } from './code-flow-callback-handler.service'; import { CodeFlowCallbackHandlerService } from './code-flow-callback-handler.service';
@ -46,13 +47,16 @@ describe('CodeFlowCallbackHandlerService', () => {
}); });
describe('codeFlowCallback', () => { describe('codeFlowCallback', () => {
it('throws error if no state is given', waitForAsync(() => { it('throws error if no state is given', async () => {
const getUrlParameterSpy = spyOn( const getUrlParameterSpy = vi
urlService, .spyOn(urlService, 'getUrlParameter')
'getUrlParameter' .mockReturnValue('params');
).and.returnValue('params');
getUrlParameterSpy.withArgs('test-url', 'state').and.returnValue(''); mockImplementationWhenArgsEqual(
getUrlParameterSpy,
['test-url', 'state'],
() => ''
);
service service
.codeFlowCallback('test-url', { configId: 'configId1' }) .codeFlowCallback('test-url', { configId: 'configId1' })
@ -61,15 +65,14 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('throws error if no code is given', waitForAsync(() => { it('throws error if no code is given', async () => {
const getUrlParameterSpy = spyOn( const getUrlParameterSpy = vi
urlService, .spyOn(urlService, 'getUrlParameter')
'getUrlParameter' .mockReturnValue('params');
).and.returnValue('params');
getUrlParameterSpy.withArgs('test-url', 'code').and.returnValue(''); getUrlParameterSpy.withArgs('test-url', 'code').mockReturnValue('');
service service
.codeFlowCallback('test-url', { configId: 'configId1' }) .codeFlowCallback('test-url', { configId: 'configId1' })
@ -78,10 +81,10 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('returns callbackContext if all params are good', waitForAsync(() => { it('returns callbackContext if all params are good', async () => {
spyOn(urlService, 'getUrlParameter').and.returnValue('params'); vi.spyOn(urlService, 'getUrlParameter').mockReturnValue('params');
const expectedCallbackContext = { const expectedCallbackContext = {
code: 'params', code: 'params',
@ -100,7 +103,7 @@ describe('CodeFlowCallbackHandlerService', () => {
.subscribe((callbackContext) => { .subscribe((callbackContext) => {
expect(callbackContext).toEqual(expectedCallbackContext); expect(callbackContext).toEqual(expectedCallbackContext);
}); });
})); });
}); });
describe('codeFlowCodeRequest ', () => { describe('codeFlowCodeRequest ', () => {
@ -112,11 +115,11 @@ describe('CodeFlowCallbackHandlerService', () => {
url: 'https://identity-server.test/openid-connect/token', url: 'https://identity-server.test/openid-connect/token',
}); });
it('throws error if state is not correct', waitForAsync(() => { it('throws error if state is not correct', async () => {
spyOn( vi.spyOn(
tokenValidationService, tokenValidationService,
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).and.returnValue(false); ).mockReturnValue(false);
service service
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
@ -125,16 +128,18 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('throws error if authWellknownEndpoints is null is given', waitForAsync(() => { it('throws error if authWellknownEndpoints is null is given', async () => {
spyOn( vi.spyOn(
tokenValidationService, tokenValidationService,
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).and.returnValue(true); ).mockReturnValue(true);
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(null); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => null
);
service service
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
@ -143,16 +148,18 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('throws error if tokenendpoint is null is given', waitForAsync(() => { it('throws error if tokenendpoint is null is given', async () => {
spyOn( vi.spyOn(
tokenValidationService, tokenValidationService,
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).and.returnValue(true); ).mockReturnValue(true);
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: null }); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ tokenEndpoint: null })
);
service service
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
@ -161,34 +168,36 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('calls dataService if all params are good', waitForAsync(() => { it('calls dataService if all params are good', async () => {
const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn( vi.spyOn(
tokenValidationService, tokenValidationService,
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).and.returnValue(true); ).mockReturnValue(true);
service service
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
.subscribe(() => { .subscribe(() => {
expect(postSpy).toHaveBeenCalledOnceWith( expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'tokenEndpoint', 'tokenEndpoint',
undefined, undefined,
{ configId: 'configId1' }, { configId: 'configId1' },
jasmine.any(HttpHeaders) expect.any(HttpHeaders)
); );
}); });
})); });
it('calls url service with custom token params', waitForAsync(() => { it('calls url service with custom token params', async () => {
const urlServiceSpy = spyOn( const urlServiceSpy = vi.spyOn(
urlService, urlService,
'createBodyForCodeFlowCodeRequest' 'createBodyForCodeFlowCodeRequest'
); );
@ -197,76 +206,84 @@ describe('CodeFlowCallbackHandlerService', () => {
customParamsCodeRequest: { foo: 'bar' }, customParamsCodeRequest: { foo: 'bar' },
}; };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn( vi.spyOn(
tokenValidationService, tokenValidationService,
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).and.returnValue(true); ).mockReturnValue(true);
const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
service service
.codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config) .codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config)
.subscribe(() => { .subscribe(() => {
expect(urlServiceSpy).toHaveBeenCalledOnceWith('foo', config, { expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith('foo', config, {
foo: 'bar', foo: 'bar',
}); });
expect(postSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledTimes(1);
}); });
})); });
it('calls dataService with correct headers if all params are good', waitForAsync(() => { it('calls dataService with correct headers if all params are good', async () => {
const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
const config = { const config = {
configId: 'configId1', configId: 'configId1',
customParamsCodeRequest: { foo: 'bar' }, customParamsCodeRequest: { foo: 'bar' },
}; };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn( vi.spyOn(
tokenValidationService, tokenValidationService,
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).and.returnValue(true); ).mockReturnValue(true);
service service
.codeFlowCodeRequest({} as CallbackContext, config) .codeFlowCodeRequest({} as CallbackContext, config)
.subscribe(() => { .subscribe(() => {
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTrue(); expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
})); });
it('returns error in case of http error', waitForAsync(() => { it('returns error in case of http error', async () => {
spyOn(dataService, 'post').and.returnValue(throwError(() => HTTP_ERROR)); vi.spyOn(dataService, 'post').mockReturnValue(
throwError(() => HTTP_ERROR)
);
const config = { const config = {
configId: 'configId1', configId: 'configId1',
customParamsCodeRequest: { foo: 'bar' }, customParamsCodeRequest: { foo: 'bar' },
authority: 'authority', authority: 'authority',
}; };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
error: (err) => { error: (err) => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('retries request in case of no connection http error and succeeds', waitForAsync(() => { it('retries request in case of no connection http error and succeeds', async () => {
const postSpy = spyOn(dataService, 'post').and.returnValue( const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => CONNECTION_ERROR), throwError(() => CONNECTION_ERROR),
of({}) of({})
@ -278,14 +295,16 @@ describe('CodeFlowCallbackHandlerService', () => {
authority: 'authority', authority: 'authority',
}; };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn( vi.spyOn(
tokenValidationService, tokenValidationService,
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).and.returnValue(true); ).mockReturnValue(true);
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
next: (res) => { next: (res) => {
@ -297,10 +316,10 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeFalsy(); expect(err).toBeFalsy();
}, },
}); });
})); });
it('retries request in case of no connection http error and fails because of http error afterwards', waitForAsync(() => { it('retries request in case of no connection http error and fails because of http error afterwards', async () => {
const postSpy = spyOn(dataService, 'post').and.returnValue( const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => CONNECTION_ERROR), throwError(() => CONNECTION_ERROR),
throwError(() => HTTP_ERROR) throwError(() => HTTP_ERROR)
@ -312,14 +331,16 @@ describe('CodeFlowCallbackHandlerService', () => {
authority: 'authority', authority: 'authority',
}; };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn( vi.spyOn(
tokenValidationService, tokenValidationService,
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).and.returnValue(true); ).mockReturnValue(true);
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
next: (res) => { next: (res) => {
@ -331,6 +352,6 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(postSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledTimes(1);
}, },
}); });
})); });
}); });
}); });

View File

@ -1,4 +1,4 @@
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@ngify/http';
import { isNetworkError } from './error-helper'; import { isNetworkError } from './error-helper';
describe('error helper', () => { describe('error helper', () => {
@ -27,31 +27,31 @@ describe('error helper', () => {
}); });
it('returns true on http error with status = 0', () => { it('returns true on http error with status = 0', () => {
expect(isNetworkError(CONNECTION_ERROR)).toBeTrue(); expect(isNetworkError(CONNECTION_ERROR)).toBeTruthy();
}); });
it('returns true on http error with status = 0 and unknown error', () => { it('returns true on http error with status = 0 and unknown error', () => {
expect(isNetworkError(UNKNOWN_CONNECTION_ERROR)).toBeTrue(); expect(isNetworkError(UNKNOWN_CONNECTION_ERROR)).toBeTruthy();
}); });
it('returns true on http error with status <> 0 and error ProgressEvent', () => { it('returns true on http error with status <> 0 and error ProgressEvent', () => {
expect(isNetworkError(PARTIAL_CONNECTION_ERROR)).toBeTrue(); expect(isNetworkError(PARTIAL_CONNECTION_ERROR)).toBeTruthy();
}); });
it('returns false on non http error', () => { it('returns false on non http error', () => {
expect(isNetworkError(new Error('not a HttpErrorResponse'))).toBeFalse(); expect(isNetworkError(new Error('not a HttpErrorResponse'))).toBeFalsy();
}); });
it('returns false on string error', () => { it('returns false on string error', () => {
expect(isNetworkError('not a HttpErrorResponse')).toBeFalse(); expect(isNetworkError('not a HttpErrorResponse')).toBeFalsy();
}); });
it('returns false on undefined', () => { it('returns false on undefined', () => {
expect(isNetworkError(undefined)).toBeFalse(); expect(isNetworkError(undefined)).toBeFalsy();
}); });
it('returns false on empty http error', () => { it('returns false on empty http error', () => {
expect(isNetworkError(HTTP_ERROR)).toBeFalse(); expect(isNetworkError(HTTP_ERROR)).toBeFalsy();
}); });
}); });
}); });

View File

@ -1,12 +1,13 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { JwtKey, JwtKeys } from '../../validation/jwtkeys'; import { mockProvider } from '../../testing/mock';
import type { JwtKey, JwtKeys } from '../../validation/jwtkeys';
import { ValidationResult } from '../../validation/validation-result'; import { ValidationResult } from '../../validation/validation-result';
import { AuthResult, CallbackContext } from '../callback-context'; import type { AuthResult, CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service';
import { SigninKeyDataService } from '../signin-key-data.service'; import { SigninKeyDataService } from '../signin-key-data.service';
@ -46,9 +47,6 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
mockProvider(ResetAuthDataService), mockProvider(ResetAuthDataService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(HistoryJwtKeysCallbackHandlerService); service = TestBed.inject(HistoryJwtKeysCallbackHandlerService);
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
resetAuthDataService = TestBed.inject(ResetAuthDataService); resetAuthDataService = TestBed.inject(ResetAuthDataService);
@ -62,8 +60,8 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
}); });
describe('callbackHistoryAndResetJwtKeys', () => { describe('callbackHistoryAndResetJwtKeys', () => {
it('writes authResult into the storage', waitForAsync(() => { it('writes authResult into the storage', async () => {
const storagePersistenceServiceSpy = spyOn( const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService, storagePersistenceService,
'write' 'write'
); );
@ -75,86 +73,86 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
const callbackContext = { const callbackContext = {
authResult: DUMMY_AUTH_RESULT, authResult: DUMMY_AUTH_RESULT,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
}, },
]; ];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys) of({ keys: [] } as JwtKeys)
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe(() => { .subscribe(() => {
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([ expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]], ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['jwtKeys', { keys: [] }, allconfigs[0]], ['jwtKeys', { keys: [] }, allConfigs[0]],
]); ]);
// write authnResult & jwtKeys // write authnResult & jwtKeys
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
}); });
})); });
it('writes refresh_token into the storage without reuse (refresh token rotation)', waitForAsync(() => { it('writes refresh_token into the storage without reuse (refresh token rotation)', async () => {
const DUMMY_AUTH_RESULT = { const DUMMY_AUTH_RESULT = {
refresh_token: 'dummy_refresh_token', refresh_token: 'dummy_refresh_token',
id_token: 'some-id-token', id_token: 'some-id-token',
}; };
const storagePersistenceServiceSpy = spyOn( const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService, storagePersistenceService,
'write' 'write'
); );
const callbackContext = { const callbackContext = {
authResult: DUMMY_AUTH_RESULT, authResult: DUMMY_AUTH_RESULT,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
}, },
]; ];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys) of({ keys: [] } as JwtKeys)
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe(() => { .subscribe(() => {
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([ expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]], ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['jwtKeys', { keys: [] }, allconfigs[0]], ['jwtKeys', { keys: [] }, allConfigs[0]],
]); ]);
// write authnResult & refresh_token & jwtKeys // write authnResult & refresh_token & jwtKeys
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
}); });
})); });
it('writes refresh_token into the storage with reuse (without refresh token rotation)', waitForAsync(() => { it('writes refresh_token into the storage with reuse (without refresh token rotation)', async () => {
const DUMMY_AUTH_RESULT = { const DUMMY_AUTH_RESULT = {
refresh_token: 'dummy_refresh_token', refresh_token: 'dummy_refresh_token',
id_token: 'some-id-token', id_token: 'some-id-token',
}; };
const storagePersistenceServiceSpy = spyOn( const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService, storagePersistenceService,
'write' 'write'
); );
const callbackContext = { const callbackContext = {
authResult: DUMMY_AUTH_RESULT, authResult: DUMMY_AUTH_RESULT,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
@ -162,27 +160,27 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
}, },
]; ];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys) of({ keys: [] } as JwtKeys)
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe(() => { .subscribe(() => {
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([ expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]], ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['reusable_refresh_token', 'dummy_refresh_token', allconfigs[0]], ['reusable_refresh_token', 'dummy_refresh_token', allConfigs[0]],
['jwtKeys', { keys: [] }, allconfigs[0]], ['jwtKeys', { keys: [] }, allConfigs[0]],
]); ]);
// write authnResult & refresh_token & jwtKeys // write authnResult & refresh_token & jwtKeys
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3); expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3);
}); });
})); });
it('resetBrowserHistory if historyCleanup is turned on and is not in a renewProcess', waitForAsync(() => { it('resetBrowserHistory if historyCleanup is turned on and is not in a renewProcess', async () => {
const DUMMY_AUTH_RESULT = { const DUMMY_AUTH_RESULT = {
id_token: 'some-id-token', id_token: 'some-id-token',
}; };
@ -190,30 +188,30 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
isRenewProcess: false, isRenewProcess: false,
authResult: DUMMY_AUTH_RESULT, authResult: DUMMY_AUTH_RESULT,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: false, historyCleanupOff: false,
}, },
]; ];
const windowSpy = spyOn(window.history, 'replaceState'); const windowSpy = vi.spyOn(window.history, 'replaceState');
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys) of({ keys: [] } as JwtKeys)
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe(() => { .subscribe(() => {
expect(windowSpy).toHaveBeenCalledTimes(1); expect(windowSpy).toHaveBeenCalledTimes(1);
}); });
})); });
it('returns callbackContext with jwtkeys filled if everything works fine', waitForAsync(() => { it('returns callbackContext with jwtkeys filled if everything works fine', async () => {
const DUMMY_AUTH_RESULT = { const DUMMY_AUTH_RESULT = {
id_token: 'some-id-token', id_token: 'some-id-token',
}; };
@ -222,21 +220,21 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
isRenewProcess: false, isRenewProcess: false,
authResult: DUMMY_AUTH_RESULT, authResult: DUMMY_AUTH_RESULT,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: false, historyCleanupOff: false,
}, },
]; ];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [{ kty: 'henlo' } as JwtKey] } as JwtKeys) of({ keys: [{ kty: 'henlo' } as JwtKey] } as JwtKeys)
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe((result) => { .subscribe((result) => {
expect(result).toEqual({ expect(result).toEqual({
@ -245,9 +243,9 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
jwtKeys: { keys: [{ kty: 'henlo' }] }, jwtKeys: { keys: [{ kty: 'henlo' }] },
} as CallbackContext); } as CallbackContext);
}); });
})); });
it('returns error if no jwtKeys have been in the call --> keys are null', waitForAsync(() => { it('returns error if no jwtKeys have been in the call --> keys are null', async () => {
const DUMMY_AUTH_RESULT = { const DUMMY_AUTH_RESULT = {
id_token: 'some-id-token', id_token: 'some-id-token',
}; };
@ -256,32 +254,32 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
isRenewProcess: false, isRenewProcess: false,
authResult: DUMMY_AUTH_RESULT, authResult: DUMMY_AUTH_RESULT,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: false, historyCleanupOff: false,
}, },
]; ];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({} as JwtKeys) of({} as JwtKeys)
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe({ .subscribe({
error: (err) => { error: (err) => {
expect(err.message).toEqual( expect(err.message).toEqual(
`Failed to retrieve signing key with error: Error: Failed to retrieve signing key` 'Failed to retrieve signing key with error: Error: Failed to retrieve signing key'
); );
}, },
}); });
})); });
it('returns error if no jwtKeys have been in the call --> keys throw an error', waitForAsync(() => { it('returns error if no jwtKeys have been in the call --> keys throw an error', async () => {
const DUMMY_AUTH_RESULT = { const DUMMY_AUTH_RESULT = {
id_token: 'some-id-token', id_token: 'some-id-token',
}; };
@ -289,36 +287,36 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
isRenewProcess: false, isRenewProcess: false,
authResult: DUMMY_AUTH_RESULT, authResult: DUMMY_AUTH_RESULT,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: false, historyCleanupOff: false,
}, },
]; ];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe({ .subscribe({
error: (err) => { error: (err) => {
expect(err.message).toEqual( expect(err.message).toEqual(
`Failed to retrieve signing key with error: Error: Error: error` 'Failed to retrieve signing key with error: Error: Error: error'
); );
}, },
}); });
})); });
it('returns error if callbackContext.authresult has an error property filled', waitForAsync(() => { it('returns error if callbackContext.authresult has an error property filled', async () => {
const callbackContext = { const callbackContext = {
authResult: { error: 'someError' }, authResult: { error: 'someError' },
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
@ -328,36 +326,36 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe({ .subscribe({
error: (err) => { error: (err) => {
expect(err.message).toEqual( expect(err.message).toEqual(
`AuthCallback AuthResult came with error: someError` 'AuthCallback AuthResult came with error: someError'
); );
}, },
}); });
})); });
it('calls resetAuthorizationData, resets nonce and authStateService in case of an error', waitForAsync(() => { it('calls resetAuthorizationData, resets nonce and authStateService in case of an error', async () => {
const callbackContext = { const callbackContext = {
authResult: { error: 'someError' }, authResult: { error: 'someError' },
isRenewProcess: false, isRenewProcess: false,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
}, },
]; ];
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
const setNonceSpy = spyOn(flowsDataService, 'setNonce'); const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce');
const updateAndPublishAuthStateSpy = spyOn( const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService, authStateService,
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
@ -365,40 +363,42 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe({ .subscribe({
error: () => { error: () => {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect(setNonceSpy).toHaveBeenCalledTimes(1); expect(setNonceSpy).toHaveBeenCalledTimes(1);
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ expect(
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
validationResult: ValidationResult.SecureTokenServerError, validationResult: ValidationResult.SecureTokenServerError,
isRenewProcess: false, isRenewProcess: false,
}); });
}, },
}); });
})); });
it('calls authStateService.updateAndPublishAuthState with login required if the error is `login_required`', waitForAsync(() => { it('calls authStateService.updateAndPublishAuthState with login required if the error is `login_required`', async () => {
const callbackContext = { const callbackContext = {
authResult: { error: 'login_required' }, authResult: { error: 'login_required' },
isRenewProcess: false, isRenewProcess: false,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
}, },
]; ];
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
const setNonceSpy = spyOn(flowsDataService, 'setNonce'); const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce');
const updateAndPublishAuthStateSpy = spyOn( const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService, authStateService,
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
@ -406,23 +406,25 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe({ .subscribe({
error: () => { error: () => {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect(setNonceSpy).toHaveBeenCalledTimes(1); expect(setNonceSpy).toHaveBeenCalledTimes(1);
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ expect(
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
validationResult: ValidationResult.LoginRequired, validationResult: ValidationResult.LoginRequired,
isRenewProcess: false, isRenewProcess: false,
}); });
}, },
}); });
})); });
it('should store jwtKeys', waitForAsync(() => { it('should store jwtKeys', async () => {
const DUMMY_AUTH_RESULT = { const DUMMY_AUTH_RESULT = {
id_token: 'some-id-token', id_token: 'some-id-token',
}; };
@ -430,33 +432,33 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
const initialCallbackContext = { const initialCallbackContext = {
authResult: DUMMY_AUTH_RESULT, authResult: DUMMY_AUTH_RESULT,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
}, },
]; ];
const storagePersistenceServiceSpy = spyOn( const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService, storagePersistenceService,
'write' 'write'
); );
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of(DUMMY_JWT_KEYS) of(DUMMY_JWT_KEYS)
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
initialCallbackContext, initialCallbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe({ .subscribe({
next: (callbackContext: CallbackContext) => { next: (callbackContext: CallbackContext) => {
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([ expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]], ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['jwtKeys', DUMMY_JWT_KEYS, allconfigs[0]], ['jwtKeys', DUMMY_JWT_KEYS, allConfigs[0]],
]); ]);
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
@ -465,9 +467,9 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
expect(err).toBeFalsy(); expect(err).toBeFalsy();
}, },
}); });
})); });
it('should not store jwtKeys on error', waitForAsync(() => { it('should not store jwtKeys on error', async () => {
const authResult = { const authResult = {
id_token: 'some-id-token', id_token: 'some-id-token',
access_token: 'some-access-token', access_token: 'some-access-token',
@ -476,26 +478,26 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
authResult, authResult,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
}, },
]; ];
const storagePersistenceServiceSpy = spyOn( const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService, storagePersistenceService,
'write' 'write'
); );
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
initialCallbackContext, initialCallbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe({ .subscribe({
next: (callbackContext: CallbackContext) => { next: (callbackContext: CallbackContext) => {
@ -505,16 +507,18 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
// storagePersistenceService.write() should not have been called with jwtKeys // storagePersistenceService.write() should not have been called with jwtKeys
expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith( expect(
storagePersistenceServiceSpy
).toHaveBeenCalledExactlyOnceWith(
'authnResult', 'authnResult',
authResult, authResult,
allconfigs[0] allConfigs[0]
); );
}, },
}); });
})); });
it('should fallback to stored jwtKeys on error', waitForAsync(() => { it('should fallback to stored jwtKeys on error', async () => {
const authResult = { const authResult = {
id_token: 'some-id-token', id_token: 'some-id-token',
access_token: 'some-access-token', access_token: 'some-access-token',
@ -523,66 +527,65 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
authResult, authResult,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
}, },
]; ];
const storagePersistenceServiceSpy = spyOn( const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService, storagePersistenceService,
'read' 'read'
); );
storagePersistenceServiceSpy.and.returnValue(DUMMY_JWT_KEYS); storagePersistenceServiceSpy.mockReturnValue(DUMMY_JWT_KEYS);
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
initialCallbackContext, initialCallbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe({ .subscribe({
next: (callbackContext: CallbackContext) => { next: (callbackContext: CallbackContext) => {
expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith( expect(
'jwtKeys', storagePersistenceServiceSpy
allconfigs[0] ).toHaveBeenCalledExactlyOnceWith('jwtKeys', allConfigs[0]);
);
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
}, },
error: (err) => { error: (err) => {
expect(err).toBeFalsy(); expect(err).toBeFalsy();
}, },
}); });
})); });
it('should throw error if no jwtKeys are stored', waitForAsync(() => { it('should throw error if no jwtKeys are stored', async () => {
const authResult = { const authResult = {
id_token: 'some-id-token', id_token: 'some-id-token',
access_token: 'some-access-token', access_token: 'some-access-token',
} as AuthResult; } as AuthResult;
const initialCallbackContext = { authResult } as CallbackContext; const initialCallbackContext = { authResult } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
historyCleanupOff: true, historyCleanupOff: true,
}, },
]; ];
spyOn(storagePersistenceService, 'read').and.returnValue(null); vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null);
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
service service
.callbackHistoryAndResetJwtKeys( .callbackHistoryAndResetJwtKeys(
initialCallbackContext, initialCallbackContext,
allconfigs[0], allConfigs[0]!,
allconfigs allConfigs
) )
.subscribe({ .subscribe({
next: (callbackContext: CallbackContext) => { next: (callbackContext: CallbackContext) => {
@ -592,7 +595,7 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
}); });
describe('historyCleanUpTurnedOn ', () => { describe('historyCleanUpTurnedOn ', () => {

View File

@ -1,14 +1,14 @@
import { DOCUMENT } from '../../dom'; import { Injectable, inject } from 'injection-js';
import { inject, Injectable } from 'injection-js'; import { type Observable, of, throwError } from 'rxjs';
import { Observable, of, throwError } from 'rxjs';
import { catchError, switchMap, tap } from 'rxjs/operators'; import { catchError, switchMap, tap } from 'rxjs/operators';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { DOCUMENT } from '../../dom';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { JwtKeys } from '../../validation/jwtkeys'; import type { JwtKeys } from '../../validation/jwtkeys';
import { ValidationResult } from '../../validation/validation-result'; import { ValidationResult } from '../../validation/validation-result';
import { CallbackContext } from '../callback-context'; import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service';
import { SigninKeyDataService } from '../signin-key-data.service'; import { SigninKeyDataService } from '../signin-key-data.service';

View File

@ -1,8 +1,9 @@
import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { DOCUMENT } from '../../dom'; import { DOCUMENT } from '../../dom';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { mockProvider } from '../../../test/auto-mock';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { CallbackContext } from '../callback-context'; import { mockProvider } from '../../testing/mock';
import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service';
import { ImplicitFlowCallbackHandlerService } from './implicit-flow-callback-handler.service'; import { ImplicitFlowCallbackHandlerService } from './implicit-flow-callback-handler.service';
@ -34,9 +35,6 @@ describe('ImplicitFlowCallbackHandlerService', () => {
}, },
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(ImplicitFlowCallbackHandlerService); service = TestBed.inject(ImplicitFlowCallbackHandlerService);
flowsDataService = TestBed.inject(FlowsDataService); flowsDataService = TestBed.inject(FlowsDataService);
resetAuthDataService = TestBed.inject(ResetAuthDataService); resetAuthDataService = TestBed.inject(ResetAuthDataService);
@ -47,46 +45,46 @@ describe('ImplicitFlowCallbackHandlerService', () => {
}); });
describe('implicitFlowCallback', () => { describe('implicitFlowCallback', () => {
it('calls "resetAuthorizationData" if silent renew is not running', waitForAsync(() => { it('calls "resetAuthorizationData" if silent renew is not running', async () => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
}, },
]; ];
service service
.implicitFlowCallback(allconfigs[0], allconfigs, 'any-hash') .implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')
.subscribe(() => { .subscribe(() => {
expect(resetAuthorizationDataSpy).toHaveBeenCalled(); expect(resetAuthorizationDataSpy).toHaveBeenCalled();
}); });
})); });
it('does NOT calls "resetAuthorizationData" if silent renew is running', waitForAsync(() => { it('does NOT calls "resetAuthorizationData" if silent renew is running', async () => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
}, },
]; ];
service service
.implicitFlowCallback(allconfigs[0], allconfigs, 'any-hash') .implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')
.subscribe(() => { .subscribe(() => {
expect(resetAuthorizationDataSpy).not.toHaveBeenCalled(); expect(resetAuthorizationDataSpy).not.toHaveBeenCalled();
}); });
})); });
it('returns callbackContext if all params are good', waitForAsync(() => { it('returns callbackContext if all params are good', async () => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
const expectedCallbackContext = { const expectedCallbackContext = {
code: '', code: '',
refreshToken: '', refreshToken: '',
@ -99,21 +97,21 @@ describe('ImplicitFlowCallbackHandlerService', () => {
existingIdToken: null, existingIdToken: null,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
}, },
]; ];
service service
.implicitFlowCallback(allconfigs[0], allconfigs, 'anyHash') .implicitFlowCallback(allConfigs[0]!, allConfigs, 'anyHash')
.subscribe((callbackContext) => { .subscribe((callbackContext) => {
expect(callbackContext).toEqual(expectedCallbackContext); expect(callbackContext).toEqual(expectedCallbackContext);
}); });
})); });
it('uses window location hash if no hash is passed', waitForAsync(() => { it('uses window location hash if no hash is passed', async () => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
const expectedCallbackContext = { const expectedCallbackContext = {
code: '', code: '',
refreshToken: '', refreshToken: '',
@ -126,17 +124,17 @@ describe('ImplicitFlowCallbackHandlerService', () => {
existingIdToken: null, existingIdToken: null,
} as CallbackContext; } as CallbackContext;
const allconfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
}, },
]; ];
service service
.implicitFlowCallback(allconfigs[0], allconfigs) .implicitFlowCallback(allConfigs[0]!, allConfigs)
.subscribe((callbackContext) => { .subscribe((callbackContext) => {
expect(callbackContext).toEqual(expectedCallbackContext); expect(callbackContext).toEqual(expectedCallbackContext);
}); });
})); });
}); });
}); });

View File

@ -1,9 +1,9 @@
import { Injectable, inject } from 'injection-js';
import { type Observable, of } from 'rxjs';
import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { DOCUMENT } from '../../dom'; import { DOCUMENT } from '../../dom';
import { inject, Injectable } from 'injection-js';
import { Observable, of } from 'rxjs';
import { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { AuthResult, CallbackContext } from '../callback-context'; import type { AuthResult, CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service';

View File

@ -1,8 +1,9 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { CallbackContext } from '../callback-context'; import { mockProvider } from '../../testing/mock';
import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
import { RefreshSessionCallbackHandlerService } from './refresh-session-callback-handler.service'; import { RefreshSessionCallbackHandlerService } from './refresh-session-callback-handler.service';
@ -33,15 +34,15 @@ describe('RefreshSessionCallbackHandlerService', () => {
}); });
describe('refreshSessionWithRefreshTokens', () => { describe('refreshSessionWithRefreshTokens', () => {
it('returns callbackContext if all params are good', waitForAsync(() => { it('returns callbackContext if all params are good', async () => {
spyOn( vi.spyOn(
flowsDataService, flowsDataService,
'getExistingOrCreateAuthStateControl' 'getExistingOrCreateAuthStateControl'
).and.returnValue('state-data'); ).mockReturnValue('state-data');
spyOn(authStateService, 'getRefreshToken').and.returnValue( vi.spyOn(authStateService, 'getRefreshToken').mockReturnValue(
'henlo-furiend' 'henlo-furiend'
); );
spyOn(authStateService, 'getIdToken').and.returnValue('henlo-legger'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('henlo-legger');
const expectedCallbackContext = { const expectedCallbackContext = {
code: '', code: '',
@ -60,15 +61,15 @@ describe('RefreshSessionCallbackHandlerService', () => {
.subscribe((callbackContext) => { .subscribe((callbackContext) => {
expect(callbackContext).toEqual(expectedCallbackContext); expect(callbackContext).toEqual(expectedCallbackContext);
}); });
})); });
it('throws error if no refresh token is given', waitForAsync(() => { it('throws error if no refresh token is given', async () => {
spyOn( vi.spyOn(
flowsDataService, flowsDataService,
'getExistingOrCreateAuthStateControl' 'getExistingOrCreateAuthStateControl'
).and.returnValue('state-data'); ).mockReturnValue('state-data');
spyOn(authStateService, 'getRefreshToken').and.returnValue(''); vi.spyOn(authStateService, 'getRefreshToken').mockReturnValue('');
spyOn(authStateService, 'getIdToken').and.returnValue('henlo-legger'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('henlo-legger');
service service
.refreshSessionWithRefreshTokens({ configId: 'configId1' }) .refreshSessionWithRefreshTokens({ configId: 'configId1' })
@ -77,6 +78,6 @@ describe('RefreshSessionCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
}); });
}); });

View File

@ -1,13 +1,14 @@
import { HttpErrorResponse, HttpHeaders } from '@angular/common/http'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { TestBed, waitForAsync } from '@angular/core/testing'; import { HttpErrorResponse, HttpHeaders } from '@ngify/http';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.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 { UrlService } from '../../utils/url/url.service';
import { CallbackContext } from '../callback-context'; import type { CallbackContext } from '../callback-context';
import { RefreshTokenCallbackHandlerService } from './refresh-token-callback-handler.service'; import { RefreshTokenCallbackHandlerService } from './refresh-token-callback-handler.service';
describe('RefreshTokenCallbackHandlerService', () => { describe('RefreshTokenCallbackHandlerService', () => {
@ -46,7 +47,7 @@ describe('RefreshTokenCallbackHandlerService', () => {
url: 'https://identity-server.test/openid-connect/token', url: 'https://identity-server.test/openid-connect/token',
}); });
it('throws error if no tokenEndpoint is given', waitForAsync(() => { it('throws error if no tokenEndpoint is given', async () => {
(service as any) (service as any)
.refreshTokensRequestTokens({} as CallbackContext) .refreshTokensRequestTokens({} as CallbackContext)
.subscribe({ .subscribe({
@ -54,41 +55,45 @@ describe('RefreshTokenCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('calls data service if all params are good', waitForAsync(() => { it('calls data service if all params are good', async () => {
const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
service service
.refreshTokensRequestTokens({} as CallbackContext, { .refreshTokensRequestTokens({} as CallbackContext, {
configId: 'configId1', configId: 'configId1',
}) })
.subscribe(() => { .subscribe(() => {
expect(postSpy).toHaveBeenCalledOnceWith( expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'tokenEndpoint', 'tokenEndpoint',
undefined, undefined,
{ configId: 'configId1' }, { configId: 'configId1' },
jasmine.any(HttpHeaders) expect.any(HttpHeaders)
); );
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTrue(); expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
})); });
it('calls data service with correct headers if all params are good', waitForAsync(() => { it('calls data service with correct headers if all params are good', async () => {
const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
service service
.refreshTokensRequestTokens({} as CallbackContext, { .refreshTokensRequestTokens({} as CallbackContext, {
@ -97,20 +102,24 @@ describe('RefreshTokenCallbackHandlerService', () => {
.subscribe(() => { .subscribe(() => {
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTrue(); expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
})); });
it('returns error in case of http error', waitForAsync(() => { it('returns error in case of http error', async () => {
spyOn(dataService, 'post').and.returnValue(throwError(() => HTTP_ERROR)); vi.spyOn(dataService, 'post').mockReturnValue(
throwError(() => HTTP_ERROR)
);
const config = { configId: 'configId1', authority: 'authority' }; const config = { configId: 'configId1', authority: 'authority' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
service service
.refreshTokensRequestTokens({} as CallbackContext, config) .refreshTokensRequestTokens({} as CallbackContext, config)
@ -119,10 +128,10 @@ describe('RefreshTokenCallbackHandlerService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('retries request in case of no connection http error and succeeds', waitForAsync(() => { it('retries request in case of no connection http error and succeeds', async () => {
const postSpy = spyOn(dataService, 'post').and.returnValue( const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => CONNECTION_ERROR), throwError(() => CONNECTION_ERROR),
of({}) of({})
@ -130,9 +139,11 @@ describe('RefreshTokenCallbackHandlerService', () => {
); );
const config = { configId: 'configId1', authority: 'authority' }; const config = { configId: 'configId1', authority: 'authority' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
service service
.refreshTokensRequestTokens({} as CallbackContext, config) .refreshTokensRequestTokens({} as CallbackContext, config)
@ -146,10 +157,10 @@ describe('RefreshTokenCallbackHandlerService', () => {
expect(err).toBeFalsy(); expect(err).toBeFalsy();
}, },
}); });
})); });
it('retries request in case of no connection http error and fails because of http error afterwards', waitForAsync(() => { it('retries request in case of no connection http error and fails because of http error afterwards', async () => {
const postSpy = spyOn(dataService, 'post').and.returnValue( const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => CONNECTION_ERROR), throwError(() => CONNECTION_ERROR),
throwError(() => HTTP_ERROR) throwError(() => HTTP_ERROR)
@ -157,9 +168,11 @@ describe('RefreshTokenCallbackHandlerService', () => {
); );
const config = { configId: 'configId1', authority: 'authority' }; const config = { configId: 'configId1', authority: 'authority' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); ['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
service service
.refreshTokensRequestTokens({} as CallbackContext, config) .refreshTokensRequestTokens({} as CallbackContext, config)
@ -173,6 +186,6 @@ describe('RefreshTokenCallbackHandlerService', () => {
expect(postSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledTimes(1);
}, },
}); });
})); });
}); });
}); });

View File

@ -1,13 +1,14 @@
import { DOCUMENT } from '../../dom'; import { TestBed } from '@/testing';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { DOCUMENT } from '../../dom';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StateValidationResult } from '../../validation/state-validation-result'; import { mockProvider } from '../../testing/mock';
import type { StateValidationResult } from '../../validation/state-validation-result';
import { StateValidationService } from '../../validation/state-validation.service'; import { StateValidationService } from '../../validation/state-validation.service';
import { ValidationResult } from '../../validation/validation-result'; import { ValidationResult } from '../../validation/validation-result';
import { CallbackContext } from '../callback-context'; import type { CallbackContext } from '../callback-context';
import { ResetAuthDataService } from '../reset-auth-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service';
import { StateValidationCallbackHandlerService } from './state-validation-callback-handler.service'; import { StateValidationCallbackHandlerService } from './state-validation-callback-handler.service';
@ -56,8 +57,11 @@ describe('StateValidationCallbackHandlerService', () => {
}); });
describe('callbackStateValidation', () => { describe('callbackStateValidation', () => {
it('returns callbackContext with validationResult if validationResult is valid', waitForAsync(() => { it('returns callbackContext with validationResult if validationResult is valid', async () => {
spyOn(stateValidationService, 'getValidatedStateResult').and.returnValue( vi.spyOn(
stateValidationService,
'getValidatedStateResult'
).mockReturnValue(
of({ of({
idToken: 'idTokenJustForTesting', idToken: 'idTokenJustForTesting',
authResponseIsValid: true, authResponseIsValid: true,
@ -68,7 +72,7 @@ describe('StateValidationCallbackHandlerService', () => {
service service
.callbackStateValidation( .callbackStateValidation(
{} as CallbackContext, {} as CallbackContext,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe((newCallbackContext) => { .subscribe((newCallbackContext) => {
@ -79,47 +83,53 @@ describe('StateValidationCallbackHandlerService', () => {
}, },
} as CallbackContext); } as CallbackContext);
}); });
})); });
it('logs error in case of an error', waitForAsync(() => { it('logs error in case of an error', async () => {
spyOn(stateValidationService, 'getValidatedStateResult').and.returnValue( vi.spyOn(
stateValidationService,
'getValidatedStateResult'
).mockReturnValue(
of({ of({
authResponseIsValid: false, authResponseIsValid: false,
} as StateValidationResult) } as StateValidationResult)
); );
const loggerSpy = spyOn(loggerService, 'logWarning'); const loggerSpy = vi.spyOn(loggerService, 'logWarning');
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
service service
.callbackStateValidation( .callbackStateValidation(
{} as CallbackContext, {} as CallbackContext,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ .subscribe({
error: () => { error: () => {
expect(loggerSpy).toHaveBeenCalledOnceWith( expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
allConfigs[0], allConfigs[0]!,
'authorizedCallback, token(s) validation failed, resetting. Hash: &anyFakeHash' 'authorizedCallback, token(s) validation failed, resetting. Hash: &anyFakeHash'
); );
}, },
}); });
})); });
it('calls resetAuthDataService.resetAuthorizationData and authStateService.updateAndPublishAuthState in case of an error', waitForAsync(() => { it('calls resetAuthDataService.resetAuthorizationData and authStateService.updateAndPublishAuthState in case of an error', async () => {
spyOn(stateValidationService, 'getValidatedStateResult').and.returnValue( vi.spyOn(
stateValidationService,
'getValidatedStateResult'
).mockReturnValue(
of({ of({
authResponseIsValid: false, authResponseIsValid: false,
state: ValidationResult.LoginRequired, state: ValidationResult.LoginRequired,
} as StateValidationResult) } as StateValidationResult)
); );
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
const updateAndPublishAuthStateSpy = spyOn( const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService, authStateService,
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
@ -128,19 +138,21 @@ describe('StateValidationCallbackHandlerService', () => {
service service
.callbackStateValidation( .callbackStateValidation(
{ isRenewProcess: true } as CallbackContext, { isRenewProcess: true } as CallbackContext,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ .subscribe({
error: () => { error: () => {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ expect(
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
validationResult: ValidationResult.LoginRequired, validationResult: ValidationResult.LoginRequired,
isRenewProcess: true, isRenewProcess: true,
}); });
}, },
}); });
})); });
}); });
}); });

View File

@ -1,13 +1,13 @@
import { DOCUMENT } from '../../dom'; import { Injectable, inject } from 'injection-js';
import { inject, Injectable } from 'injection-js'; import type { Observable } from 'rxjs';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { DOCUMENT } from '../../dom';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StateValidationResult } from '../../validation/state-validation-result'; import type { StateValidationResult } from '../../validation/state-validation-result';
import { StateValidationService } from '../../validation/state-validation.service'; import { StateValidationService } from '../../validation/state-validation.service';
import { CallbackContext } from '../callback-context'; import type { CallbackContext } from '../callback-context';
import { ResetAuthDataService } from '../reset-auth-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service';
@Injectable() @Injectable()

View File

@ -1,12 +1,13 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { UserService } from '../../user-data/user.service'; import { UserService } from '../../user-data/user.service';
import { StateValidationResult } from '../../validation/state-validation-result'; import { StateValidationResult } from '../../validation/state-validation-result';
import { ValidationResult } from '../../validation/validation-result'; import { ValidationResult } from '../../validation/validation-result';
import { CallbackContext } from '../callback-context'; import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service';
import { UserCallbackHandlerService } from './user-callback-handler.service'; import { UserCallbackHandlerService } from './user-callback-handler.service';
@ -44,7 +45,7 @@ describe('UserCallbackHandlerService', () => {
}); });
describe('callbackUser', () => { describe('callbackUser', () => {
it('calls flowsDataService.setSessionState with correct params if autoUserInfo is false, isRenewProcess is false and refreshToken is null', waitForAsync(() => { it('calls flowsDataService.setSessionState with correct params if autoUserInfo is false, isRenewProcess is false and refreshToken is null', async () => {
const svr = new StateValidationResult( const svr = new StateValidationResult(
'accesstoken', 'accesstoken',
'idtoken', 'idtoken',
@ -70,17 +71,17 @@ describe('UserCallbackHandlerService', () => {
}, },
]; ];
const spy = spyOn(flowsDataService, 'setSessionState'); const spy = vi.spyOn(flowsDataService, 'setSessionState');
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => { .subscribe((resultCallbackContext) => {
expect(spy).toHaveBeenCalledOnceWith('mystate', allConfigs[0]); expect(spy).toHaveBeenCalledExactlyOnceWith('mystate', allConfigs[0]);
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
})); });
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false, isRenewProcess is true and refreshToken is null', waitForAsync(() => { it('does NOT call flowsDataService.setSessionState if autoUserInfo is false, isRenewProcess is true and refreshToken is null', async () => {
const svr = new StateValidationResult( const svr = new StateValidationResult(
'accesstoken', 'accesstoken',
'idtoken', 'idtoken',
@ -104,17 +105,17 @@ describe('UserCallbackHandlerService', () => {
autoUserInfo: false, autoUserInfo: false,
}, },
]; ];
const spy = spyOn(flowsDataService, 'setSessionState'); const spy = vi.spyOn(flowsDataService, 'setSessionState');
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => { .subscribe((resultCallbackContext) => {
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
})); });
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value', waitForAsync(() => { it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value', async () => {
const svr = new StateValidationResult( const svr = new StateValidationResult(
'accesstoken', 'accesstoken',
'idtoken', 'idtoken',
@ -138,17 +139,17 @@ describe('UserCallbackHandlerService', () => {
autoUserInfo: false, autoUserInfo: false,
}, },
]; ];
const spy = spyOn(flowsDataService, 'setSessionState'); const spy = vi.spyOn(flowsDataService, 'setSessionState');
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => { .subscribe((resultCallbackContext) => {
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
})); });
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value, id_token is false', waitForAsync(() => { it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value, id_token is false', async () => {
const svr = new StateValidationResult('accesstoken', '', true, ''); const svr = new StateValidationResult('accesstoken', '', true, '');
const callbackContext = { const callbackContext = {
code: '', code: '',
@ -168,17 +169,17 @@ describe('UserCallbackHandlerService', () => {
}, },
]; ];
const spy = spyOn(flowsDataService, 'setSessionState'); const spy = vi.spyOn(flowsDataService, 'setSessionState');
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => { .subscribe((resultCallbackContext) => {
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
})); });
it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is false', waitForAsync(() => { it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is false', async () => {
const svr = new StateValidationResult( const svr = new StateValidationResult(
'accesstoken', 'accesstoken',
'idtoken', 'idtoken',
@ -204,24 +205,24 @@ describe('UserCallbackHandlerService', () => {
}, },
]; ];
const updateAndPublishAuthStateSpy = spyOn( const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService, authStateService,
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => { .subscribe((resultCallbackContext) => {
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: true, isAuthenticated: true,
validationResult: ValidationResult.NotSet, validationResult: ValidationResult.NotSet,
isRenewProcess: false, isRenewProcess: false,
}); });
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
})); });
it('calls userService.getAndPersistUserDataInStore with correct params if autoUserInfo is true', waitForAsync(() => { it('calls userService.getAndPersistUserDataInStore with correct params if autoUserInfo is true', async () => {
const svr = new StateValidationResult( const svr = new StateValidationResult(
'accesstoken', 'accesstoken',
'idtoken', 'idtoken',
@ -247,16 +248,17 @@ describe('UserCallbackHandlerService', () => {
}, },
]; ];
const getAndPersistUserDataInStoreSpy = spyOn( const getAndPersistUserDataInStoreSpy = vi
userService, .spyOn(userService, 'getAndPersistUserDataInStore')
'getAndPersistUserDataInStore' .mockReturnValue(of({ user: 'some_data' }));
).and.returnValue(of({ user: 'some_data' }));
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => { .subscribe((resultCallbackContext) => {
expect(getAndPersistUserDataInStoreSpy).toHaveBeenCalledOnceWith( expect(
allConfigs[0], getAndPersistUserDataInStoreSpy
).toHaveBeenCalledExactlyOnceWith(
allConfigs[0]!,
allConfigs, allConfigs,
false, false,
'idtoken', 'idtoken',
@ -264,9 +266,9 @@ describe('UserCallbackHandlerService', () => {
); );
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
})); });
it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is true', waitForAsync(() => { it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is true', async () => {
const svr = new StateValidationResult( const svr = new StateValidationResult(
'accesstoken', 'accesstoken',
'idtoken', 'idtoken',
@ -293,27 +295,27 @@ describe('UserCallbackHandlerService', () => {
}, },
]; ];
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue( vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
of({ user: 'some_data' }) of({ user: 'some_data' })
); );
const updateAndPublishAuthStateSpy = spyOn( const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService, authStateService,
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => { .subscribe((resultCallbackContext) => {
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: true, isAuthenticated: true,
validationResult: ValidationResult.MaxOffsetExpired, validationResult: ValidationResult.MaxOffsetExpired,
isRenewProcess: false, isRenewProcess: false,
}); });
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
})); });
it('calls flowsDataService.setSessionState with correct params if user data is present and NOT refresh token', waitForAsync(() => { it('calls flowsDataService.setSessionState with correct params if user data is present and NOT refresh token', async () => {
const svr = new StateValidationResult( const svr = new StateValidationResult(
'accesstoken', 'accesstoken',
'idtoken', 'idtoken',
@ -340,23 +342,23 @@ describe('UserCallbackHandlerService', () => {
}, },
]; ];
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue( vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
of({ user: 'some_data' }) of({ user: 'some_data' })
); );
const setSessionStateSpy = spyOn(flowsDataService, 'setSessionState'); const setSessionStateSpy = vi.spyOn(flowsDataService, 'setSessionState');
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => { .subscribe((resultCallbackContext) => {
expect(setSessionStateSpy).toHaveBeenCalledOnceWith( expect(setSessionStateSpy).toHaveBeenCalledExactlyOnceWith(
'mystate', 'mystate',
allConfigs[0] allConfigs[0]
); );
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
})); });
it('calls authStateService.publishUnauthorizedState with correct params if user info which are coming back are null', waitForAsync(() => { it('calls authStateService.publishUnauthorizedState with correct params if user info which are coming back are null', async () => {
const svr = new StateValidationResult( const svr = new StateValidationResult(
'accesstoken', 'accesstoken',
'idtoken', 'idtoken',
@ -383,19 +385,21 @@ describe('UserCallbackHandlerService', () => {
}, },
]; ];
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue( vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
of(null) of(null)
); );
const updateAndPublishAuthStateSpy = spyOn( const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService, authStateService,
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe({ .subscribe({
error: (err) => { error: (err) => {
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ expect(
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
validationResult: ValidationResult.MaxOffsetExpired, validationResult: ValidationResult.MaxOffsetExpired,
isRenewProcess: false, isRenewProcess: false,
@ -405,9 +409,9 @@ describe('UserCallbackHandlerService', () => {
); );
}, },
}); });
})); });
it('calls resetAuthDataService.resetAuthorizationData if user info which are coming back are null', waitForAsync(() => { it('calls resetAuthDataService.resetAuthorizationData if user info which are coming back are null', async () => {
const svr = new StateValidationResult( const svr = new StateValidationResult(
'accesstoken', 'accesstoken',
'idtoken', 'idtoken',
@ -434,16 +438,16 @@ describe('UserCallbackHandlerService', () => {
}, },
]; ];
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue( vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
of(null) of(null)
); );
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
service service
.callbackUser(callbackContext, allConfigs[0], allConfigs) .callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe({ .subscribe({
error: (err) => { error: (err) => {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
@ -452,6 +456,6 @@ describe('UserCallbackHandlerService', () => {
); );
}, },
}); });
})); });
}); });
}); });

View File

@ -1,12 +1,12 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, of, throwError } from 'rxjs'; import { type Observable, of, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators'; import { catchError, switchMap } from 'rxjs/operators';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { UserService } from '../../user-data/user.service'; import { UserService } from '../../user-data/user.service';
import { StateValidationResult } from '../../validation/state-validation-result'; import type { StateValidationResult } from '../../validation/state-validation-result';
import { CallbackContext } from '../callback-context'; import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service';

View File

@ -1,7 +1,8 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockProvider } from '../testing/mock';
import { CryptoService } from '../utils/crypto/crypto.service'; import { CryptoService } from '../utils/crypto/crypto.service';
import { FlowsDataService } from './flows-data.service'; import { FlowsDataService } from './flows-data.service';
import { RandomService } from './random/random.service'; import { RandomService } from './random/random.service';
@ -37,12 +38,12 @@ describe('Flows Data Service', () => {
describe('createNonce', () => { describe('createNonce', () => {
it('createNonce returns nonce and stores it', () => { it('createNonce returns nonce and stores it', () => {
const spy = spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
const result = service.createNonce({ configId: 'configId1' }); const result = service.createNonce({ configId: 'configId1' });
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(spy).toHaveBeenCalledOnceWith('authNonce', result, { expect(spy).toHaveBeenCalledExactlyOnceWith('authNonce', result, {
configId: 'configId1', configId: 'configId1',
}); });
}); });
@ -50,32 +51,38 @@ describe('Flows Data Service', () => {
describe('AuthStateControl', () => { describe('AuthStateControl', () => {
it('getAuthStateControl returns property from store', () => { it('getAuthStateControl returns property from store', () => {
const spy = spyOn(storagePersistenceService, 'read'); const spy = vi.spyOn(storagePersistenceService, 'read');
service.getAuthStateControl({ configId: 'configId1' }); service.getAuthStateControl({ configId: 'configId1' });
expect(spy).toHaveBeenCalledOnceWith('authStateControl', { expect(spy).toHaveBeenCalledExactlyOnceWith('authStateControl', {
configId: 'configId1', configId: 'configId1',
}); });
}); });
it('setAuthStateControl saves property in store', () => { it('setAuthStateControl saves property in store', () => {
const spy = spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
service.setAuthStateControl('ToSave', { configId: 'configId1' }); service.setAuthStateControl('ToSave', { configId: 'configId1' });
expect(spy).toHaveBeenCalledOnceWith('authStateControl', 'ToSave', { expect(spy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', 'authStateControl',
}); 'ToSave',
{
configId: 'configId1',
}
);
}); });
}); });
describe('getExistingOrCreateAuthStateControl', () => { describe('getExistingOrCreateAuthStateControl', () => {
it('if nothing stored it creates a 40 char one and saves the authStateControl', () => { it('if nothing stored it creates a 40 char one and saves the authStateControl', () => {
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authStateControl', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(null); ['authStateControl', { configId: 'configId1' }],
const setSpy = spyOn(storagePersistenceService, 'write'); () => null
);
const setSpy = vi.spyOn(storagePersistenceService, 'write');
const result = service.getExistingOrCreateAuthStateControl({ const result = service.getExistingOrCreateAuthStateControl({
configId: 'configId1', configId: 'configId1',
@ -83,16 +90,22 @@ describe('Flows Data Service', () => {
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result.length).toBe(41); expect(result.length).toBe(41);
expect(setSpy).toHaveBeenCalledOnceWith('authStateControl', result, { expect(setSpy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', 'authStateControl',
}); result,
{
configId: 'configId1',
}
);
}); });
it('if stored it returns the value and does NOT Store the value again', () => { it('if stored it returns the value and does NOT Store the value again', () => {
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authStateControl', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue('someAuthStateControl'); ['authStateControl', { configId: 'configId1' }],
const setSpy = spyOn(storagePersistenceService, 'write'); () => 'someAuthStateControl'
);
const setSpy = vi.spyOn(storagePersistenceService, 'write');
const result = service.getExistingOrCreateAuthStateControl({ const result = service.getExistingOrCreateAuthStateControl({
configId: 'configId1', configId: 'configId1',
@ -106,11 +119,11 @@ describe('Flows Data Service', () => {
describe('setSessionState', () => { describe('setSessionState', () => {
it('setSessionState saves the value in the storage', () => { it('setSessionState saves the value in the storage', () => {
const spy = spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
service.setSessionState('Genesis', { configId: 'configId1' }); service.setSessionState('Genesis', { configId: 'configId1' });
expect(spy).toHaveBeenCalledOnceWith('session_state', 'Genesis', { expect(spy).toHaveBeenCalledExactlyOnceWith('session_state', 'Genesis', {
configId: 'configId1', configId: 'configId1',
}); });
}); });
@ -118,7 +131,7 @@ describe('Flows Data Service', () => {
describe('resetStorageFlowData', () => { describe('resetStorageFlowData', () => {
it('resetStorageFlowData calls correct method on storagePersistenceService', () => { it('resetStorageFlowData calls correct method on storagePersistenceService', () => {
const spy = spyOn(storagePersistenceService, 'resetStorageFlowData'); const spy = vi.spyOn(storagePersistenceService, 'resetStorageFlowData');
service.resetStorageFlowData({ configId: 'configId1' }); service.resetStorageFlowData({ configId: 'configId1' });
@ -128,26 +141,27 @@ describe('Flows Data Service', () => {
describe('codeVerifier', () => { describe('codeVerifier', () => {
it('getCodeVerifier returns value from the store', () => { it('getCodeVerifier returns value from the store', () => {
const spy = spyOn(storagePersistenceService, 'read') const spy = vi
.spyOn(storagePersistenceService, 'read')
.withArgs('codeVerifier', { configId: 'configId1' }) .withArgs('codeVerifier', { configId: 'configId1' })
.and.returnValue('Genesis'); .mockReturnValue('Genesis');
const result = service.getCodeVerifier({ configId: 'configId1' }); const result = service.getCodeVerifier({ configId: 'configId1' });
expect(result).toBe('Genesis'); expect(result).toBe('Genesis');
expect(spy).toHaveBeenCalledOnceWith('codeVerifier', { expect(spy).toHaveBeenCalledExactlyOnceWith('codeVerifier', {
configId: 'configId1', configId: 'configId1',
}); });
}); });
it('createCodeVerifier returns random createCodeVerifier and stores it', () => { it('createCodeVerifier returns random createCodeVerifier and stores it', () => {
const setSpy = spyOn(storagePersistenceService, 'write'); const setSpy = vi.spyOn(storagePersistenceService, 'write');
const result = service.createCodeVerifier({ configId: 'configId1' }); const result = service.createCodeVerifier({ configId: 'configId1' });
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result.length).toBe(67); expect(result.length).toBe(67);
expect(setSpy).toHaveBeenCalledOnceWith('codeVerifier', result, { expect(setSpy).toHaveBeenCalledExactlyOnceWith('codeVerifier', result, {
configId: 'configId1', configId: 'configId1',
}); });
}); });
@ -165,22 +179,26 @@ describe('Flows Data Service', () => {
jasmine.clock().mockDate(baseTime); jasmine.clock().mockDate(baseTime);
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('storageCodeFlowInProgress', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(true); ['storageCodeFlowInProgress', config],
const spyWrite = spyOn(storagePersistenceService, 'write'); () => true
);
const spyWrite = vi.spyOn(storagePersistenceService, 'write');
const isCodeFlowInProgressResult = service.isCodeFlowInProgress(config); const isCodeFlowInProgressResult = service.isCodeFlowInProgress(config);
expect(spyWrite).not.toHaveBeenCalled(); expect(spyWrite).not.toHaveBeenCalled();
expect(isCodeFlowInProgressResult).toBeTrue(); expect(isCodeFlowInProgressResult).toBeTruthy();
}); });
it('state object does not exist returns false result', () => { it('state object does not exist returns false result', () => {
// arrange // arrange
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('storageCodeFlowInProgress', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(null); ['storageCodeFlowInProgress', { configId: 'configId1' }],
() => null
);
// act // act
const isCodeFlowInProgressResult = service.isCodeFlowInProgress({ const isCodeFlowInProgressResult = service.isCodeFlowInProgress({
@ -188,7 +206,7 @@ describe('Flows Data Service', () => {
}); });
// assert // assert
expect(isCodeFlowInProgressResult).toBeFalse(); expect(isCodeFlowInProgressResult).toBeFalsy();
}); });
}); });
@ -200,23 +218,31 @@ describe('Flows Data Service', () => {
jasmine.clock().mockDate(baseTime); jasmine.clock().mockDate(baseTime);
const spy = spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
service.setCodeFlowInProgress({ configId: 'configId1' }); service.setCodeFlowInProgress({ configId: 'configId1' });
expect(spy).toHaveBeenCalledOnceWith('storageCodeFlowInProgress', true, { expect(spy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', 'storageCodeFlowInProgress',
}); true,
{
configId: 'configId1',
}
);
}); });
}); });
describe('resetCodeFlowInProgress', () => { describe('resetCodeFlowInProgress', () => {
it('set resetCodeFlowInProgress to false when called', () => { it('set resetCodeFlowInProgress to false when called', () => {
const spy = spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
service.resetCodeFlowInProgress({ configId: 'configId1' }); service.resetCodeFlowInProgress({ configId: 'configId1' });
expect(spy).toHaveBeenCalledOnceWith('storageCodeFlowInProgress', false, { expect(spy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', 'storageCodeFlowInProgress',
}); false,
{
configId: 'configId1',
}
);
}); });
}); });
@ -238,21 +264,21 @@ describe('Flows Data Service', () => {
dateOfLaunchedProcessUtc: baseTime.toISOString(), dateOfLaunchedProcessUtc: baseTime.toISOString(),
}; };
spyOn(storagePersistenceService, 'read') vi.spyOn(storagePersistenceService, 'read')
.withArgs('storageSilentRenewRunning', config) .withArgs('storageSilentRenewRunning', config)
.and.returnValue(JSON.stringify(storageObject)); .mockReturnValue(JSON.stringify(storageObject));
const spyWrite = spyOn(storagePersistenceService, 'write'); const spyWrite = vi.spyOn(storagePersistenceService, 'write');
jasmine.clock().tick((config.silentRenewTimeoutInSeconds + 1) * 1000); jasmine.clock().tick((config.silentRenewTimeoutInSeconds + 1) * 1000);
const isSilentRenewRunningResult = service.isSilentRenewRunning(config); const isSilentRenewRunningResult = service.isSilentRenewRunning(config);
expect(spyWrite).toHaveBeenCalledOnceWith( expect(spyWrite).toHaveBeenCalledExactlyOnceWith(
'storageSilentRenewRunning', 'storageSilentRenewRunning',
'', '',
config config
); );
expect(isSilentRenewRunningResult).toBeFalse(); expect(isSilentRenewRunningResult).toBeFalsy();
}); });
it('checks silent renew process and returns result', () => { it('checks silent renew process and returns result', () => {
@ -272,27 +298,29 @@ describe('Flows Data Service', () => {
dateOfLaunchedProcessUtc: baseTime.toISOString(), dateOfLaunchedProcessUtc: baseTime.toISOString(),
}; };
spyOn(storagePersistenceService, 'read') vi.spyOn(storagePersistenceService, 'read')
.withArgs('storageSilentRenewRunning', config) .withArgs('storageSilentRenewRunning', config)
.and.returnValue(JSON.stringify(storageObject)); .mockReturnValue(JSON.stringify(storageObject));
const spyWrite = spyOn(storagePersistenceService, 'write'); const spyWrite = vi.spyOn(storagePersistenceService, 'write');
const isSilentRenewRunningResult = service.isSilentRenewRunning(config); const isSilentRenewRunningResult = service.isSilentRenewRunning(config);
expect(spyWrite).not.toHaveBeenCalled(); expect(spyWrite).not.toHaveBeenCalled();
expect(isSilentRenewRunningResult).toBeTrue(); expect(isSilentRenewRunningResult).toBeTruthy();
}); });
it('state object does not exist returns false result', () => { it('state object does not exist returns false result', () => {
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('storageSilentRenewRunning', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(null); ['storageSilentRenewRunning', { configId: 'configId1' }],
() => null
);
const isSilentRenewRunningResult = service.isSilentRenewRunning({ const isSilentRenewRunningResult = service.isSilentRenewRunning({
configId: 'configId1', configId: 'configId1',
}); });
expect(isSilentRenewRunningResult).toBeFalse(); expect(isSilentRenewRunningResult).toBeFalsy();
}); });
}); });
@ -309,10 +337,10 @@ describe('Flows Data Service', () => {
dateOfLaunchedProcessUtc: baseTime.toISOString(), dateOfLaunchedProcessUtc: baseTime.toISOString(),
}; };
const spy = spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
service.setSilentRenewRunning({ configId: 'configId1' }); service.setSilentRenewRunning({ configId: 'configId1' });
expect(spy).toHaveBeenCalledOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
'storageSilentRenewRunning', 'storageSilentRenewRunning',
JSON.stringify(storageObject), JSON.stringify(storageObject),
{ configId: 'configId1' } { configId: 'configId1' }
@ -322,12 +350,16 @@ describe('Flows Data Service', () => {
describe('resetSilentRenewRunning', () => { describe('resetSilentRenewRunning', () => {
it('set resetSilentRenewRunning to empty string when called', () => { it('set resetSilentRenewRunning to empty string when called', () => {
const spy = spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
service.resetSilentRenewRunning({ configId: 'configId1' }); service.resetSilentRenewRunning({ configId: 'configId1' });
expect(spy).toHaveBeenCalledOnceWith('storageSilentRenewRunning', '', { expect(spy).toHaveBeenCalledExactlyOnceWith(
configId: 'configId1', 'storageSilentRenewRunning',
}); '',
{
configId: 'configId1',
}
);
}); });
}); });
}); });

View File

@ -1,8 +1,8 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { SilentRenewRunning } from './flows.models'; import type { SilentRenewRunning } from './flows.models';
import { RandomService } from './random/random.service'; import { RandomService } from './random/random.service';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class FlowsDataService {
createNonce(configuration: OpenIdConfiguration): string { createNonce(configuration: OpenIdConfiguration): string {
const nonce = this.randomService.createRandom(40, configuration); const nonce = this.randomService.createRandom(40, configuration);
this.loggerService.logDebug(configuration, 'Nonce created. nonce:' + nonce); this.loggerService.logDebug(configuration, `Nonce created. nonce:${nonce}`);
this.setNonce(nonce, configuration); this.setNonce(nonce, configuration);
return nonce; return nonce;

View File

@ -1,7 +1,8 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { CallbackContext } from './callback-context'; import { mockProvider } from '../testing/mock';
import type { CallbackContext } from './callback-context';
import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service'; import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service';
import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service'; import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service';
import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service'; import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service';
@ -64,27 +65,25 @@ describe('Flows Service', () => {
}); });
describe('processCodeFlowCallback', () => { describe('processCodeFlowCallback', () => {
it('calls all methods correctly', waitForAsync(() => { it('calls all methods correctly', async () => {
const codeFlowCallbackSpy = spyOn( const codeFlowCallbackSpy = vi
codeFlowCallbackHandlerService, .spyOn(codeFlowCallbackHandlerService, 'codeFlowCallback')
'codeFlowCallback' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext)); const codeFlowCodeRequestSpy = vi
const codeFlowCodeRequestSpy = spyOn( .spyOn(codeFlowCallbackHandlerService, 'codeFlowCodeRequest')
codeFlowCallbackHandlerService, .mockReturnValue(of({} as CallbackContext));
'codeFlowCodeRequest' const callbackHistoryAndResetJwtKeysSpy = vi
).and.returnValue(of({} as CallbackContext)); .spyOn(
const callbackHistoryAndResetJwtKeysSpy = spyOn( historyJwtKeysCallbackHandlerService,
historyJwtKeysCallbackHandlerService, 'callbackHistoryAndResetJwtKeys'
'callbackHistoryAndResetJwtKeys' )
).and.returnValue(of({} as CallbackContext)); .mockReturnValue(of({} as CallbackContext));
const callbackStateValidationSpy = spyOn( const callbackStateValidationSpy = vi
stateValidationCallbackHandlerService, .spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation')
'callbackStateValidation' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext)); const callbackUserSpy = vi
const callbackUserSpy = spyOn( .spyOn(userCallbackHandlerService, 'callbackUser')
userCallbackHandlerService, .mockReturnValue(of({} as CallbackContext));
'callbackUser'
).and.returnValue(of({} as CallbackContext));
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -92,10 +91,10 @@ describe('Flows Service', () => {
]; ];
service service
.processCodeFlowCallback('some-url1234', allConfigs[0], allConfigs) .processCodeFlowCallback('some-url1234', allConfigs[0]!, allConfigs)
.subscribe((value) => { .subscribe((value) => {
expect(value).toEqual({} as CallbackContext); expect(value).toEqual({} as CallbackContext);
expect(codeFlowCallbackSpy).toHaveBeenCalledOnceWith( expect(codeFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith(
'some-url1234', 'some-url1234',
allConfigs[0] allConfigs[0]
); );
@ -104,27 +103,26 @@ describe('Flows Service', () => {
expect(callbackStateValidationSpy).toHaveBeenCalledTimes(1); expect(callbackStateValidationSpy).toHaveBeenCalledTimes(1);
expect(callbackUserSpy).toHaveBeenCalledTimes(1); expect(callbackUserSpy).toHaveBeenCalledTimes(1);
}); });
})); });
}); });
describe('processSilentRenewCodeFlowCallback', () => { describe('processSilentRenewCodeFlowCallback', () => {
it('calls all methods correctly', waitForAsync(() => { it('calls all methods correctly', async () => {
const codeFlowCodeRequestSpy = spyOn( const codeFlowCodeRequestSpy = vi
codeFlowCallbackHandlerService, .spyOn(codeFlowCallbackHandlerService, 'codeFlowCodeRequest')
'codeFlowCodeRequest' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext)); const callbackHistoryAndResetJwtKeysSpy = vi
const callbackHistoryAndResetJwtKeysSpy = spyOn( .spyOn(
historyJwtKeysCallbackHandlerService, historyJwtKeysCallbackHandlerService,
'callbackHistoryAndResetJwtKeys' 'callbackHistoryAndResetJwtKeys'
).and.returnValue(of({} as CallbackContext)); )
const callbackStateValidationSpy = spyOn( .mockReturnValue(of({} as CallbackContext));
stateValidationCallbackHandlerService, const callbackStateValidationSpy = vi
'callbackStateValidation' .spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation')
).and.returnValue(of({} as CallbackContext)); .mockReturnValue(of({} as CallbackContext));
const callbackUserSpy = spyOn( const callbackUserSpy = vi
userCallbackHandlerService, .spyOn(userCallbackHandlerService, 'callbackUser')
'callbackUser' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext));
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -134,7 +132,7 @@ describe('Flows Service', () => {
service service
.processSilentRenewCodeFlowCallback( .processSilentRenewCodeFlowCallback(
{} as CallbackContext, {} as CallbackContext,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe((value) => { .subscribe((value) => {
@ -144,27 +142,26 @@ describe('Flows Service', () => {
expect(callbackStateValidationSpy).toHaveBeenCalled(); expect(callbackStateValidationSpy).toHaveBeenCalled();
expect(callbackUserSpy).toHaveBeenCalled(); expect(callbackUserSpy).toHaveBeenCalled();
}); });
})); });
}); });
describe('processImplicitFlowCallback', () => { describe('processImplicitFlowCallback', () => {
it('calls all methods correctly', waitForAsync(() => { it('calls all methods correctly', async () => {
const implicitFlowCallbackSpy = spyOn( const implicitFlowCallbackSpy = vi
implicitFlowCallbackHandlerService, .spyOn(implicitFlowCallbackHandlerService, 'implicitFlowCallback')
'implicitFlowCallback' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext)); const callbackHistoryAndResetJwtKeysSpy = vi
const callbackHistoryAndResetJwtKeysSpy = spyOn( .spyOn(
historyJwtKeysCallbackHandlerService, historyJwtKeysCallbackHandlerService,
'callbackHistoryAndResetJwtKeys' 'callbackHistoryAndResetJwtKeys'
).and.returnValue(of({} as CallbackContext)); )
const callbackStateValidationSpy = spyOn( .mockReturnValue(of({} as CallbackContext));
stateValidationCallbackHandlerService, const callbackStateValidationSpy = vi
'callbackStateValidation' .spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation')
).and.returnValue(of({} as CallbackContext)); .mockReturnValue(of({} as CallbackContext));
const callbackUserSpy = spyOn( const callbackUserSpy = vi
userCallbackHandlerService, .spyOn(userCallbackHandlerService, 'callbackUser')
'callbackUser' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext));
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -172,7 +169,7 @@ describe('Flows Service', () => {
]; ];
service service
.processImplicitFlowCallback(allConfigs[0], allConfigs, 'any-hash') .processImplicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')
.subscribe((value) => { .subscribe((value) => {
expect(value).toEqual({} as CallbackContext); expect(value).toEqual({} as CallbackContext);
expect(implicitFlowCallbackSpy).toHaveBeenCalled(); expect(implicitFlowCallbackSpy).toHaveBeenCalled();
@ -180,31 +177,32 @@ describe('Flows Service', () => {
expect(callbackStateValidationSpy).toHaveBeenCalled(); expect(callbackStateValidationSpy).toHaveBeenCalled();
expect(callbackUserSpy).toHaveBeenCalled(); expect(callbackUserSpy).toHaveBeenCalled();
}); });
})); });
}); });
describe('processRefreshToken', () => { describe('processRefreshToken', () => {
it('calls all methods correctly', waitForAsync(() => { it('calls all methods correctly', async () => {
const refreshSessionWithRefreshTokensSpy = spyOn( const refreshSessionWithRefreshTokensSpy = vi
refreshSessionCallbackHandlerService, .spyOn(
'refreshSessionWithRefreshTokens' refreshSessionCallbackHandlerService,
).and.returnValue(of({} as CallbackContext)); 'refreshSessionWithRefreshTokens'
const refreshTokensRequestTokensSpy = spyOn( )
refreshTokenCallbackHandlerService, .mockReturnValue(of({} as CallbackContext));
'refreshTokensRequestTokens' const refreshTokensRequestTokensSpy = vi
).and.returnValue(of({} as CallbackContext)); .spyOn(refreshTokenCallbackHandlerService, 'refreshTokensRequestTokens')
const callbackHistoryAndResetJwtKeysSpy = spyOn( .mockReturnValue(of({} as CallbackContext));
historyJwtKeysCallbackHandlerService, const callbackHistoryAndResetJwtKeysSpy = vi
'callbackHistoryAndResetJwtKeys' .spyOn(
).and.returnValue(of({} as CallbackContext)); historyJwtKeysCallbackHandlerService,
const callbackStateValidationSpy = spyOn( 'callbackHistoryAndResetJwtKeys'
stateValidationCallbackHandlerService, )
'callbackStateValidation' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext)); const callbackStateValidationSpy = vi
const callbackUserSpy = spyOn( .spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation')
userCallbackHandlerService, .mockReturnValue(of({} as CallbackContext));
'callbackUser' const callbackUserSpy = vi
).and.returnValue(of({} as CallbackContext)); .spyOn(userCallbackHandlerService, 'callbackUser')
.mockReturnValue(of({} as CallbackContext));
const allConfigs = [ const allConfigs = [
{ {
configId: 'configId1', configId: 'configId1',
@ -212,7 +210,7 @@ describe('Flows Service', () => {
]; ];
service service
.processRefreshToken(allConfigs[0], allConfigs) .processRefreshToken(allConfigs[0]!, allConfigs)
.subscribe((value) => { .subscribe((value) => {
expect(value).toEqual({} as CallbackContext); expect(value).toEqual({} as CallbackContext);
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled(); expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
@ -221,6 +219,6 @@ describe('Flows Service', () => {
expect(callbackStateValidationSpy).toHaveBeenCalled(); expect(callbackStateValidationSpy).toHaveBeenCalled();
expect(callbackUserSpy).toHaveBeenCalled(); expect(callbackUserSpy).toHaveBeenCalled();
}); });
})); });
}); });
}); });

View File

@ -1,8 +1,8 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable } from 'rxjs'; import type { Observable } from 'rxjs';
import { concatMap } from 'rxjs/operators'; import { concatMap } from 'rxjs/operators';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from './callback-context'; import type { CallbackContext } from './callback-context';
import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service'; import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service';
import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service'; import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service';
import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service'; import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service';

View File

@ -1,6 +1,7 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { CryptoService } from '../../utils/crypto/crypto.service'; import { CryptoService } from '../../utils/crypto/crypto.service';
import { RandomService } from './random.service'; import { RandomService } from './random.service';

View File

@ -1,7 +1,8 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { UserService } from '../user-data/user.service'; import { UserService } from '../user-data/user.service';
import { FlowsDataService } from './flows-data.service'; import { FlowsDataService } from './flows-data.service';
import { ResetAuthDataService } from './reset-auth-data.service'; import { ResetAuthDataService } from './reset-auth-data.service';
@ -37,7 +38,7 @@ describe('ResetAuthDataService', () => {
describe('resetAuthorizationData', () => { describe('resetAuthorizationData', () => {
it('calls resetUserDataInStore when autoUserInfo is true', () => { it('calls resetUserDataInStore when autoUserInfo is true', () => {
const resetUserDataInStoreSpy = spyOn( const resetUserDataInStoreSpy = vi.spyOn(
userService, userService,
'resetUserDataInStore' 'resetUserDataInStore'
); );
@ -47,16 +48,16 @@ describe('ResetAuthDataService', () => {
}, },
]; ];
service.resetAuthorizationData(allConfigs[0], allConfigs); service.resetAuthorizationData(allConfigs[0]!, allConfigs);
expect(resetUserDataInStoreSpy).toHaveBeenCalled(); expect(resetUserDataInStoreSpy).toHaveBeenCalled();
}); });
it('calls correct methods', () => { it('calls correct methods', () => {
const resetStorageFlowDataSpy = spyOn( const resetStorageFlowDataSpy = vi.spyOn(
flowsDataService, flowsDataService,
'resetStorageFlowData' 'resetStorageFlowData'
); );
const setUnauthorizedAndFireEventSpy = spyOn( const setUnauthorizedAndFireEventSpy = vi.spyOn(
authStateService, authStateService,
'setUnauthenticatedAndFireEvent' 'setUnauthenticatedAndFireEvent'
); );
@ -66,7 +67,7 @@ describe('ResetAuthDataService', () => {
}, },
]; ];
service.resetAuthorizationData(allConfigs[0], allConfigs); service.resetAuthorizationData(allConfigs[0]!, allConfigs);
expect(resetStorageFlowDataSpy).toHaveBeenCalled(); expect(resetStorageFlowDataSpy).toHaveBeenCalled();
expect(setUnauthorizedAndFireEventSpy).toHaveBeenCalled(); expect(setUnauthorizedAndFireEventSpy).toHaveBeenCalled();

View File

@ -1,6 +1,6 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { UserService } from '../user-data/user.service'; import { UserService } from '../user-data/user.service';
import { FlowsDataService } from './flows-data.service'; import { FlowsDataService } from './flows-data.service';

View File

@ -1,11 +1,12 @@
import { HttpResponse } from '@angular/common/http'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { TestBed, waitForAsync } from '@angular/core/testing'; import { HttpResponse } from '@ngify/http';
import { isObservable, of, throwError } from 'rxjs'; import { isObservable, of, throwError } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { createRetriableStream } from '../../test/create-retriable-stream.helper';
import { DataService } from '../api/data.service'; import { DataService } from '../api/data.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { createRetriableStream } from '../testing/create-retriable-stream.helper';
import { mockProvider } from '../testing/mock';
import { SigninKeyDataService } from './signin-key-data.service'; import { SigninKeyDataService } from './signin-key-data.service';
const DUMMY_JWKS = { const DUMMY_JWKS = {
@ -53,10 +54,12 @@ describe('Signin Key Data Service', () => {
}); });
describe('getSigningKeys', () => { describe('getSigningKeys', () => {
it('throws error when no wellKnownEndpoints given', waitForAsync(() => { it('throws error when no wellKnownEndpoints given', async () => {
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(null); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => null
);
const result = service.getSigningKeys({ configId: 'configId1' }); const result = service.getSigningKeys({ configId: 'configId1' });
result.subscribe({ result.subscribe({
@ -64,12 +67,14 @@ describe('Signin Key Data Service', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('throws error when no jwksUri given', waitForAsync(() => { it('throws error when no jwksUri given', async () => {
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ jwksUri: null }); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ jwksUri: null })
);
const result = service.getSigningKeys({ configId: 'configId1' }); const result = service.getSigningKeys({ configId: 'configId1' });
result.subscribe({ result.subscribe({
@ -77,30 +82,34 @@ describe('Signin Key Data Service', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('calls dataservice if jwksurl is given', waitForAsync(() => { it('calls dataservice if jwksurl is given', async () => {
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ jwksUri: 'someUrl' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
const spy = spyOn(dataService, 'get').and.callFake(() => of()); () => ({ jwksUri: 'someUrl' })
);
const spy = vi.spyOn(dataService, 'get').mockImplementation(() => of());
const result = service.getSigningKeys({ configId: 'configId1' }); const result = service.getSigningKeys({ configId: 'configId1' });
result.subscribe({ result.subscribe({
complete: () => { complete: () => {
expect(spy).toHaveBeenCalledOnceWith('someUrl', { expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl', {
configId: 'configId1', configId: 'configId1',
}); });
}, },
}); });
})); });
it('should retry once', waitForAsync(() => { it('should retry once', async () => {
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ jwksUri: 'someUrl' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
spyOn(dataService, 'get').and.returnValue( () => ({ jwksUri: 'someUrl' })
);
vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
of(DUMMY_JWKS) of(DUMMY_JWKS)
@ -113,13 +122,15 @@ describe('Signin Key Data Service', () => {
expect(res).toEqual(DUMMY_JWKS); expect(res).toEqual(DUMMY_JWKS);
}, },
}); });
})); });
it('should retry twice', waitForAsync(() => { it('should retry twice', async () => {
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ jwksUri: 'someUrl' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
spyOn(dataService, 'get').and.returnValue( () => ({ jwksUri: 'someUrl' })
);
vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
@ -133,13 +144,15 @@ describe('Signin Key Data Service', () => {
expect(res).toEqual(DUMMY_JWKS); expect(res).toEqual(DUMMY_JWKS);
}, },
}); });
})); });
it('should fail after three tries', waitForAsync(() => { it('should fail after three tries', async () => {
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ jwksUri: 'someUrl' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
spyOn(dataService, 'get').and.returnValue( () => ({ jwksUri: 'someUrl' })
);
vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
@ -153,21 +166,21 @@ describe('Signin Key Data Service', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
}); });
describe('handleErrorGetSigningKeys', () => { describe('handleErrorGetSigningKeys', () => {
it('keeps observable if error is catched', waitForAsync(() => { it('keeps observable if error is catched', async () => {
const result = (service as any).handleErrorGetSigningKeys( const result = (service as any).handleErrorGetSigningKeys(
new HttpResponse() new HttpResponse()
); );
const hasTypeObservable = isObservable(result); const hasTypeObservable = isObservable(result);
expect(hasTypeObservable).toBeTrue(); expect(hasTypeObservable).toBeTruthy();
})); });
it('logs error if error is response', waitForAsync(() => { it('logs error if error is response', async () => {
const logSpy = spyOn(loggerService, 'logError'); const logSpy = vi.spyOn(loggerService, 'logError');
(service as any) (service as any)
.handleErrorGetSigningKeys( .handleErrorGetSigningKeys(
@ -176,31 +189,31 @@ describe('Signin Key Data Service', () => {
) )
.subscribe({ .subscribe({
error: () => { error: () => {
expect(logSpy).toHaveBeenCalledOnceWith( expect(logSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'400 - nono {}' '400 - nono {}'
); );
}, },
}); });
})); });
it('logs error if error is not a response', waitForAsync(() => { it('logs error if error is not a response', async () => {
const logSpy = spyOn(loggerService, 'logError'); const logSpy = vi.spyOn(loggerService, 'logError');
(service as any) (service as any)
.handleErrorGetSigningKeys('Just some Error', { configId: 'configId1' }) .handleErrorGetSigningKeys('Just some Error', { configId: 'configId1' })
.subscribe({ .subscribe({
error: () => { error: () => {
expect(logSpy).toHaveBeenCalledOnceWith( expect(logSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'Just some Error' 'Just some Error'
); );
}, },
}); });
})); });
it('logs error if error with message property is not a response', waitForAsync(() => { it('logs error if error with message property is not a response', async () => {
const logSpy = spyOn(loggerService, 'logError'); const logSpy = vi.spyOn(loggerService, 'logError');
(service as any) (service as any)
.handleErrorGetSigningKeys( .handleErrorGetSigningKeys(
@ -209,12 +222,12 @@ describe('Signin Key Data Service', () => {
) )
.subscribe({ .subscribe({
error: () => { error: () => {
expect(logSpy).toHaveBeenCalledOnceWith( expect(logSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'Just some Error' 'Just some Error'
); );
}, },
}); });
})); });
}); });
}); });

11
src/http/index.ts Normal file
View File

@ -0,0 +1,11 @@
import type { HttpFeature } from '@ngify/http';
export function provideHttpClient() {
// todo
throw new Error('todo!');
}
export function withInterceptorsFromDi(): HttpFeature {
// todo
throw new Error('todo!');
}

View File

@ -1,13 +1,14 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { skip } from 'rxjs/operators'; import { skip } from 'rxjs/operators';
import { mockAbstractProvider, mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { OidcSecurityService } from '../oidc.security.service'; import { OidcSecurityService } from '../oidc.security.service';
import { PublicEventsService } from '../public-events/public-events.service'; import { PublicEventsService } from '../public-events/public-events.service';
import { AbstractSecurityStorage } from '../storage/abstract-security-storage'; import { AbstractSecurityStorage } from '../storage/abstract-security-storage';
import { DefaultSessionStorageService } from '../storage/default-sessionstorage.service'; import { DefaultSessionStorageService } from '../storage/default-sessionstorage.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockAbstractProvider, mockProvider } from '../testing/mock';
import { PlatformProvider } from '../utils/platform-provider/platform.provider'; import { PlatformProvider } from '../utils/platform-provider/platform.provider';
import { CheckSessionService } from './check-session.service'; import { CheckSessionService } from './check-session.service';
import { IFrameService } from './existing-iframe.service'; import { IFrameService } from './existing-iframe.service';
@ -67,7 +68,7 @@ describe('CheckSessionService', () => {
}); });
it('getOrCreateIframe calls iFrameService.addIFrameToWindowBody if no Iframe exists', () => { it('getOrCreateIframe calls iFrameService.addIFrameToWindowBody if no Iframe exists', () => {
spyOn(iFrameService, 'addIFrameToWindowBody').and.callThrough(); vi.spyOn(iFrameService, 'addIFrameToWindowBody')();
const result = (checkSessionService as any).getOrCreateIframe({ const result = (checkSessionService as any).getOrCreateIframe({
configId: 'configId1', configId: 'configId1',
@ -89,7 +90,7 @@ describe('CheckSessionService', () => {
it('init appends iframe on body with correct values', () => { it('init appends iframe on body with correct values', () => {
expect((checkSessionService as any).sessionIframe).toBeFalsy(); expect((checkSessionService as any).sessionIframe).toBeFalsy();
spyOn<any>(loggerService, 'logDebug').and.callFake(() => undefined); vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined);
(checkSessionService as any).init(); (checkSessionService as any).init();
const iframe = (checkSessionService as any).getOrCreateIframe({ const iframe = (checkSessionService as any).getOrCreateIframe({
@ -105,29 +106,34 @@ describe('CheckSessionService', () => {
}); });
it('log warning if authWellKnownEndpoints.check_session_iframe is not existing', () => { it('log warning if authWellKnownEndpoints.check_session_iframe is not existing', () => {
const spyLogWarning = spyOn<any>(loggerService, 'logWarning'); const spyLogWarning = vi.spyOn<any>(loggerService, 'logWarning');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn<any>(loggerService, 'logDebug').and.callFake(() => undefined); vi.spyOn<any>(loggerService, 'logDebug').mockImplementation(
spyOn(storagePersistenceService, 'read') () => undefined
);
vi.spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config) .withArgs('authWellKnownEndPoints', config)
.and.returnValue({ checkSessionIframe: undefined }); .mockReturnValue({ checkSessionIframe: undefined });
(checkSessionService as any).init(config); (checkSessionService as any).init(config);
expect(spyLogWarning).toHaveBeenCalledOnceWith(config, jasmine.any(String)); expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith(
config,
expect.any(String)
);
}); });
it('start() calls pollserversession() with clientId if no scheduledheartbeat is set', () => { it('start() calls pollserversession() with clientId if no scheduledheartbeat is set', () => {
const spy = spyOn<any>(checkSessionService, 'pollServerSession'); const spy = vi.spyOn<any>(checkSessionService, 'pollServerSession');
const config = { clientId: 'clientId', configId: 'configId1' }; const config = { clientId: 'clientId', configId: 'configId1' };
checkSessionService.start(config); checkSessionService.start(config);
expect(spy).toHaveBeenCalledOnceWith('clientId', config); expect(spy).toHaveBeenCalledExactlyOnceWith('clientId', config);
}); });
it('start() does not call pollServerSession() if scheduledHeartBeatRunning is set', () => { it('start() does not call pollServerSession() if scheduledHeartBeatRunning is set', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const spy = spyOn<any>(checkSessionService, 'pollServerSession'); const spy = vi.spyOn<any>(checkSessionService, 'pollServerSession');
(checkSessionService as any).scheduledHeartBeatRunning = (): void => (checkSessionService as any).scheduledHeartBeatRunning = (): void =>
undefined; undefined;
@ -148,10 +154,10 @@ describe('CheckSessionService', () => {
it('stopCheckingSession does nothing if scheduledHeartBeatRunning is not set', () => { it('stopCheckingSession does nothing if scheduledHeartBeatRunning is not set', () => {
(checkSessionService as any).scheduledHeartBeatRunning = null; (checkSessionService as any).scheduledHeartBeatRunning = null;
const spy = spyOn<any>(checkSessionService, 'clearScheduledHeartBeat'); const spy = vi.spyOn<any>(checkSessionService, 'clearScheduledHeartBeat');
checkSessionService.stop(); checkSessionService.stop();
expect(spy).not.toHaveBeenCalledOnceWith(); expect(spy).not.toHaveBeenCalledExactlyOnceWith();
}); });
describe('serverStateChanged', () => { describe('serverStateChanged', () => {
@ -167,7 +173,7 @@ describe('CheckSessionService', () => {
const config = { startCheckSession: true, configId: 'configId1' }; const config = { startCheckSession: true, configId: 'configId1' };
const result = checkSessionService.serverStateChanged(config); const result = checkSessionService.serverStateChanged(config);
expect(result).toBeFalse(); expect(result).toBeFalsy();
}); });
it('returns true if startCheckSession is configured and checkSessionReceived is true', () => { it('returns true if startCheckSession is configured and checkSessionReceived is true', () => {
@ -175,17 +181,17 @@ describe('CheckSessionService', () => {
const config = { startCheckSession: true, configId: 'configId1' }; const config = { startCheckSession: true, configId: 'configId1' };
const result = checkSessionService.serverStateChanged(config); const result = checkSessionService.serverStateChanged(config);
expect(result).toBeTrue(); expect(result).toBeTruthy();
}); });
}); });
describe('pollServerSession', () => { describe('pollServerSession', () => {
beforeEach(() => { beforeEach(() => {
spyOn<any>(checkSessionService, 'init').and.returnValue(of(undefined)); vi.spyOn<any>(checkSessionService, 'init').mockReturnValue(of(undefined));
}); });
it('increases outstandingMessages', () => { it('increases outstandingMessages', () => {
spyOn<any>(checkSessionService, 'getExistingIframe').and.returnValue({ vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue({
contentWindow: { postMessage: () => undefined }, contentWindow: { postMessage: () => undefined },
}); });
const authWellKnownEndpoints = { const authWellKnownEndpoints = {
@ -193,18 +199,20 @@ describe('CheckSessionService', () => {
}; };
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(authWellKnownEndpoints) ['authWellKnownEndPoints', config],
() => authWellKnownEndpoints
)
.withArgs('session_state', config) .withArgs('session_state', config)
.and.returnValue('session_state'); .mockReturnValue('session_state');
spyOn(loggerService, 'logDebug').and.callFake(() => undefined); vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined);
(checkSessionService as any).pollServerSession('clientId', config); (checkSessionService as any).pollServerSession('clientId', config);
expect((checkSessionService as any).outstandingMessages).toBe(1); expect((checkSessionService as any).outstandingMessages).toBe(1);
}); });
it('logs warning if iframe does not exist', () => { it('logs warning if iframe does not exist', () => {
spyOn<any>(checkSessionService, 'getExistingIframe').and.returnValue( vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
null null
); );
const authWellKnownEndpoints = { const authWellKnownEndpoints = {
@ -212,77 +220,91 @@ describe('CheckSessionService', () => {
}; };
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(authWellKnownEndpoints); ['authWellKnownEndPoints', config],
const spyLogWarning = spyOn(loggerService, 'logWarning').and.callFake( () => authWellKnownEndpoints
() => undefined
); );
const spyLogWarning = vi
.spyOn(loggerService, 'logWarning')
.mockImplementation(() => undefined);
spyOn(loggerService, 'logDebug').and.callFake(() => undefined); vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined);
(checkSessionService as any).pollServerSession('clientId', config); (checkSessionService as any).pollServerSession('clientId', config);
expect(spyLogWarning).toHaveBeenCalledOnceWith( expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith(
config, config,
jasmine.any(String) expect.any(String)
); );
}); });
it('logs warning if clientId is not set', () => { it('logs warning if clientId is not set', () => {
spyOn<any>(checkSessionService, 'getExistingIframe').and.returnValue({}); vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
{}
);
const authWellKnownEndpoints = { const authWellKnownEndpoints = {
checkSessionIframe: 'https://some-testing-url.com', checkSessionIframe: 'https://some-testing-url.com',
}; };
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(authWellKnownEndpoints); ['authWellKnownEndPoints', config],
const spyLogWarning = spyOn(loggerService, 'logWarning').and.callFake( () => authWellKnownEndpoints
() => undefined
); );
const spyLogWarning = vi
.spyOn(loggerService, 'logWarning')
.mockImplementation(() => undefined);
spyOn(loggerService, 'logDebug').and.callFake(() => undefined); vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined);
(checkSessionService as any).pollServerSession('', config); (checkSessionService as any).pollServerSession('', config);
expect(spyLogWarning).toHaveBeenCalledOnceWith( expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith(
config, config,
jasmine.any(String) expect.any(String)
); );
}); });
it('logs debug if session_state is not set', () => { it('logs debug if session_state is not set', () => {
spyOn<any>(checkSessionService, 'getExistingIframe').and.returnValue({}); vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
{}
);
const authWellKnownEndpoints = { const authWellKnownEndpoints = {
checkSessionIframe: 'https://some-testing-url.com', checkSessionIframe: 'https://some-testing-url.com',
}; };
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(authWellKnownEndpoints) ['authWellKnownEndPoints', config],
() => authWellKnownEndpoints
)
.withArgs('session_state', config) .withArgs('session_state', config)
.and.returnValue(null); .mockReturnValue(null);
const spyLogDebug = spyOn(loggerService, 'logDebug').and.callFake( const spyLogDebug = vi
() => undefined .spyOn(loggerService, 'logDebug')
); .mockImplementation(() => undefined);
(checkSessionService as any).pollServerSession('clientId', config); (checkSessionService as any).pollServerSession('clientId', config);
expect(spyLogDebug).toHaveBeenCalledTimes(2); expect(spyLogDebug).toHaveBeenCalledTimes(2);
}); });
it('logs debug if session_state is set but authWellKnownEndpoints are not set', () => { it('logs debug if session_state is set but authWellKnownEndpoints are not set', () => {
spyOn<any>(checkSessionService, 'getExistingIframe').and.returnValue({}); vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
{}
);
const authWellKnownEndpoints = null; const authWellKnownEndpoints = null;
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(authWellKnownEndpoints) ['authWellKnownEndPoints', config],
() => authWellKnownEndpoints
)
.withArgs('session_state', config) .withArgs('session_state', config)
.and.returnValue('some_session_state'); .mockReturnValue('some_session_state');
const spyLogDebug = spyOn(loggerService, 'logDebug').and.callFake( const spyLogDebug = vi
() => undefined .spyOn(loggerService, 'logDebug')
); .mockImplementation(() => undefined);
(checkSessionService as any).pollServerSession('clientId', config); (checkSessionService as any).pollServerSession('clientId', config);
expect(spyLogDebug).toHaveBeenCalledTimes(2); expect(spyLogDebug).toHaveBeenCalledTimes(2);
@ -290,7 +312,7 @@ describe('CheckSessionService', () => {
}); });
describe('init', () => { describe('init', () => {
it('returns falsy observable when lastIframerefresh and iframeRefreshInterval are bigger than now', waitForAsync(() => { it('returns falsy observable when lastIframerefresh and iframeRefreshInterval are bigger than now', async () => {
const serviceAsAny = checkSessionService as any; const serviceAsAny = checkSessionService as any;
const dateNow = new Date(); const dateNow = new Date();
const lastRefresh = dateNow.setMinutes(dateNow.getMinutes() + 30); const lastRefresh = dateNow.setMinutes(dateNow.getMinutes() + 30);
@ -301,7 +323,7 @@ describe('CheckSessionService', () => {
serviceAsAny.init().subscribe((result: any) => { serviceAsAny.init().subscribe((result: any) => {
expect(result).toBeUndefined(); expect(result).toBeUndefined();
}); });
})); });
}); });
describe('isCheckSessionConfigured', () => { describe('isCheckSessionConfigured', () => {
@ -323,7 +345,7 @@ describe('CheckSessionService', () => {
}); });
describe('checkSessionChanged$', () => { describe('checkSessionChanged$', () => {
it('emits when internal event is thrown', waitForAsync(() => { it('emits when internal event is thrown', async () => {
checkSessionService.checkSessionChanged$ checkSessionService.checkSessionChanged$
.pipe(skip(1)) .pipe(skip(1))
.subscribe((result) => { .subscribe((result) => {
@ -333,15 +355,15 @@ describe('CheckSessionService', () => {
const serviceAsAny = checkSessionService as any; const serviceAsAny = checkSessionService as any;
serviceAsAny.checkSessionChangedInternal$.next(true); serviceAsAny.checkSessionChangedInternal$.next(true);
})); });
it('emits false initially', waitForAsync(() => { it('emits false initially', async () => {
checkSessionService.checkSessionChanged$.subscribe((result) => { checkSessionService.checkSessionChanged$.subscribe((result) => {
expect(result).toBe(false); expect(result).toBe(false);
}); });
})); });
it('emits false then true when emitted', waitForAsync(() => { it('emits false then true when emitted', async () => {
const expectedResultsInOrder = [false, true]; const expectedResultsInOrder = [false, true];
let counter = 0; let counter = 0;
@ -351,6 +373,6 @@ describe('CheckSessionService', () => {
}); });
(checkSessionService as any).checkSessionChangedInternal$.next(true); (checkSessionService as any).checkSessionChangedInternal$.next(true);
})); });
}); });
}); });

View File

@ -1,7 +1,8 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { UrlService } from '../utils/url/url.service'; import { UrlService } from '../utils/url/url.service';
import { RefreshSessionIframeService } from './refresh-session-iframe.service'; import { RefreshSessionIframeService } from './refresh-session-iframe.service';
import { SilentRenewService } from './silent-renew.service'; import { SilentRenewService } from './silent-renew.service';
@ -31,37 +32,43 @@ describe('RefreshSessionIframeService ', () => {
}); });
describe('refreshSessionWithIframe', () => { describe('refreshSessionWithIframe', () => {
it('calls sendAuthorizeRequestUsingSilentRenew with created url', waitForAsync(() => { it('calls sendAuthorizeRequestUsingSilentRenew with created url', async () => {
spyOn(urlService, 'getRefreshSessionSilentRenewUrl').and.returnValue( vi.spyOn(urlService, 'getRefreshSessionSilentRenewUrl').mockReturnValue(
of('a-url') of('a-url')
); );
const sendAuthorizeRequestUsingSilentRenewSpy = spyOn( const sendAuthorizeRequestUsingSilentRenewSpy = vi
refreshSessionIframeService as any, .spyOn(
'sendAuthorizeRequestUsingSilentRenew' refreshSessionIframeService as any,
).and.returnValue(of(null)); 'sendAuthorizeRequestUsingSilentRenew'
)
.mockReturnValue(of(null));
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
refreshSessionIframeService refreshSessionIframeService
.refreshSessionWithIframe(allConfigs[0], allConfigs) .refreshSessionWithIframe(allConfigs[0]!, allConfigs)
.subscribe(() => { .subscribe(() => {
expect( expect(
sendAuthorizeRequestUsingSilentRenewSpy sendAuthorizeRequestUsingSilentRenewSpy
).toHaveBeenCalledOnceWith('a-url', allConfigs[0], allConfigs); ).toHaveBeenCalledExactlyOnceWith(
'a-url',
allConfigs[0]!,
allConfigs
);
}); });
})); });
}); });
describe('initSilentRenewRequest', () => { describe('initSilentRenewRequest', () => {
it('dispatches customevent to window object', waitForAsync(() => { it('dispatches customevent to window object', async () => {
const dispatchEventSpy = spyOn(window, 'dispatchEvent'); const dispatchEventSpy = vi.spyOn(window, 'dispatchEvent');
(refreshSessionIframeService as any).initSilentRenewRequest(); (refreshSessionIframeService as any).initSilentRenewRequest();
expect(dispatchEventSpy).toHaveBeenCalledOnceWith( expect(dispatchEventSpy).toHaveBeenCalledExactlyOnceWith(
new CustomEvent('oidc-silent-renew-init', { new CustomEvent('oidc-silent-renew-init', {
detail: jasmine.any(Number), detail: expect.any(Number),
}) })
); );
})); });
}); });
}); });

View File

@ -1,8 +1,8 @@
import { DOCUMENT } from '../dom';
import { Injectable, RendererFactory2, inject } from 'injection-js'; import { Injectable, RendererFactory2, inject } from 'injection-js';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { DOCUMENT } from '../dom';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { UrlService } from '../utils/url/url.service'; import { UrlService } from '../utils/url/url.service';
import { SilentRenewService } from './silent-renew.service'; import { SilentRenewService } from './silent-renew.service';

View File

@ -1,14 +1,15 @@
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { TestBed, fakeAsync, tick } from '@/testing';
import { Observable, of, throwError } from 'rxjs'; import { Observable, of, throwError } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service'; import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service';
import { IntervalService } from '../callback/interval.service'; import { IntervalService } from '../callback/interval.service';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { FlowsService } from '../flows/flows.service'; import { FlowsService } from '../flows/flows.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
import { ValidationResult } from '../validation/validation-result'; import { ValidationResult } from '../validation/validation-result';
import { IFrameService } from './existing-iframe.service'; import { IFrameService } from './existing-iframe.service';
@ -63,7 +64,7 @@ describe('SilentRenewService ', () => {
describe('refreshSessionWithIFrameCompleted', () => { describe('refreshSessionWithIFrameCompleted', () => {
it('is of type observable', () => { it('is of type observable', () => {
expect(silentRenewService.refreshSessionWithIFrameCompleted$).toEqual( expect(silentRenewService.refreshSessionWithIFrameCompleted$).toEqual(
jasmine.any(Observable) expect.any(Observable)
); );
}); });
}); });
@ -95,7 +96,7 @@ describe('SilentRenewService ', () => {
describe('getOrCreateIframe', () => { describe('getOrCreateIframe', () => {
it('returns iframe if iframe is truthy', () => { it('returns iframe if iframe is truthy', () => {
spyOn(silentRenewService as any, 'getExistingIframe').and.returnValue({ vi.spyOn(silentRenewService as any, 'getExistingIframe').mockReturnValue({
name: 'anything', name: 'anything',
}); });
@ -109,31 +110,33 @@ describe('SilentRenewService ', () => {
it('adds iframe to body if existing iframe is falsy', () => { it('adds iframe to body if existing iframe is falsy', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(silentRenewService as any, 'getExistingIframe').and.returnValue( vi.spyOn(silentRenewService as any, 'getExistingIframe').mockReturnValue(
null null
); );
const spy = spyOn(iFrameService, 'addIFrameToWindowBody').and.returnValue( const spy = vi
{ name: 'anything' } as HTMLIFrameElement .spyOn(iFrameService, 'addIFrameToWindowBody')
); .mockReturnValue({ name: 'anything' } as HTMLIFrameElement);
const result = silentRenewService.getOrCreateIframe(config); const result = silentRenewService.getOrCreateIframe(config);
expect(result).toEqual({ name: 'anything' } as HTMLIFrameElement); expect(result).toEqual({ name: 'anything' } as HTMLIFrameElement);
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledOnceWith('myiFrameForSilentRenew', config); expect(spy).toHaveBeenCalledExactlyOnceWith(
'myiFrameForSilentRenew',
config
);
}); });
}); });
describe('codeFlowCallbackSilentRenewIframe', () => { describe('codeFlowCallbackSilentRenewIframe', () => {
it('calls processSilentRenewCodeFlowCallback with correct arguments', waitForAsync(() => { it('calls processSilentRenewCodeFlowCallback with correct arguments', async () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const allConfigs = [config]; const allConfigs = [config];
const spy = spyOn( const spy = vi
flowsService, .spyOn(flowsService, 'processSilentRenewCodeFlowCallback')
'processSilentRenewCodeFlowCallback' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext));
const expectedContext = { const expectedContext = {
code: 'some-code', code: 'some-code',
refreshToken: '', refreshToken: '',
@ -152,32 +155,31 @@ describe('SilentRenewService ', () => {
silentRenewService silentRenewService
.codeFlowCallbackSilentRenewIframe([url, urlParts], config, allConfigs) .codeFlowCallbackSilentRenewIframe([url, urlParts], config, allConfigs)
.subscribe(() => { .subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
expectedContext, expectedContext,
config, config,
allConfigs allConfigs
); );
}); });
})); });
it('throws error if url has error param and resets everything on error', waitForAsync(() => { it('throws error if url has error param and resets everything on error', async () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const allConfigs = [config]; const allConfigs = [config];
const spy = spyOn( const spy = vi
flowsService, .spyOn(flowsService, 'processSilentRenewCodeFlowCallback')
'processSilentRenewCodeFlowCallback' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext)); const authStateServiceSpy = vi.spyOn(
const authStateServiceSpy = spyOn(
authStateService, authStateService,
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
const setNonceSpy = spyOn(flowsDataService, 'setNonce'); const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce');
const stopPeriodicTokenCheckSpy = spyOn( const stopPeriodicTokenCheckSpy = vi.spyOn(
intervalService, intervalService,
'stopPeriodicTokenCheck' 'stopPeriodicTokenCheck'
); );
@ -191,121 +193,116 @@ describe('SilentRenewService ', () => {
error: (error) => { error: (error) => {
expect(error).toEqual(new Error('some_error')); expect(error).toEqual(new Error('some_error'));
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
expect(authStateServiceSpy).toHaveBeenCalledOnceWith({ expect(authStateServiceSpy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
validationResult: ValidationResult.LoginRequired, validationResult: ValidationResult.LoginRequired,
isRenewProcess: true, isRenewProcess: true,
}); });
expect(resetAuthorizationDataSpy).toHaveBeenCalledOnceWith( expect(resetAuthorizationDataSpy).toHaveBeenCalledExactlyOnceWith(
config, config,
allConfigs allConfigs
); );
expect(setNonceSpy).toHaveBeenCalledOnceWith('', config); expect(setNonceSpy).toHaveBeenCalledExactlyOnceWith('', config);
expect(stopPeriodicTokenCheckSpy).toHaveBeenCalledTimes(1); expect(stopPeriodicTokenCheckSpy).toHaveBeenCalledTimes(1);
}, },
}); });
})); });
}); });
describe('silentRenewEventHandler', () => { describe('silentRenewEventHandler', () => {
it('returns if no details is given', fakeAsync(() => { it('returns if no details is given', async () => {
const isCurrentFlowCodeFlowSpy = spyOn( const isCurrentFlowCodeFlowSpy = vi
flowHelper, .spyOn(flowHelper, 'isCurrentFlowCodeFlow')
'isCurrentFlowCodeFlow' .mockReturnValue(false);
).and.returnValue(false);
spyOn( vi.spyOn(
implicitFlowCallbackService, implicitFlowCallbackService,
'authenticatedImplicitFlowCallback' 'authenticatedImplicitFlowCallback'
).and.returnValue(of({} as CallbackContext)); ).mockReturnValue(of({} as CallbackContext));
const eventData = { detail: null } as CustomEvent; const eventData = { detail: null } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
silentRenewService.silentRenewEventHandler( silentRenewService.silentRenewEventHandler(
eventData, eventData,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
expect(isCurrentFlowCodeFlowSpy).not.toHaveBeenCalled(); expect(isCurrentFlowCodeFlowSpy).not.toHaveBeenCalled();
})); });
it('calls authorizedImplicitFlowCallback if current flow is not code flow', fakeAsync(() => { it('calls authorizedImplicitFlowCallback if current flow is not code flow', async () => {
const isCurrentFlowCodeFlowSpy = spyOn( const isCurrentFlowCodeFlowSpy = vi
flowHelper, .spyOn(flowHelper, 'isCurrentFlowCodeFlow')
'isCurrentFlowCodeFlow' .mockReturnValue(false);
).and.returnValue(false); const authorizedImplicitFlowCallbackSpy = vi
const authorizedImplicitFlowCallbackSpy = spyOn( .spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback')
implicitFlowCallbackService, .mockReturnValue(of({} as CallbackContext));
'authenticatedImplicitFlowCallback'
).and.returnValue(of({} as CallbackContext));
const eventData = { detail: 'detail' } as CustomEvent; const eventData = { detail: 'detail' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
silentRenewService.silentRenewEventHandler( silentRenewService.silentRenewEventHandler(
eventData, eventData,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
expect(isCurrentFlowCodeFlowSpy).toHaveBeenCalled(); expect(isCurrentFlowCodeFlowSpy).toHaveBeenCalled();
expect(authorizedImplicitFlowCallbackSpy).toHaveBeenCalledOnceWith( expect(authorizedImplicitFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith(
allConfigs[0], allConfigs[0]!,
allConfigs, allConfigs,
'detail' 'detail'
); );
})); });
it('calls codeFlowCallbackSilentRenewIframe if current flow is code flow', fakeAsync(() => { it('calls codeFlowCallbackSilentRenewIframe if current flow is code flow', async () => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const codeFlowCallbackSilentRenewIframe = spyOn( const codeFlowCallbackSilentRenewIframe = vi
silentRenewService, .spyOn(silentRenewService, 'codeFlowCallbackSilentRenewIframe')
'codeFlowCallbackSilentRenewIframe' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext));
const eventData = { detail: 'detail?detail2' } as CustomEvent; const eventData = { detail: 'detail?detail2' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
silentRenewService.silentRenewEventHandler( silentRenewService.silentRenewEventHandler(
eventData, eventData,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledOnceWith( expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledExactlyOnceWith(
['detail', 'detail2'], ['detail', 'detail2'],
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
); );
})); });
it('calls authorizedImplicitFlowCallback if current flow is not code flow', fakeAsync(() => { it('calls authorizedImplicitFlowCallback if current flow is not code flow', async () => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const codeFlowCallbackSilentRenewIframe = spyOn( const codeFlowCallbackSilentRenewIframe = vi
silentRenewService, .spyOn(silentRenewService, 'codeFlowCallbackSilentRenewIframe')
'codeFlowCallbackSilentRenewIframe' .mockReturnValue(of({} as CallbackContext));
).and.returnValue(of({} as CallbackContext));
const eventData = { detail: 'detail?detail2' } as CustomEvent; const eventData = { detail: 'detail?detail2' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
silentRenewService.silentRenewEventHandler( silentRenewService.silentRenewEventHandler(
eventData, eventData,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledOnceWith( expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledExactlyOnceWith(
['detail', 'detail2'], ['detail', 'detail2'],
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
); );
})); });
it('calls next on refreshSessionWithIFrameCompleted with callbackcontext', fakeAsync(() => { it('calls next on refreshSessionWithIFrameCompleted with callbackcontext', async () => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
spyOn( vi.spyOn(
silentRenewService, silentRenewService,
'codeFlowCallbackSilentRenewIframe' 'codeFlowCallbackSilentRenewIframe'
).and.returnValue( ).mockReturnValue(
of({ refreshToken: 'callbackContext' } as CallbackContext) of({ refreshToken: 'callbackContext' } as CallbackContext)
); );
const eventData = { detail: 'detail?detail2' } as CustomEvent; const eventData = { detail: 'detail?detail2' } as CustomEvent;
@ -321,42 +318,42 @@ describe('SilentRenewService ', () => {
silentRenewService.silentRenewEventHandler( silentRenewService.silentRenewEventHandler(
eventData, eventData,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
})); });
it('loggs and calls flowsDataService.resetSilentRenewRunning in case of an error', fakeAsync(() => { it('loggs and calls flowsDataService.resetSilentRenewRunning in case of an error', async () => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
spyOn( vi.spyOn(
silentRenewService, silentRenewService,
'codeFlowCallbackSilentRenewIframe' 'codeFlowCallbackSilentRenewIframe'
).and.returnValue(throwError(() => new Error('ERROR'))); ).mockReturnValue(throwError(() => new Error('ERROR')));
const resetSilentRenewRunningSpy = spyOn( const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService, flowsDataService,
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
const logErrorSpy = spyOn(loggerService, 'logError'); const logErrorSpy = vi.spyOn(loggerService, 'logError');
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
const eventData = { detail: 'detail?detail2' } as CustomEvent; const eventData = { detail: 'detail?detail2' } as CustomEvent;
silentRenewService.silentRenewEventHandler( silentRenewService.silentRenewEventHandler(
eventData, eventData,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
expect(resetSilentRenewRunningSpy).toHaveBeenCalledTimes(1); expect(resetSilentRenewRunningSpy).toHaveBeenCalledTimes(1);
expect(logErrorSpy).toHaveBeenCalledTimes(1); expect(logErrorSpy).toHaveBeenCalledTimes(1);
})); });
it('calls next on refreshSessionWithIFrameCompleted with null in case of error', fakeAsync(() => { it('calls next on refreshSessionWithIFrameCompleted with null in case of error', async () => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
spyOn( vi.spyOn(
silentRenewService, silentRenewService,
'codeFlowCallbackSilentRenewIframe' 'codeFlowCallbackSilentRenewIframe'
).and.returnValue(throwError(() => new Error('ERROR'))); ).mockReturnValue(throwError(() => new Error('ERROR')));
const eventData = { detail: 'detail?detail2' } as CustomEvent; const eventData = { detail: 'detail?detail2' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
@ -368,10 +365,10 @@ describe('SilentRenewService ', () => {
silentRenewService.silentRenewEventHandler( silentRenewService.silentRenewEventHandler(
eventData, eventData,
allConfigs[0], allConfigs[0]!,
allConfigs allConfigs
); );
tick(1000); await vi.advanceTimersByTimeAsync(1000);
})); });
}); });
}); });

View File

@ -1,12 +1,12 @@
import { HttpParams } from '@ngify/http'; import { HttpParams } from '@ngify/http';
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, Subject, throwError } from 'rxjs'; import { type Observable, Subject, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service'; import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service';
import { IntervalService } from '../callback/interval.service'; import { IntervalService } from '../callback/interval.service';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
import { FlowsService } from '../flows/flows.service'; import { FlowsService } from '../flows/flows.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service';

View File

@ -5,7 +5,6 @@ export * from './auth-options';
export * from './auth-state/auth-result'; export * from './auth-state/auth-result';
export * from './auth-state/auth-state'; export * from './auth-state/auth-state';
export * from './auth.module'; export * from './auth.module';
export * from './auto-login/auto-login-all-routes.guard';
export * from './auto-login/auto-login-partial-routes.guard'; export * from './auto-login/auto-login-partial-routes.guard';
export * from './config/auth-well-known/auth-well-known-endpoints'; export * from './config/auth-well-known/auth-well-known-endpoints';
export * from './config/config.service'; export * from './config/config.service';
@ -29,3 +28,5 @@ export * from './user-data/userdata-result';
export * from './validation/jwtkeys'; export * from './validation/jwtkeys';
export * from './validation/state-validation-result'; export * from './validation/state-validation-result';
export * from './validation/validation-result'; export * from './validation/validation-result';
export * from './injection';
export * from './router';

View File

@ -0,0 +1,7 @@
import { InjectionToken } from 'injection-js';
import type { Observable } from 'rxjs';
export const APP_INITIALIZER = new InjectionToken<
// biome-ignore lint/suspicious/noConfusingVoidType: <explanation>
readonly (() => void | Observable<unknown> | Promise<unknown>)[]
>('APP_INITIALIZER');

3
src/injection/index.ts Normal file
View File

@ -0,0 +1,3 @@
export { Module } from './module';
export { APP_INITIALIZER } from './convention';
export { injectAbstractType } from './inject';

10
src/injection/inject.ts Normal file
View File

@ -0,0 +1,10 @@
import { inject } from 'injection-js';
// biome-ignore lint/complexity/noBannedTypes: <explanation>
export interface AbstractType<T> extends Function {
prototype: T;
}
export function injectAbstractType<T>(abstractType: AbstractType<T>): T {
return inject<T>(abstractType as any);
}

4
src/injection/module.ts Normal file
View File

@ -0,0 +1,4 @@
import 'reflect-metadata';
import type { Injector } from 'injection-js';
export type Module = (parentInjector: Injector) => Injector;

View File

@ -1,19 +1,20 @@
import { TestBed } from '@/testing';
import { import {
HTTP_INTERCEPTORS, HTTP_INTERCEPTORS,
HttpClient, HttpClient,
provideHttpClient, provideHttpClient,
withInterceptors, withInterceptors,
withInterceptorsFromDi, withInterceptorsFromDi,
} from '@angular/common/http'; } from '@ngify/http';
import { import {
HttpTestingController, HttpTestingController,
provideHttpClientTesting, provideHttpClientTesting,
} from '@angular/common/http/testing'; } from '@ngify/http/testing';
import { TestBed, waitForAsync } from '@angular/core/testing'; import { vi } from 'vitest';
import { mockProvider } from '../../test/auto-mock';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { ConfigurationService } from '../config/config.service'; import { ConfigurationService } from '../config/config.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { AuthInterceptor, authInterceptor } from './auth.interceptor'; import { AuthInterceptor, authInterceptor } from './auth.interceptor';
import { ClosestMatchingRouteService } from './closest-matching-route.service'; import { ClosestMatchingRouteService } from './closest-matching-route.service';
@ -85,18 +86,22 @@ describe(`AuthHttpInterceptor`, () => {
}); });
function runTests(): void { function runTests(): void {
it('should add an Authorization header when route matches and token is present', waitForAsync(() => { it('should add an Authorization header when route matches and token is present', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = `https://jsonplaceholder.typicode.com/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
secureRoutes: [actionUrl], secureRoutes: [actionUrl],
configId: 'configId1', configId: 'configId1',
}, },
]); ]);
spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken'); vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); 'thisIsAToken'
);
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
httpClient.get(actionUrl).subscribe((response) => { httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
@ -108,18 +113,22 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something'); httpRequest.flush('something');
httpTestingController.verify(); httpTestingController.verify();
})); });
it('should not add an Authorization header when `secureRoutes` is not given', waitForAsync(() => { it('should not add an Authorization header when `secureRoutes` is not given', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = `https://jsonplaceholder.typicode.com/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
configId: 'configId1', configId: 'configId1',
}, },
]); ]);
spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken'); vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); 'thisIsAToken'
);
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
httpClient.get(actionUrl).subscribe((response) => { httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
@ -131,20 +140,24 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something'); httpRequest.flush('something');
httpTestingController.verify(); httpTestingController.verify();
})); });
it('should not add an Authorization header when no routes configured', waitForAsync(() => { it('should not add an Authorization header when no routes configured', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = `https://jsonplaceholder.typicode.com/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
secureRoutes: [], secureRoutes: [],
configId: 'configId1', configId: 'configId1',
}, },
]); ]);
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken'); true
);
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
'thisIsAToken'
);
httpClient.get(actionUrl).subscribe((response) => { httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
@ -156,19 +169,21 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something'); httpRequest.flush('something');
httpTestingController.verify(); httpTestingController.verify();
})); });
it('should not add an Authorization header when no routes configured', waitForAsync(() => { it('should not add an Authorization header when no routes configured', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = `https://jsonplaceholder.typicode.com/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
secureRoutes: [], secureRoutes: [],
configId: 'configId1', configId: 'configId1',
}, },
]); ]);
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
httpClient.get(actionUrl).subscribe((response) => { httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
@ -180,20 +195,22 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something'); httpRequest.flush('something');
httpTestingController.verify(); httpTestingController.verify();
})); });
it('should not add an Authorization header when route is configured but no token is present', waitForAsync(() => { it('should not add an Authorization header when route is configured but no token is present', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = `https://jsonplaceholder.typicode.com/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
secureRoutes: [actionUrl], secureRoutes: [actionUrl],
configId: 'configId1', configId: 'configId1',
}, },
]); ]);
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
spyOn(authStateService, 'getAccessToken').and.returnValue(''); true
);
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('');
httpClient.get(actionUrl).subscribe((response) => { httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
@ -205,12 +222,14 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something'); httpRequest.flush('something');
httpTestingController.verify(); httpTestingController.verify();
})); });
it('should not add an Authorization header when no config is present', waitForAsync(() => { it('should not add an Authorization header when no config is present', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = `https://jsonplaceholder.typicode.com/`;
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(false); vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
false
);
httpClient.get(actionUrl).subscribe((response) => { httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
@ -222,22 +241,24 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something'); httpRequest.flush('something');
httpTestingController.verify(); httpTestingController.verify();
})); });
it('should not add an Authorization header when no configured route is matching the request', waitForAsync(() => { it('should not add an Authorization header when no configured route is matching the request', async () => {
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = `https://jsonplaceholder.typicode.com/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
secureRoutes: [actionUrl], secureRoutes: [actionUrl],
configId: 'configId1', configId: 'configId1',
}, },
]); ]);
spyOn( vi.spyOn(
closestMatchingRouteService, closestMatchingRouteService,
'getConfigIdForClosestMatchingRoute' 'getConfigIdForClosestMatchingRoute'
).and.returnValue({ ).mockReturnValue({
matchingRoute: null, matchingRoute: null,
matchingConfig: null, matchingConfig: null,
}); });
@ -252,18 +273,22 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something'); httpRequest.flush('something');
httpTestingController.verify(); httpTestingController.verify();
})); });
it('should add an Authorization header when multiple routes are configured and token is present', waitForAsync(() => { it('should add an Authorization header when multiple routes are configured and token is present', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = `https://jsonplaceholder.typicode.com/`;
const actionUrl2 = `https://some-other-url.com/`; const actionUrl2 = `https://some-other-url.com/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ secureRoutes: [actionUrl, actionUrl2], configId: 'configId1' }, { secureRoutes: [actionUrl, actionUrl2], configId: 'configId1' },
]); ]);
spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken'); vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); 'thisIsAToken'
);
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
httpClient.get(actionUrl).subscribe((response) => { httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
@ -284,6 +309,6 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something'); httpRequest.flush('something');
httpRequest2.flush('something'); httpRequest2.flush('something');
httpTestingController.verify(); httpTestingController.verify();
})); });
} }
}); });

View File

@ -1,6 +1,7 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { ClosestMatchingRouteService } from './closest-matching-route.service'; import { ClosestMatchingRouteService } from './closest-matching-route.service';
describe('ClosestMatchingRouteService', () => { describe('ClosestMatchingRouteService', () => {

View File

@ -1,4 +1,5 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { AbstractLoggerService } from './abstract-logger.service'; import { AbstractLoggerService } from './abstract-logger.service';
import { ConsoleLoggerService } from './console-logger.service'; import { ConsoleLoggerService } from './console-logger.service';
import { LogLevel } from './log-level'; import { LogLevel } from './log-level';
@ -26,7 +27,7 @@ describe('Logger Service', () => {
describe('logError', () => { describe('logError', () => {
it('should not log error if loglevel is None', () => { it('should not log error if loglevel is None', () => {
const spy = spyOn(console, 'error'); const spy = vi.spyOn(console, 'error');
loggerService.logError( loggerService.logError(
{ configId: 'configId1', logLevel: LogLevel.None }, { configId: 'configId1', logLevel: LogLevel.None },
@ -36,23 +37,25 @@ describe('Logger Service', () => {
}); });
it('should log error as default if error is string', () => { it('should log error as default if error is string', () => {
const spy = spyOn(console, 'error'); const spy = vi.spyOn(console, 'error');
loggerService.logError({ configId: 'configId1' }, 'some message'); loggerService.logError({ configId: 'configId1' }, 'some message');
expect(spy).toHaveBeenCalledOnceWith('[ERROR] configId1 - some message'); expect(spy).toHaveBeenCalledExactlyOnceWith(
'[ERROR] configId1 - some message'
);
}); });
it('should log error as default if error is object', () => { it('should log error as default if error is object', () => {
const spy = spyOn(console, 'error'); const spy = vi.spyOn(console, 'error');
loggerService.logError({ configId: 'configId1' }, { some: 'message' }); loggerService.logError({ configId: 'configId1' }, { some: 'message' });
expect(spy).toHaveBeenCalledOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
'[ERROR] configId1 - {"some":"message"}' '[ERROR] configId1 - {"some":"message"}'
); );
}); });
it('should always log error with args', () => { it('should always log error with args', () => {
const spy = spyOn(console, 'error'); const spy = vi.spyOn(console, 'error');
loggerService.logError( loggerService.logError(
{ configId: 'configId1' }, { configId: 'configId1' },
@ -60,7 +63,7 @@ describe('Logger Service', () => {
'arg1', 'arg1',
'arg2' 'arg2'
); );
expect(spy).toHaveBeenCalledOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
'[ERROR] configId1 - some message', '[ERROR] configId1 - some message',
'arg1', 'arg1',
'arg2' 'arg2'
@ -70,7 +73,7 @@ describe('Logger Service', () => {
describe('logWarn', () => { describe('logWarn', () => {
it('should not log if no log level is set (null)', () => { it('should not log if no log level is set (null)', () => {
const spy = spyOn(console, 'warn'); const spy = vi.spyOn(console, 'warn');
loggerService.logWarning( loggerService.logWarning(
{ configId: 'configId1', logLevel: undefined }, { configId: 'configId1', logLevel: undefined },
@ -80,14 +83,14 @@ describe('Logger Service', () => {
}); });
it('should not log if no config is given', () => { it('should not log if no config is given', () => {
const spy = spyOn(console, 'warn'); const spy = vi.spyOn(console, 'warn');
loggerService.logWarning({}, 'some message'); loggerService.logWarning({}, 'some message');
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
}); });
it('should not log if no log level is set (undefined)', () => { it('should not log if no log level is set (undefined)', () => {
const spy = spyOn(console, 'warn'); const spy = vi.spyOn(console, 'warn');
loggerService.logWarning({ configId: 'configId1' }, 'some message'); loggerService.logWarning({ configId: 'configId1' }, 'some message');
@ -95,7 +98,7 @@ describe('Logger Service', () => {
}); });
it('should not log if log level is turned off', () => { it('should not log if log level is turned off', () => {
const spy = spyOn(console, 'warn'); const spy = vi.spyOn(console, 'warn');
loggerService.logWarning( loggerService.logWarning(
{ configId: 'configId1', logLevel: LogLevel.None }, { configId: 'configId1', logLevel: LogLevel.None },
@ -105,29 +108,31 @@ describe('Logger Service', () => {
}); });
it('should log warning when loglevel is Warn and message is string', () => { it('should log warning when loglevel is Warn and message is string', () => {
const spy = spyOn(console, 'warn'); const spy = vi.spyOn(console, 'warn');
loggerService.logWarning( loggerService.logWarning(
{ configId: 'configId1', logLevel: LogLevel.Warn }, { configId: 'configId1', logLevel: LogLevel.Warn },
'some message' 'some message'
); );
expect(spy).toHaveBeenCalledOnceWith('[WARN] configId1 - some message'); expect(spy).toHaveBeenCalledExactlyOnceWith(
'[WARN] configId1 - some message'
);
}); });
it('should log warning when loglevel is Warn and message is object', () => { it('should log warning when loglevel is Warn and message is object', () => {
const spy = spyOn(console, 'warn'); const spy = vi.spyOn(console, 'warn');
loggerService.logWarning( loggerService.logWarning(
{ configId: 'configId1', logLevel: LogLevel.Warn }, { configId: 'configId1', logLevel: LogLevel.Warn },
{ some: 'message' } { some: 'message' }
); );
expect(spy).toHaveBeenCalledOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
'[WARN] configId1 - {"some":"message"}' '[WARN] configId1 - {"some":"message"}'
); );
}); });
it('should log warning when loglevel is Warn with args', () => { it('should log warning when loglevel is Warn with args', () => {
const spy = spyOn(console, 'warn'); const spy = vi.spyOn(console, 'warn');
loggerService.logWarning( loggerService.logWarning(
{ configId: 'configId1', logLevel: LogLevel.Warn }, { configId: 'configId1', logLevel: LogLevel.Warn },
@ -135,7 +140,7 @@ describe('Logger Service', () => {
'arg1', 'arg1',
'arg2' 'arg2'
); );
expect(spy).toHaveBeenCalledOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
'[WARN] configId1 - some message', '[WARN] configId1 - some message',
'arg1', 'arg1',
'arg2' 'arg2'
@ -143,17 +148,19 @@ describe('Logger Service', () => {
}); });
it('should log warning when loglevel is Debug', () => { it('should log warning when loglevel is Debug', () => {
const spy = spyOn(console, 'warn'); const spy = vi.spyOn(console, 'warn');
loggerService.logWarning( loggerService.logWarning(
{ configId: 'configId1', logLevel: LogLevel.Debug }, { configId: 'configId1', logLevel: LogLevel.Debug },
'some message' 'some message'
); );
expect(spy).toHaveBeenCalledOnceWith('[WARN] configId1 - some message'); expect(spy).toHaveBeenCalledExactlyOnceWith(
'[WARN] configId1 - some message'
);
}); });
it('should not log warning when loglevel is error', () => { it('should not log warning when loglevel is error', () => {
const spy = spyOn(console, 'warn'); const spy = vi.spyOn(console, 'warn');
loggerService.logWarning( loggerService.logWarning(
{ configId: 'configId1', logLevel: LogLevel.Error }, { configId: 'configId1', logLevel: LogLevel.Error },
@ -165,7 +172,7 @@ describe('Logger Service', () => {
describe('logDebug', () => { describe('logDebug', () => {
it('should not log if no log level is set (null)', () => { it('should not log if no log level is set (null)', () => {
const spy = spyOn(console, 'debug'); const spy = vi.spyOn(console, 'debug');
loggerService.logDebug( loggerService.logDebug(
{ configId: 'configId1', logLevel: undefined }, { configId: 'configId1', logLevel: undefined },
@ -175,14 +182,14 @@ describe('Logger Service', () => {
}); });
it('should not log if no log level is set (undefined)', () => { it('should not log if no log level is set (undefined)', () => {
const spy = spyOn(console, 'debug'); const spy = vi.spyOn(console, 'debug');
loggerService.logDebug({ configId: 'configId1' }, 'some message'); loggerService.logDebug({ configId: 'configId1' }, 'some message');
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
}); });
it('should not log if log level is turned off', () => { it('should not log if log level is turned off', () => {
const spy = spyOn(console, 'debug'); const spy = vi.spyOn(console, 'debug');
loggerService.logDebug( loggerService.logDebug(
{ configId: 'configId1', logLevel: LogLevel.None }, { configId: 'configId1', logLevel: LogLevel.None },
@ -192,29 +199,31 @@ describe('Logger Service', () => {
}); });
it('should log when loglevel is Debug and value is string', () => { it('should log when loglevel is Debug and value is string', () => {
const spy = spyOn(console, 'debug'); const spy = vi.spyOn(console, 'debug');
loggerService.logDebug( loggerService.logDebug(
{ configId: 'configId1', logLevel: LogLevel.Debug }, { configId: 'configId1', logLevel: LogLevel.Debug },
'some message' 'some message'
); );
expect(spy).toHaveBeenCalledOnceWith('[DEBUG] configId1 - some message'); expect(spy).toHaveBeenCalledExactlyOnceWith(
'[DEBUG] configId1 - some message'
);
}); });
it('should log when loglevel is Debug and value is object', () => { it('should log when loglevel is Debug and value is object', () => {
const spy = spyOn(console, 'debug'); const spy = vi.spyOn(console, 'debug');
loggerService.logDebug( loggerService.logDebug(
{ configId: 'configId1', logLevel: LogLevel.Debug }, { configId: 'configId1', logLevel: LogLevel.Debug },
{ some: 'message' } { some: 'message' }
); );
expect(spy).toHaveBeenCalledOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
'[DEBUG] configId1 - {"some":"message"}' '[DEBUG] configId1 - {"some":"message"}'
); );
}); });
it('should log when loglevel is Debug with args', () => { it('should log when loglevel is Debug with args', () => {
const spy = spyOn(console, 'debug'); const spy = vi.spyOn(console, 'debug');
loggerService.logDebug( loggerService.logDebug(
{ configId: 'configId1', logLevel: LogLevel.Debug }, { configId: 'configId1', logLevel: LogLevel.Debug },
@ -222,7 +231,7 @@ describe('Logger Service', () => {
'arg1', 'arg1',
'arg2' 'arg2'
); );
expect(spy).toHaveBeenCalledOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
'[DEBUG] configId1 - some message', '[DEBUG] configId1 - some message',
'arg1', 'arg1',
'arg2' 'arg2'
@ -230,7 +239,7 @@ describe('Logger Service', () => {
}); });
it('should not log when loglevel is Warn', () => { it('should not log when loglevel is Warn', () => {
const spy = spyOn(console, 'debug'); const spy = vi.spyOn(console, 'debug');
loggerService.logDebug( loggerService.logDebug(
{ configId: 'configId1', logLevel: LogLevel.Warn }, { configId: 'configId1', logLevel: LogLevel.Warn },
@ -240,7 +249,7 @@ describe('Logger Service', () => {
}); });
it('should not log when loglevel is error', () => { it('should not log when loglevel is error', () => {
const spy = spyOn(console, 'debug'); const spy = vi.spyOn(console, 'debug');
loggerService.logDebug( loggerService.logDebug(
{ configId: 'configId1', logLevel: LogLevel.Error }, { configId: 'configId1', logLevel: LogLevel.Error },

View File

@ -1,11 +1,14 @@
import { inject, Injectable } from 'injection-js'; import { Injectable } from 'injection-js';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { injectAbstractType } from '../injection/inject';
import { AbstractLoggerService } from './abstract-logger.service'; import { AbstractLoggerService } from './abstract-logger.service';
import { LogLevel } from './log-level'; import { LogLevel } from './log-level';
@Injectable() @Injectable()
export class LoggerService { export class LoggerService {
private readonly abstractLoggerService = inject(AbstractLoggerService); private readonly abstractLoggerService = injectAbstractType(
AbstractLoggerService
);
logError( logError(
configuration: OpenIdConfiguration, configuration: OpenIdConfiguration,

View File

@ -1,9 +1,10 @@
import { TestBed } from '@/testing';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { LoginResponse } from './login-response'; import { mockProvider } from '../testing/mock';
import type { LoginResponse } from './login-response';
import { LoginService } from './login.service'; import { LoginService } from './login.service';
import { ParLoginService } from './par/par-login.service'; import { ParLoginService } from './par/par-login.service';
import { PopUpLoginService } from './popup/popup-login.service'; import { PopUpLoginService } from './popup/popup-login.service';
@ -48,8 +49,8 @@ describe('LoginService', () => {
describe('login', () => { describe('login', () => {
it('calls parLoginService loginPar if usePushedAuthorisationRequests is true', () => { it('calls parLoginService loginPar if usePushedAuthorisationRequests is true', () => {
const config = { usePushedAuthorisationRequests: true }; const config = { usePushedAuthorisationRequests: true };
const loginParSpy = spyOn(parLoginService, 'loginPar'); const loginParSpy = vi.spyOn(parLoginService, 'loginPar');
const standardLoginSpy = spyOn(standardLoginService, 'loginStandard'); const standardLoginSpy = vi.spyOn(standardLoginService, 'loginStandard');
service.login(config); service.login(config);
@ -59,8 +60,8 @@ describe('LoginService', () => {
it('calls standardLoginService loginStandard if usePushedAuthorisationRequests is false', () => { it('calls standardLoginService loginStandard if usePushedAuthorisationRequests is false', () => {
const config = { usePushedAuthorisationRequests: false }; const config = { usePushedAuthorisationRequests: false };
const loginParSpy = spyOn(parLoginService, 'loginPar'); const loginParSpy = vi.spyOn(parLoginService, 'loginPar');
const standardLoginSpy = spyOn(standardLoginService, 'loginStandard'); const standardLoginSpy = vi.spyOn(standardLoginService, 'loginStandard');
service.login(config); service.login(config);
@ -71,7 +72,7 @@ describe('LoginService', () => {
it('stores the customParams to the storage if customParams are given', () => { it('stores the customParams to the storage if customParams are given', () => {
// arrange // arrange
const config = { usePushedAuthorisationRequests: false }; const config = { usePushedAuthorisationRequests: false };
const storagePersistenceServiceSpy = spyOn( const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService, storagePersistenceService,
'write' 'write'
); );
@ -79,7 +80,7 @@ describe('LoginService', () => {
service.login(config, authOptions); service.login(config, authOptions);
expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith( expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith(
'storageCustomParamsAuthRequest', 'storageCustomParamsAuthRequest',
{ custom: 'params' }, { custom: 'params' },
config config
@ -89,8 +90,8 @@ describe('LoginService', () => {
it("should throw error if configuration is null and doesn't call loginPar or loginStandard", () => { it("should throw error if configuration is null and doesn't call loginPar or loginStandard", () => {
// arrange // arrange
const config = null; const config = null;
const loginParSpy = spyOn(parLoginService, 'loginPar'); const loginParSpy = vi.spyOn(parLoginService, 'loginPar');
const standardLoginSpy = spyOn(standardLoginService, 'loginStandard'); const standardLoginSpy = vi.spyOn(standardLoginService, 'loginStandard');
const authOptions = { customParams: { custom: 'params' } }; const authOptions = { customParams: { custom: 'params' } };
// act // act
@ -106,17 +107,15 @@ describe('LoginService', () => {
}); });
describe('loginWithPopUp', () => { describe('loginWithPopUp', () => {
it('calls parLoginService loginWithPopUpPar if usePushedAuthorisationRequests is true', waitForAsync(() => { it('calls parLoginService loginWithPopUpPar if usePushedAuthorisationRequests is true', async () => {
// arrange // arrange
const config = { usePushedAuthorisationRequests: true }; const config = { usePushedAuthorisationRequests: true };
const loginWithPopUpPar = spyOn( const loginWithPopUpPar = vi
parLoginService, .spyOn(parLoginService, 'loginWithPopUpPar')
'loginWithPopUpPar' .mockReturnValue(of({} as LoginResponse));
).and.returnValue(of({} as LoginResponse)); const loginWithPopUpStandardSpy = vi
const loginWithPopUpStandardSpy = spyOn( .spyOn(popUpLoginService, 'loginWithPopUpStandard')
popUpLoginService, .mockReturnValue(of({} as LoginResponse));
'loginWithPopUpStandard'
).and.returnValue(of({} as LoginResponse));
// act // act
service.loginWithPopUp(config, [config]).subscribe(() => { service.loginWithPopUp(config, [config]).subscribe(() => {
@ -124,19 +123,17 @@ describe('LoginService', () => {
expect(loginWithPopUpPar).toHaveBeenCalledTimes(1); expect(loginWithPopUpPar).toHaveBeenCalledTimes(1);
expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled(); expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled();
}); });
})); });
it('calls standardLoginService loginstandard if usePushedAuthorisationRequests is false', waitForAsync(() => { it('calls standardLoginService loginstandard if usePushedAuthorisationRequests is false', async () => {
// arrange // arrange
const config = { usePushedAuthorisationRequests: false }; const config = { usePushedAuthorisationRequests: false };
const loginWithPopUpPar = spyOn( const loginWithPopUpPar = vi
parLoginService, .spyOn(parLoginService, 'loginWithPopUpPar')
'loginWithPopUpPar' .mockReturnValue(of({} as LoginResponse));
).and.returnValue(of({} as LoginResponse)); const loginWithPopUpStandardSpy = vi
const loginWithPopUpStandardSpy = spyOn( .spyOn(popUpLoginService, 'loginWithPopUpStandard')
popUpLoginService, .mockReturnValue(of({} as LoginResponse));
'loginWithPopUpStandard'
).and.returnValue(of({} as LoginResponse));
// act // act
service.loginWithPopUp(config, [config]).subscribe(() => { service.loginWithPopUp(config, [config]).subscribe(() => {
@ -144,46 +141,44 @@ describe('LoginService', () => {
expect(loginWithPopUpPar).not.toHaveBeenCalled(); expect(loginWithPopUpPar).not.toHaveBeenCalled();
expect(loginWithPopUpStandardSpy).toHaveBeenCalledTimes(1); expect(loginWithPopUpStandardSpy).toHaveBeenCalledTimes(1);
}); });
})); });
it('stores the customParams to the storage if customParams are given', waitForAsync(() => { it('stores the customParams to the storage if customParams are given', async () => {
// arrange // arrange
const config = { usePushedAuthorisationRequests: false }; const config = { usePushedAuthorisationRequests: false };
const storagePersistenceServiceSpy = spyOn( const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService, storagePersistenceService,
'write' 'write'
); );
const authOptions = { customParams: { custom: 'params' } }; const authOptions = { customParams: { custom: 'params' } };
spyOn(popUpLoginService, 'loginWithPopUpStandard').and.returnValue( vi.spyOn(popUpLoginService, 'loginWithPopUpStandard').mockReturnValue(
of({} as LoginResponse) of({} as LoginResponse)
); );
// act // act
service.loginWithPopUp(config, [config], authOptions).subscribe(() => { service.loginWithPopUp(config, [config], authOptions).subscribe(() => {
// assert // assert
expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith( expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith(
'storageCustomParamsAuthRequest', 'storageCustomParamsAuthRequest',
{ custom: 'params' }, { custom: 'params' },
config config
); );
}); });
})); });
it('returns error if there is already a popup open', () => { it('returns error if there is already a popup open', () => {
// arrange // arrange
const config = { usePushedAuthorisationRequests: false }; const config = { usePushedAuthorisationRequests: false };
const authOptions = { customParams: { custom: 'params' } }; const authOptions = { customParams: { custom: 'params' } };
const loginWithPopUpPar = spyOn( const loginWithPopUpPar = vi
parLoginService, .spyOn(parLoginService, 'loginWithPopUpPar')
'loginWithPopUpPar' .mockReturnValue(of({} as LoginResponse));
).and.returnValue(of({} as LoginResponse)); const loginWithPopUpStandardSpy = vi
const loginWithPopUpStandardSpy = spyOn( .spyOn(popUpLoginService, 'loginWithPopUpStandard')
popUpLoginService, .mockReturnValue(of({} as LoginResponse));
'loginWithPopUpStandard'
).and.returnValue(of({} as LoginResponse));
spyOn(popUpService, 'isCurrentlyInPopup').and.returnValue(true); vi.spyOn(popUpService, 'isCurrentlyInPopup').mockReturnValue(true);
// act // act
service service

View File

@ -1,12 +1,12 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, of } from 'rxjs'; import { type Observable, of } from 'rxjs';
import { AuthOptions } from '../auth-options'; import type { AuthOptions } from '../auth-options';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { LoginResponse } from './login-response'; import type { LoginResponse } from './login-response';
import { ParLoginService } from './par/par-login.service'; import { ParLoginService } from './par/par-login.service';
import { PopUpLoginService } from './popup/popup-login.service'; import { PopUpLoginService } from './popup/popup-login.service';
import { PopupOptions } from './popup/popup-options'; import type { PopupOptions } from './popup/popup-options';
import { PopUpService } from './popup/popup.service'; import { PopUpService } from './popup/popup.service';
import { StandardLoginService } from './standard/standard-login.service'; import { StandardLoginService } from './standard/standard-login.service';
@ -45,12 +45,9 @@ export class LoginService {
} }
if (usePushedAuthorisationRequests) { if (usePushedAuthorisationRequests) {
return this.parLoginService.loginPar(configuration, authOptions); this.parLoginService.loginPar(configuration, authOptions);
} else { } else {
return this.standardLoginService.loginStandard( this.standardLoginService.loginStandard(configuration, authOptions);
configuration,
authOptions
);
} }
} }

View File

@ -1,17 +1,18 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { CheckAuthService } from '../../auth-state/check-auth.service'; import { CheckAuthService } from '../../auth-state/check-auth.service';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { RedirectService } from '../../utils/redirect/redirect.service'; import { RedirectService } from '../../utils/redirect/redirect.service';
import { UrlService } from '../../utils/url/url.service'; import { UrlService } from '../../utils/url/url.service';
import { LoginResponse } from '../login-response'; import type { LoginResponse } from '../login-response';
import { PopupResult } from '../popup/popup-result'; import type { PopupResult } from '../popup/popup-result';
import { PopUpService } from '../popup/popup.service'; import { PopUpService } from '../popup/popup.service';
import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service';
import { ParLoginService } from './par-login.service'; import { ParLoginService } from './par-login.service';
import { ParResponse } from './par-response'; import type { ParResponse } from './par-response';
import { ParService } from './par.service'; import { ParService } from './par.service';
describe('ParLoginService', () => { describe('ParLoginService', () => {
@ -60,33 +61,33 @@ describe('ParLoginService', () => {
}); });
describe('loginPar', () => { describe('loginPar', () => {
it('does nothing if it has an invalid response type', waitForAsync(() => { it('does nothing if it has an invalid response type', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(false); ).mockReturnValue(false);
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const result = service.loginPar({}); const result = service.loginPar({});
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
})); });
it('calls parService.postParRequest without custom params when no custom params are passed', waitForAsync(() => { it('calls parService.postParRequest without custom params when no custom params are passed', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
const spy = spyOn(parService, 'postParRequest').and.returnValue( const spy = vi
of({ requestUri: 'requestUri' } as ParResponse) .spyOn(parService, 'postParRequest')
); .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse));
const result = service.loginPar({ const result = service.loginPar({
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
@ -95,69 +96,69 @@ describe('ParLoginService', () => {
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
})); });
it('calls parService.postParRequest with custom params when custom params are passed', waitForAsync(() => { it('calls parService.postParRequest with custom params when custom params are passed', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
const spy = spyOn(parService, 'postParRequest').and.returnValue( const spy = vi
of({ requestUri: 'requestUri' } as ParResponse) .spyOn(parService, 'postParRequest')
); .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse));
const result = service.loginPar(config, { const result = service.loginPar(config, {
customParams: { some: 'thing' }, customParams: { some: 'thing' },
}); });
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(spy).toHaveBeenCalledOnceWith(config, { expect(spy).toHaveBeenCalledExactlyOnceWith(config, {
customParams: { some: 'thing' }, customParams: { some: 'thing' },
}); });
})); });
it('returns undefined and logs error when no url could be created', waitForAsync(() => { it('returns undefined and logs error when no url could be created', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(parService, 'postParRequest').and.returnValue( vi.spyOn(parService, 'postParRequest').mockReturnValue(
of({ requestUri: 'requestUri' } as ParResponse) of({ requestUri: 'requestUri' } as ParResponse)
); );
spyOn(urlService, 'getAuthorizeParUrl').and.returnValue(''); vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue('');
const spy = spyOn(loggerService, 'logError'); const spy = vi.spyOn(loggerService, 'logError');
const result = service.loginPar(config); const result = service.loginPar(config);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
})); });
it('calls redirect service redirectTo when url could be created', waitForAsync(() => { it('calls redirect service redirectTo when url could be created', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
@ -165,42 +166,46 @@ describe('ParLoginService', () => {
const authOptions = {}; const authOptions = {};
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(parService, 'postParRequest').and.returnValue( vi.spyOn(parService, 'postParRequest').mockReturnValue(
of({ requestUri: 'requestUri' } as ParResponse) of({ requestUri: 'requestUri' } as ParResponse)
); );
spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(
const spy = spyOn(redirectService, 'redirectTo'); 'some-par-url'
);
const spy = vi.spyOn(redirectService, 'redirectTo');
service.loginPar(config, authOptions); service.loginPar(config, authOptions);
expect(spy).toHaveBeenCalledOnceWith('some-par-url'); expect(spy).toHaveBeenCalledExactlyOnceWith('some-par-url');
})); });
it('calls urlHandler when URL is passed', waitForAsync(() => { it('calls urlHandler when URL is passed', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(parService, 'postParRequest').and.returnValue( vi.spyOn(parService, 'postParRequest').mockReturnValue(
of({ requestUri: 'requestUri' } as ParResponse) of({ requestUri: 'requestUri' } as ParResponse)
); );
spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(
const redirectToSpy = spyOn(redirectService, 'redirectTo'); 'some-par-url'
);
const redirectToSpy = vi.spyOn(redirectService, 'redirectTo');
const spy = jasmine.createSpy(); const spy = jasmine.createSpy();
const urlHandler = (url: any): void => { const urlHandler = (url: any): void => {
spy(url); spy(url);
@ -208,18 +213,18 @@ describe('ParLoginService', () => {
service.loginPar(config, { urlHandler }); service.loginPar(config, { urlHandler });
expect(spy).toHaveBeenCalledOnceWith('some-par-url'); expect(spy).toHaveBeenCalledExactlyOnceWith('some-par-url');
expect(redirectToSpy).not.toHaveBeenCalled(); expect(redirectToSpy).not.toHaveBeenCalled();
})); });
}); });
describe('loginWithPopUpPar', () => { describe('loginWithPopUpPar', () => {
it('does nothing if it has an invalid response type', waitForAsync(() => { it('does nothing if it has an invalid response type', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(false); ).mockReturnValue(false);
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const config = {}; const config = {};
const allConfigs = [config]; const allConfigs = [config];
@ -229,27 +234,27 @@ describe('ParLoginService', () => {
expect(err.message).toBe('Invalid response type!'); expect(err.message).toBe('Invalid response type!');
}, },
}); });
})); });
it('calls parService.postParRequest without custom params when no custom params are passed', waitForAsync(() => { it('calls parService.postParRequest without custom params when no custom params are passed', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
const allConfigs = [config]; const allConfigs = [config];
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
const spy = spyOn(parService, 'postParRequest').and.returnValue( const spy = vi
of({ requestUri: 'requestUri' } as ParResponse) .spyOn(parService, 'postParRequest')
); .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse));
service.loginWithPopUpPar(config, allConfigs).subscribe({ service.loginWithPopUpPar(config, allConfigs).subscribe({
error: (err) => { error: (err) => {
@ -259,27 +264,27 @@ describe('ParLoginService', () => {
); );
}, },
}); });
})); });
it('calls parService.postParRequest with custom params when custom params are passed', waitForAsync(() => { it('calls parService.postParRequest with custom params when custom params are passed', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
const allConfigs = [config]; const allConfigs = [config];
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
const spy = spyOn(parService, 'postParRequest').and.returnValue( const spy = vi
of({ requestUri: 'requestUri' } as ParResponse) .spyOn(parService, 'postParRequest')
); .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse));
service service
.loginWithPopUpPar(config, allConfigs, { .loginWithPopUpPar(config, allConfigs, {
@ -287,7 +292,7 @@ describe('ParLoginService', () => {
}) })
.subscribe({ .subscribe({
error: (err) => { error: (err) => {
expect(spy).toHaveBeenCalledOnceWith(config, { expect(spy).toHaveBeenCalledExactlyOnceWith(config, {
customParams: { some: 'thing' }, customParams: { some: 'thing' },
}); });
expect(err.message).toBe( expect(err.message).toBe(
@ -295,29 +300,29 @@ describe('ParLoginService', () => {
); );
}, },
}); });
})); });
it('returns undefined and logs error when no URL could be created', waitForAsync(() => { it('returns undefined and logs error when no URL could be created', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
const allConfigs = [config]; const allConfigs = [config];
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(parService, 'postParRequest').and.returnValue( vi.spyOn(parService, 'postParRequest').mockReturnValue(
of({ requestUri: 'requestUri' } as ParResponse) of({ requestUri: 'requestUri' } as ParResponse)
); );
spyOn(urlService, 'getAuthorizeParUrl').and.returnValue(''); vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue('');
const spy = spyOn(loggerService, 'logError'); const spy = vi.spyOn(loggerService, 'logError');
service service
.loginWithPopUpPar(config, allConfigs, { .loginWithPopUpPar(config, allConfigs, {
@ -331,46 +336,52 @@ describe('ParLoginService', () => {
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
}, },
}); });
})); });
it('calls popupService openPopUp when URL could be created', waitForAsync(() => { it('calls popupService openPopUp when URL could be created', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
const allConfigs = [config]; const allConfigs = [config];
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(parService, 'postParRequest').and.returnValue( vi.spyOn(parService, 'postParRequest').mockReturnValue(
of({ requestUri: 'requestUri' } as ParResponse) of({ requestUri: 'requestUri' } as ParResponse)
); );
spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(
spyOn(checkAuthService, 'checkAuth').and.returnValue( 'some-par-url'
);
vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue(
of({} as LoginResponse) of({} as LoginResponse)
); );
spyOnProperty(popupService, 'result$').and.returnValue( vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of({} as PopupResult) of({} as PopupResult)
); );
const spy = spyOn(popupService, 'openPopUp'); const spy = vi.spyOn(popupService, 'openPopUp');
service.loginWithPopUpPar(config, allConfigs).subscribe(() => { service.loginWithPopUpPar(config, allConfigs).subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith('some-par-url', undefined, config); expect(spy).toHaveBeenCalledExactlyOnceWith(
'some-par-url',
undefined,
config
);
}); });
})); });
it('returns correct properties if URL is received', waitForAsync(() => { it('returns correct properties if URL is received', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
@ -378,34 +389,40 @@ describe('ParLoginService', () => {
}; };
const allConfigs = [config]; const allConfigs = [config];
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(parService, 'postParRequest').and.returnValue( vi.spyOn(parService, 'postParRequest').mockReturnValue(
of({ requestUri: 'requestUri' } as ParResponse) of({ requestUri: 'requestUri' } as ParResponse)
); );
spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(
'some-par-url'
const checkAuthSpy = spyOn(checkAuthService, 'checkAuth').and.returnValue(
of({
isAuthenticated: true,
configId: 'configId1',
idToken: '',
userData: { any: 'userData' },
accessToken: 'anyAccessToken',
})
); );
const checkAuthSpy = vi
.spyOn(checkAuthService, 'checkAuth')
.mockReturnValue(
of({
isAuthenticated: true,
configId: 'configId1',
idToken: '',
userData: { any: 'userData' },
accessToken: 'anyAccessToken',
})
);
const popupResult: PopupResult = { const popupResult: PopupResult = {
userClosed: false, userClosed: false,
receivedUrl: 'someUrl', receivedUrl: 'someUrl',
}; };
spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult)); vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of(popupResult)
);
service.loginWithPopUpPar(config, allConfigs).subscribe((result) => { service.loginWithPopUpPar(config, allConfigs).subscribe((result) => {
expect(checkAuthSpy).toHaveBeenCalledOnceWith( expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith(
config, config,
allConfigs, allConfigs,
'someUrl' 'someUrl'
@ -419,13 +436,13 @@ describe('ParLoginService', () => {
accessToken: 'anyAccessToken', accessToken: 'anyAccessToken',
}); });
}); });
})); });
it('returns correct properties if popup was closed by user', waitForAsync(() => { it('returns correct properties if popup was closed by user', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
@ -433,20 +450,24 @@ describe('ParLoginService', () => {
}; };
const allConfigs = [config]; const allConfigs = [config];
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(parService, 'postParRequest').and.returnValue( vi.spyOn(parService, 'postParRequest').mockReturnValue(
of({ requestUri: 'requestUri' } as ParResponse) of({ requestUri: 'requestUri' } as ParResponse)
); );
spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(
'some-par-url'
);
const checkAuthSpy = spyOn(checkAuthService, 'checkAuth'); const checkAuthSpy = vi.spyOn(checkAuthService, 'checkAuth');
const popupResult = { userClosed: true } as PopupResult; const popupResult = { userClosed: true } as PopupResult;
spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult)); vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of(popupResult)
);
service.loginWithPopUpPar(config, allConfigs).subscribe((result) => { service.loginWithPopUpPar(config, allConfigs).subscribe((result) => {
expect(checkAuthSpy).not.toHaveBeenCalled(); expect(checkAuthSpy).not.toHaveBeenCalled();
@ -459,6 +480,6 @@ describe('ParLoginService', () => {
accessToken: '', accessToken: '',
}); });
}); });
})); });
}); });
}); });

View File

@ -1,19 +1,19 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, of, throwError } from 'rxjs'; import { type Observable, of, throwError } from 'rxjs';
import { switchMap, take } from 'rxjs/operators'; import { switchMap, take } from 'rxjs/operators';
import { AuthOptions } from '../../auth-options'; import type { AuthOptions } from '../../auth-options';
import { CheckAuthService } from '../../auth-state/check-auth.service'; import { CheckAuthService } from '../../auth-state/check-auth.service';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { RedirectService } from '../../utils/redirect/redirect.service'; import { RedirectService } from '../../utils/redirect/redirect.service';
import { UrlService } from '../../utils/url/url.service'; import { UrlService } from '../../utils/url/url.service';
import { LoginResponse } from '../login-response'; import type { LoginResponse } from '../login-response';
import { PopupOptions } from '../popup/popup-options'; import type { PopupOptions } from '../popup/popup-options';
import { PopupResult } from '../popup/popup-result'; import type { PopupResult } from '../popup/popup-result';
import { PopUpService } from '../popup/popup.service'; import { PopUpService } from '../popup/popup.service';
import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service';
import { ParResponse } from './par-response'; import type { ParResponse } from './par-response';
import { ParService } from './par.service'; import { ParService } from './par.service';
@Injectable() @Injectable()

View File

@ -1,11 +1,12 @@
import { HttpHeaders } from '@angular/common/http'; import { TestBed } from '@/testing';
import { TestBed, waitForAsync } from '@angular/core/testing'; import { HttpHeaders } from '@ngify/http';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.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 { UrlService } from '../../utils/url/url.service';
import { ParService } from './par.service'; import { ParService } from './par.service';
@ -40,13 +41,15 @@ describe('ParService', () => {
}); });
describe('postParRequest', () => { describe('postParRequest', () => {
it('throws error if authWellKnownEndPoints does not exist in storage', waitForAsync(() => { it('throws error if authWellKnownEndPoints does not exist in storage', async () => {
spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue(
of(null) of(null)
); );
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue(null); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => null
);
service.postParRequest({ configId: 'configId1' }).subscribe({ service.postParRequest({ configId: 'configId1' }).subscribe({
error: (err) => { error: (err) => {
expect(err.message).toBe( expect(err.message).toBe(
@ -54,15 +57,17 @@ describe('ParService', () => {
); );
}, },
}); });
})); });
it('throws error if par endpoint does not exist in storage', waitForAsync(() => { it('throws error if par endpoint does not exist in storage', async () => {
spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue(
of(null) of(null)
); );
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ some: 'thing' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ some: 'thing' })
);
service.postParRequest({ configId: 'configId1' }).subscribe({ service.postParRequest({ configId: 'configId1' }).subscribe({
error: (err) => { error: (err) => {
expect(err.message).toBe( expect(err.message).toBe(
@ -70,77 +75,87 @@ describe('ParService', () => {
); );
}, },
}); });
})); });
it('calls data service with correct params', waitForAsync(() => { it('calls data service with correct params', async () => {
spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue(
of('some-url123') of('some-url123')
); );
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ parEndpoint: 'parEndpoint' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ parEndpoint: 'parEndpoint' })
);
const dataServiceSpy = spyOn(dataService, 'post').and.returnValue(of({})); const dataServiceSpy = vi
.spyOn(dataService, 'post')
.mockReturnValue(of({}));
service.postParRequest({ configId: 'configId1' }).subscribe(() => { service.postParRequest({ configId: 'configId1' }).subscribe(() => {
expect(dataServiceSpy).toHaveBeenCalledOnceWith( expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
'parEndpoint', 'parEndpoint',
'some-url123', 'some-url123',
{ configId: 'configId1' }, { configId: 'configId1' },
jasmine.any(HttpHeaders) expect.any(HttpHeaders)
); );
}); });
})); });
it('Gives back correct object properties', waitForAsync(() => { it('Gives back correct object properties', async () => {
spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue(
of('some-url456') of('some-url456')
); );
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ parEndpoint: 'parEndpoint' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
spyOn(dataService, 'post').and.returnValue( () => ({ parEndpoint: 'parEndpoint' })
);
vi.spyOn(dataService, 'post').mockReturnValue(
of({ expires_in: 123, request_uri: 'request_uri' }) of({ expires_in: 123, request_uri: 'request_uri' })
); );
service.postParRequest({ configId: 'configId1' }).subscribe((result) => { service.postParRequest({ configId: 'configId1' }).subscribe((result) => {
expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
}); });
})); });
it('throws error if data service has got an error', waitForAsync(() => { it('throws error if data service has got an error', async () => {
spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue(
of('some-url789') of('some-url789')
); );
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ parEndpoint: 'parEndpoint' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
spyOn(dataService, 'post').and.returnValue( () => ({ parEndpoint: 'parEndpoint' })
);
vi.spyOn(dataService, 'post').mockReturnValue(
throwError(() => new Error('ERROR')) throwError(() => new Error('ERROR'))
); );
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
service.postParRequest({ configId: 'configId1' }).subscribe({ service.postParRequest({ configId: 'configId1' }).subscribe({
error: (err) => { error: (err) => {
expect(err.message).toBe( expect(err.message).toBe(
'There was an error on ParService postParRequest' 'There was an error on ParService postParRequest'
); );
expect(loggerSpy).toHaveBeenCalledOnceWith( expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'There was an error on ParService postParRequest', 'There was an error on ParService postParRequest',
jasmine.any(Error) expect.any(Error)
); );
}, },
}); });
})); });
it('should retry once', waitForAsync(() => { it('should retry once', async () => {
spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue(
of('some-url456') of('some-url456')
); );
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ parEndpoint: 'parEndpoint' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
spyOn(dataService, 'post').and.returnValue( () => ({ parEndpoint: 'parEndpoint' })
);
vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('ERROR')), throwError(() => new Error('ERROR')),
of({ expires_in: 123, request_uri: 'request_uri' }) of({ expires_in: 123, request_uri: 'request_uri' })
@ -153,16 +168,18 @@ describe('ParService', () => {
expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
}, },
}); });
})); });
it('should retry twice', waitForAsync(() => { it('should retry twice', async () => {
spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue(
of('some-url456') of('some-url456')
); );
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ parEndpoint: 'parEndpoint' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
spyOn(dataService, 'post').and.returnValue( () => ({ parEndpoint: 'parEndpoint' })
);
vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('ERROR')), throwError(() => new Error('ERROR')),
throwError(() => new Error('ERROR')), throwError(() => new Error('ERROR')),
@ -176,16 +193,18 @@ describe('ParService', () => {
expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
}, },
}); });
})); });
it('should fail after three tries', waitForAsync(() => { it('should fail after three tries', async () => {
spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue(
of('some-url456') of('some-url456')
); );
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', { configId: 'configId1' }) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ parEndpoint: 'parEndpoint' }); ['authWellKnownEndPoints', { configId: 'configId1' }],
spyOn(dataService, 'post').and.returnValue( () => ({ parEndpoint: 'parEndpoint' })
);
vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('ERROR')), throwError(() => new Error('ERROR')),
throwError(() => new Error('ERROR')), throwError(() => new Error('ERROR')),
@ -199,6 +218,6 @@ describe('ParService', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
}); });
}); });

View File

@ -1,15 +1,16 @@
import { TestBed } from '@/testing';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { CheckAuthService } from '../../auth-state/check-auth.service'; import { CheckAuthService } from '../../auth-state/check-auth.service';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { UrlService } from '../../utils/url/url.service'; import { UrlService } from '../../utils/url/url.service';
import { LoginResponse } from '../login-response'; import type { LoginResponse } from '../login-response';
import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service';
import { PopUpLoginService } from './popup-login.service'; import { PopUpLoginService } from './popup-login.service';
import { PopupResult } from './popup-result'; import type { PopupResult } from './popup-result';
import { PopUpService } from './popup.service'; import { PopUpService } from './popup.service';
describe('PopUpLoginService', () => { describe('PopUpLoginService', () => {
@ -53,14 +54,14 @@ describe('PopUpLoginService', () => {
}); });
describe('loginWithPopUpStandard', () => { describe('loginWithPopUpStandard', () => {
it('does nothing if it has an invalid response type', waitForAsync(() => { it('does nothing if it has an invalid response type', async () => {
const config = { responseType: 'stubValue' }; const config = { responseType: 'stubValue' };
spyOn( vi.spyOn(
responseTypValidationService, responseTypValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(false); ).mockReturnValue(false);
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
popUpLoginService.loginWithPopUpStandard(config, [config]).subscribe({ popUpLoginService.loginWithPopUpStandard(config, [config]).subscribe({
error: (err) => { error: (err) => {
@ -68,27 +69,27 @@ describe('PopUpLoginService', () => {
expect(err.message).toBe('Invalid response type!'); expect(err.message).toBe('Invalid response type!');
}, },
}); });
})); });
it('calls urlService.getAuthorizeUrl() if everything fits', waitForAsync(() => { it('calls urlService.getAuthorizeUrl() if everything fits', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypValidationService, responseTypValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOnProperty(popupService, 'result$').and.returnValue( vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of({} as PopupResult) of({} as PopupResult)
); );
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
spyOn(checkAuthService, 'checkAuth').and.returnValue( vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue(
of({} as LoginResponse) of({} as LoginResponse)
); );
@ -97,74 +98,78 @@ describe('PopUpLoginService', () => {
.subscribe(() => { .subscribe(() => {
expect(urlService.getAuthorizeUrl).toHaveBeenCalled(); expect(urlService.getAuthorizeUrl).toHaveBeenCalled();
}); });
})); });
it('opens popup if everything fits', waitForAsync(() => { it('opens popup if everything fits', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypValidationService, responseTypValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
spyOnProperty(popupService, 'result$').and.returnValue( vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of({} as PopupResult) of({} as PopupResult)
); );
spyOn(checkAuthService, 'checkAuth').and.returnValue( vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue(
of({} as LoginResponse) of({} as LoginResponse)
); );
const popupSpy = spyOn(popupService, 'openPopUp'); const popupSpy = vi.spyOn(popupService, 'openPopUp');
popUpLoginService popUpLoginService
.loginWithPopUpStandard(config, [config]) .loginWithPopUpStandard(config, [config])
.subscribe(() => { .subscribe(() => {
expect(popupSpy).toHaveBeenCalled(); expect(popupSpy).toHaveBeenCalled();
}); });
})); });
it('returns three properties when popupservice received an url', waitForAsync(() => { it('returns three properties when popupservice received an url', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypValidationService, responseTypValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
spyOn(popupService, 'openPopUp'); vi.spyOn(popupService, 'openPopUp');
const checkAuthSpy = spyOn(checkAuthService, 'checkAuth').and.returnValue( const checkAuthSpy = vi
of({ .spyOn(checkAuthService, 'checkAuth')
isAuthenticated: true, .mockReturnValue(
configId: 'configId1', of({
idToken: '', isAuthenticated: true,
userData: { any: 'userData' }, configId: 'configId1',
accessToken: 'anyAccessToken', idToken: '',
}) userData: { any: 'userData' },
); accessToken: 'anyAccessToken',
})
);
const popupResult: PopupResult = { const popupResult: PopupResult = {
userClosed: false, userClosed: false,
receivedUrl: 'someUrl', receivedUrl: 'someUrl',
}; };
spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult)); vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of(popupResult)
);
popUpLoginService popUpLoginService
.loginWithPopUpStandard(config, [config]) .loginWithPopUpStandard(config, [config])
.subscribe((result) => { .subscribe((result) => {
expect(checkAuthSpy).toHaveBeenCalledOnceWith( expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith(
config, config,
[config], [config],
'someUrl' 'someUrl'
@ -178,31 +183,33 @@ describe('PopUpLoginService', () => {
accessToken: 'anyAccessToken', accessToken: 'anyAccessToken',
}); });
}); });
})); });
it('returns two properties if popup was closed by user', waitForAsync(() => { it('returns two properties if popup was closed by user', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
configId: 'configId1', configId: 'configId1',
}; };
spyOn( vi.spyOn(
responseTypValidationService, responseTypValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
spyOn(popupService, 'openPopUp'); vi.spyOn(popupService, 'openPopUp');
const checkAuthSpy = spyOn(checkAuthService, 'checkAuth').and.returnValue( const checkAuthSpy = vi
of({} as LoginResponse) .spyOn(checkAuthService, 'checkAuth')
); .mockReturnValue(of({} as LoginResponse));
const popupResult = { userClosed: true } as PopupResult; const popupResult = { userClosed: true } as PopupResult;
spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult)); vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of(popupResult)
);
popUpLoginService popUpLoginService
.loginWithPopUpStandard(config, [config]) .loginWithPopUpStandard(config, [config])
@ -217,6 +224,6 @@ describe('PopUpLoginService', () => {
accessToken: '', accessToken: '',
}); });
}); });
})); });
}); });
}); });

View File

@ -1,16 +1,16 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, of, throwError } from 'rxjs'; import { type Observable, of, throwError } from 'rxjs';
import { switchMap, take, tap } from 'rxjs/operators'; import { switchMap, take, tap } from 'rxjs/operators';
import { AuthOptions } from '../../auth-options'; import type { AuthOptions } from '../../auth-options';
import { CheckAuthService } from '../../auth-state/check-auth.service'; import { CheckAuthService } from '../../auth-state/check-auth.service';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { UrlService } from '../../utils/url/url.service'; import { UrlService } from '../../utils/url/url.service';
import { LoginResponse } from '../login-response'; import type { LoginResponse } from '../login-response';
import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service';
import { PopupOptions } from './popup-options'; import type { PopupOptions } from './popup-options';
import { PopupResult } from './popup-result'; import type { PopupResult } from './popup-result';
import { PopUpService } from './popup.service'; import { PopUpService } from './popup.service';
@Injectable() @Injectable()

View File

@ -1,9 +1,10 @@
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { TestBed, fakeAsync, tick } from '@/testing';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { PopupResult } from './popup-result'; import { mockProvider } from '../../testing/mock';
import type { PopupResult } from './popup-result';
import { PopUpService } from './popup.service'; import { PopUpService } from './popup.service';
describe('PopUpService', () => { describe('PopUpService', () => {
@ -18,9 +19,6 @@ describe('PopUpService', () => {
mockProvider(LoggerService), mockProvider(LoggerService),
], ],
}); });
});
beforeEach(() => {
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
loggerService = TestBed.inject(LoggerService); loggerService = TestBed.inject(LoggerService);
popUpService = TestBed.inject(PopUpService); popUpService = TestBed.inject(PopUpService);
@ -51,13 +49,13 @@ describe('PopUpService', () => {
describe('isCurrentlyInPopup', () => { describe('isCurrentlyInPopup', () => {
it('returns false if can not access Session Storage', () => { it('returns false if can not access Session Storage', () => {
// arrange // arrange
spyOn(popUpService as any, 'canAccessSessionStorage').and.returnValue( vi.spyOn(popUpService as any, 'canAccessSessionStorage').mockReturnValue(
false false
); );
spyOnProperty(popUpService as any, 'windowInternal').and.returnValue({ vi.spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({
opener: {} as Window, opener: {} as Window,
}); });
spyOn(storagePersistenceService, 'read').and.returnValue({ vi.spyOn(storagePersistenceService, 'read').mockReturnValue({
popupauth: true, popupauth: true,
}); });
const config = {} as OpenIdConfiguration; const config = {} as OpenIdConfiguration;
@ -71,10 +69,10 @@ describe('PopUpService', () => {
it('returns false if window has no opener', () => { it('returns false if window has no opener', () => {
// arrange // arrange
spyOn(popUpService as any, 'canAccessSessionStorage').and.returnValue( vi.spyOn(popUpService as any, 'canAccessSessionStorage').mockReturnValue(
true true
); );
spyOn(storagePersistenceService, 'read').and.returnValue({ vi.spyOn(storagePersistenceService, 'read').mockReturnValue({
popupauth: true, popupauth: true,
}); });
const config = {} as OpenIdConfiguration; const config = {} as OpenIdConfiguration;
@ -88,13 +86,13 @@ describe('PopUpService', () => {
it('returns true if isCurrentlyInPopup', () => { it('returns true if isCurrentlyInPopup', () => {
// arrange // arrange
spyOn(popUpService as any, 'canAccessSessionStorage').and.returnValue( vi.spyOn(popUpService as any, 'canAccessSessionStorage').mockReturnValue(
true true
); );
spyOnProperty(popUpService as any, 'windowInternal').and.returnValue({ vi.spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({
opener: {} as Window, opener: {} as Window,
}); });
spyOn(storagePersistenceService, 'read').and.returnValue({ vi.spyOn(storagePersistenceService, 'read').mockReturnValue({
popupauth: true, popupauth: true,
}); });
const config = {} as OpenIdConfiguration; const config = {} as OpenIdConfiguration;
@ -108,7 +106,7 @@ describe('PopUpService', () => {
}); });
describe('result$', () => { describe('result$', () => {
it('emits when internal subject is called', waitForAsync(() => { it('emits when internal subject is called', async () => {
const popupResult: PopupResult = { const popupResult: PopupResult = {
userClosed: false, userClosed: false,
receivedUrl: 'some-url1111', receivedUrl: 'some-url1111',
@ -119,62 +117,62 @@ describe('PopUpService', () => {
}); });
(popUpService as any).resultInternal$.next(popupResult); (popUpService as any).resultInternal$.next(popupResult);
})); });
}); });
describe('openPopup', () => { describe('openPopup', () => {
it('popup opens with parameters and default options', waitForAsync(() => { it('popup opens with parameters and default options', async () => {
// arrange // arrange
const popupSpy = spyOn(window, 'open').and.callFake( const popupSpy = vi.spyOn(window, 'open').and.callFake(
() => () =>
({ ({
closed: true, closed: true,
close: () => undefined, close: () => undefined,
} as Window) }) as Window
); );
// act // act
popUpService.openPopUp('url', {}, { configId: 'configId1' }); popUpService.openPopUp('url', {}, { configId: 'configId1' });
// assert // assert
expect(popupSpy).toHaveBeenCalledOnceWith( expect(popupSpy).toHaveBeenCalledExactlyOnceWith(
'url', 'url',
'_blank', '_blank',
jasmine.any(String) expect.any(String)
); );
})); });
it('popup opens with parameters and passed options', waitForAsync(() => { it('popup opens with parameters and passed options', async () => {
// arrange // arrange
const popupSpy = spyOn(window, 'open').and.callFake( const popupSpy = vi.spyOn(window, 'open').and.callFake(
() => () =>
({ ({
closed: true, closed: true,
close: () => undefined, close: () => undefined,
} as Window) }) as Window
); );
// act // act
popUpService.openPopUp('url', { width: 100 }, { configId: 'configId1' }); popUpService.openPopUp('url', { width: 100 }, { configId: 'configId1' });
// assert // assert
expect(popupSpy).toHaveBeenCalledOnceWith( expect(popupSpy).toHaveBeenCalledExactlyOnceWith(
'url', 'url',
'_blank', '_blank',
jasmine.any(String) expect.any(String)
); );
})); });
it('logs error and return if popup could not be opened', () => { it('logs error and return if popup could not be opened', () => {
// arrange // arrange
spyOn(window, 'open').and.callFake(() => null); vi.spyOn(window, 'open').mockImplementation(() => null);
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
// act // act
popUpService.openPopUp('url', { width: 100 }, { configId: 'configId1' }); popUpService.openPopUp('url', { width: 100 }, { configId: 'configId1' });
// assert // assert
expect(loggerSpy).toHaveBeenCalledOnceWith( expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'Could not open popup' 'Could not open popup'
); );
@ -191,21 +189,21 @@ describe('PopUpService', () => {
close: () => undefined, close: () => undefined,
} as Window; } as Window;
spyOn(window, 'open').and.returnValue(popup); vi.spyOn(window, 'open').mockReturnValue(popup);
cleanUpSpy = spyOn(popUpService as any, 'cleanUp').and.callThrough(); cleanUpSpy = vi.spyOn(popUpService as any, 'cleanUp')();
popupResult = {} as PopupResult; popupResult = {} as PopupResult;
popUpService.result$.subscribe((result) => (popupResult = result)); popUpService.result$.subscribe((result) => (popupResult = result));
}); });
it('message received with data', fakeAsync(() => { it('message received with data', async () => {
let listener: (event: MessageEvent) => void = () => { let listener: (event: MessageEvent) => void = () => {
return; return;
}; };
spyOn(window, 'addEventListener').and.callFake( vi.spyOn(window, 'addEventListener').and.callFake(
(_: any, func: any) => (listener = func) (_: any, func: any) => (listener = func)
); );
@ -222,20 +220,20 @@ describe('PopUpService', () => {
userClosed: false, userClosed: false,
receivedUrl: 'some-url1111', receivedUrl: 'some-url1111',
}); });
expect(cleanUpSpy).toHaveBeenCalledOnceWith(listener, { expect(cleanUpSpy).toHaveBeenCalledExactlyOnceWith(listener, {
configId: 'configId1', configId: 'configId1',
}); });
})); });
it('message received without data does return but cleanup does not throw event', fakeAsync(() => { it('message received without data does return but cleanup does not throw event', async () => {
let listener: (event: MessageEvent) => void = () => { let listener: (event: MessageEvent) => void = () => {
return; return;
}; };
spyOn(window, 'addEventListener').and.callFake( vi.spyOn(window, 'addEventListener').and.callFake(
(_: any, func: any) => (listener = func) (_: any, func: any) => (listener = func)
); );
const nextSpy = spyOn((popUpService as any).resultInternal$, 'next'); const nextSpy = vi.spyOn((popUpService as any).resultInternal$, 'next');
popUpService.openPopUp('url', {}, { configId: 'configId1' }); popUpService.openPopUp('url', {}, { configId: 'configId1' });
@ -249,9 +247,9 @@ describe('PopUpService', () => {
expect(popupResult).toEqual({} as PopupResult); expect(popupResult).toEqual({} as PopupResult);
expect(cleanUpSpy).toHaveBeenCalled(); expect(cleanUpSpy).toHaveBeenCalled();
expect(nextSpy).not.toHaveBeenCalled(); expect(nextSpy).not.toHaveBeenCalled();
})); });
it('user closed', fakeAsync(() => { it('user closed', async () => {
popUpService.openPopUp('url', undefined, { configId: 'configId1' }); popUpService.openPopUp('url', undefined, { configId: 'configId1' });
expect(popupResult).toEqual({} as PopupResult); expect(popupResult).toEqual({} as PopupResult);
@ -266,48 +264,48 @@ describe('PopUpService', () => {
receivedUrl: '', receivedUrl: '',
} as PopupResult); } as PopupResult);
expect(cleanUpSpy).toHaveBeenCalled(); expect(cleanUpSpy).toHaveBeenCalled();
})); });
}); });
}); });
describe('sendMessageToMainWindow', () => { describe('sendMessageToMainWindow', () => {
it('does nothing if window.opener is null', waitForAsync(() => { it('does nothing if window.opener is null', async () => {
// arrange // arrange
spyOnProperty(window, 'opener').and.returnValue(null); vi.spyOnProperty(window, 'opener').mockReturnValue(null);
const sendMessageSpy = spyOn(popUpService as any, 'sendMessage'); const sendMessageSpy = vi.spyOn(popUpService as any, 'sendMessage');
// act // act
popUpService.sendMessageToMainWindow('', {}); popUpService.sendMessageToMainWindow('', {});
// assert // assert
expect(sendMessageSpy).not.toHaveBeenCalled(); expect(sendMessageSpy).not.toHaveBeenCalled();
})); });
it('calls postMessage when window opener is given', waitForAsync(() => { it('calls postMessage when window opener is given', async () => {
// arrange // arrange
spyOnProperty(window, 'opener').and.returnValue({ vi.spyOnProperty(window, 'opener').mockReturnValue({
postMessage: () => undefined, postMessage: () => undefined,
}); });
const sendMessageSpy = spyOn(window.opener, 'postMessage'); const sendMessageSpy = vi.spyOn(window.opener, 'postMessage');
// act // act
popUpService.sendMessageToMainWindow('someUrl', {}); popUpService.sendMessageToMainWindow('someUrl', {});
// assert // assert
expect(sendMessageSpy).toHaveBeenCalledOnceWith( expect(sendMessageSpy).toHaveBeenCalledExactlyOnceWith(
'someUrl', 'someUrl',
jasmine.any(String) expect.any(String)
); );
})); });
}); });
describe('cleanUp', () => { describe('cleanUp', () => {
it('calls removeEventListener on window with correct params', waitForAsync(() => { it('calls removeEventListener on window with correct params', async () => {
// arrange // arrange
const spy = spyOn(window, 'removeEventListener').and.callFake( const spy = vi
() => undefined .spyOn(window, 'removeEventListener')
); .mockImplementation(() => undefined);
const listener: any = null; const listener: any = null;
// act // act
@ -315,29 +313,29 @@ describe('PopUpService', () => {
// assert // assert
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledOnceWith('message', listener, false); expect(spy).toHaveBeenCalledExactlyOnceWith('message', listener, false);
})); });
it('removes popup from sessionstorage, closes and nulls when popup is opened', waitForAsync(() => { it('removes popup from sessionstorage, closes and nulls when popup is opened', async () => {
// arrange // arrange
const popupMock = { const popupMock = {
anyThing: 'truthy', anyThing: 'truthy',
sessionStorage: mockStorage, sessionStorage: mockStorage,
close: (): void => undefined, close: (): void => undefined,
}; };
const removeItemSpy = spyOn(storagePersistenceService, 'remove'); const removeItemSpy = vi.spyOn(storagePersistenceService, 'remove');
const closeSpy = spyOn(popupMock, 'close'); const closeSpy = vi.spyOn(popupMock, 'close');
// act // act
(popUpService as any).popUp = popupMock; (popUpService as any).popUp = popupMock;
(popUpService as any).cleanUp(null, { configId: 'configId1' }); (popUpService as any).cleanUp(null, { configId: 'configId1' });
// assert // assert
expect(removeItemSpy).toHaveBeenCalledOnceWith('popupauth', { expect(removeItemSpy).toHaveBeenCalledExactlyOnceWith('popupauth', {
configId: 'configId1', configId: 'configId1',
}); });
expect(closeSpy).toHaveBeenCalledTimes(1); expect(closeSpy).toHaveBeenCalledTimes(1);
expect((popUpService as any).popUp).toBeNull(); expect((popUpService as any).popUp).toBeNull();
})); });
}); });
}); });

View File

@ -1,6 +1,7 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { FlowHelper } from '../../utils/flowHelper/flow-helper.service'; import { FlowHelper } from '../../utils/flowHelper/flow-helper.service';
import { ResponseTypeValidationService } from './response-type-validation.service'; import { ResponseTypeValidationService } from './response-type-validation.service';
@ -32,7 +33,9 @@ describe('ResponseTypeValidationService', () => {
describe('hasConfigValidResponseType', () => { describe('hasConfigValidResponseType', () => {
it('returns true if current configured flow is any implicit flow', () => { it('returns true if current configured flow is any implicit flow', () => {
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true); vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
true
);
const result = responseTypeValidationService.hasConfigValidResponseType({ const result = responseTypeValidationService.hasConfigValidResponseType({
configId: 'configId1', configId: 'configId1',
@ -42,8 +45,10 @@ describe('ResponseTypeValidationService', () => {
}); });
it('returns true if current configured flow is code flow', () => { it('returns true if current configured flow is code flow', () => {
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(false); vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); false
);
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const result = responseTypeValidationService.hasConfigValidResponseType({ const result = responseTypeValidationService.hasConfigValidResponseType({
configId: 'configId1', configId: 'configId1',
@ -53,8 +58,10 @@ describe('ResponseTypeValidationService', () => {
}); });
it('returns false if current configured flow is neither code nor implicit flow', () => { it('returns false if current configured flow is neither code nor implicit flow', () => {
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(false); vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); false
);
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
const result = responseTypeValidationService.hasConfigValidResponseType({ const result = responseTypeValidationService.hasConfigValidResponseType({
configId: 'configId1', configId: 'configId1',

View File

@ -1,9 +1,10 @@
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { TestBed, fakeAsync, tick } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock'; import { vi } from 'vitest';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
import { FlowsDataService } from '../../flows/flows-data.service'; import { FlowsDataService } from '../../flows/flows-data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { RedirectService } from '../../utils/redirect/redirect.service'; import { RedirectService } from '../../utils/redirect/redirect.service';
import { UrlService } from '../../utils/url/url.service'; import { UrlService } from '../../utils/url/url.service';
import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service';
@ -51,12 +52,12 @@ describe('StandardLoginService', () => {
}); });
describe('loginStandard', () => { describe('loginStandard', () => {
it('does nothing if it has an invalid response type', waitForAsync(() => { it('does nothing if it has an invalid response type', async () => {
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(false); ).mockReturnValue(false);
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const result = standardLoginService.loginStandard({ const result = standardLoginService.loginStandard({
configId: 'configId1', configId: 'configId1',
@ -64,95 +65,92 @@ describe('StandardLoginService', () => {
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
})); });
it('calls flowsDataService.setCodeFlowInProgress() if everything fits', waitForAsync(() => { it('calls flowsDataService.setCodeFlowInProgress() if everything fits', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const flowsDataSpy = spyOn(flowsDataService, 'setCodeFlowInProgress'); const flowsDataSpy = vi.spyOn(flowsDataService, 'setCodeFlowInProgress');
const result = standardLoginService.loginStandard(config); const result = standardLoginService.loginStandard(config);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(flowsDataSpy).toHaveBeenCalled(); expect(flowsDataSpy).toHaveBeenCalled();
})); });
it('calls urlService.getAuthorizeUrl() if everything fits', waitForAsync(() => { it('calls urlService.getAuthorizeUrl() if everything fits', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const result = standardLoginService.loginStandard(config); const result = standardLoginService.loginStandard(config);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
})); });
it('redirects to URL with no URL handler', fakeAsync(() => { it('redirects to URL with no URL handler', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const redirectSpy = spyOn( const redirectSpy = vi.spyOn(redirectService, 'redirectTo')();
redirectService,
'redirectTo'
).and.callThrough();
standardLoginService.loginStandard(config); standardLoginService.loginStandard(config);
tick(); tick();
expect(redirectSpy).toHaveBeenCalledOnceWith('someUrl'); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl');
})); });
it('redirects to URL with URL handler when urlHandler is given', fakeAsync(() => { it('redirects to URL with URL handler when urlHandler is given', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const redirectSpy = spyOn(redirectService, 'redirectTo').and.callFake( const redirectSpy = vi
() => undefined .spyOn(redirectService, 'redirectTo')
); .mockImplementation(() => undefined);
const spy = jasmine.createSpy(); const spy = jasmine.createSpy();
const urlHandler = (url: any): void => { const urlHandler = (url: any): void => {
spy(url); spy(url);
@ -160,94 +158,96 @@ describe('StandardLoginService', () => {
standardLoginService.loginStandard(config, { urlHandler }); standardLoginService.loginStandard(config, { urlHandler });
tick(); tick();
expect(spy).toHaveBeenCalledOnceWith('someUrl'); expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl');
expect(redirectSpy).not.toHaveBeenCalled(); expect(redirectSpy).not.toHaveBeenCalled();
})); });
it('calls resetSilentRenewRunning', fakeAsync(() => { it('calls resetSilentRenewRunning', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const flowsDataSpy = spyOn(flowsDataService, 'resetSilentRenewRunning'); const flowsDataSpy = vi.spyOn(
flowsDataService,
'resetSilentRenewRunning'
);
standardLoginService.loginStandard(config, {}); standardLoginService.loginStandard(config, {});
tick(); tick();
expect(flowsDataSpy).toHaveBeenCalled(); expect(flowsDataSpy).toHaveBeenCalled();
})); });
it('calls getAuthorizeUrl with custom params if they are given as parameter', fakeAsync(() => { it('calls getAuthorizeUrl with custom params if they are given as parameter', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
const getAuthorizeUrlSpy = spyOn( const getAuthorizeUrlSpy = vi
urlService, .spyOn(urlService, 'getAuthorizeUrl')
'getAuthorizeUrl' .mockReturnValue(of('someUrl'));
).and.returnValue(of('someUrl')); const redirectSpy = vi
const redirectSpy = spyOn(redirectService, 'redirectTo').and.callFake( .spyOn(redirectService, 'redirectTo')
() => undefined .mockImplementation(() => undefined);
);
standardLoginService.loginStandard(config, { standardLoginService.loginStandard(config, {
customParams: { to: 'add', as: 'well' }, customParams: { to: 'add', as: 'well' },
}); });
tick(); tick();
expect(redirectSpy).toHaveBeenCalledOnceWith('someUrl'); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl');
expect(getAuthorizeUrlSpy).toHaveBeenCalledOnceWith(config, { expect(getAuthorizeUrlSpy).toHaveBeenCalledExactlyOnceWith(config, {
customParams: { to: 'add', as: 'well' }, customParams: { to: 'add', as: 'well' },
}); });
})); });
it('does nothing, logs only if getAuthorizeUrl returns falsy', fakeAsync(() => { it('does nothing, logs only if getAuthorizeUrl returns falsy', async () => {
const config = { const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}; };
spyOn( vi.spyOn(
responseTypeValidationService, responseTypeValidationService,
'hasConfigValidResponseType' 'hasConfigValidResponseType'
).and.returnValue(true); ).mockReturnValue(true);
spyOn( vi.spyOn(
authWellKnownService, authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({})); ).mockReturnValue(of({}));
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of(''));
const redirectSpy = spyOn(redirectService, 'redirectTo').and.callFake( const redirectSpy = vi
() => undefined .spyOn(redirectService, 'redirectTo')
); .mockImplementation(() => undefined);
standardLoginService.loginStandard(config); standardLoginService.loginStandard(config);
tick(); tick();
expect(loggerSpy).toHaveBeenCalledOnceWith( expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
config, config,
'Could not create URL', 'Could not create URL',
'' ''
); );
expect(redirectSpy).not.toHaveBeenCalled(); expect(redirectSpy).not.toHaveBeenCalled();
})); });
}); });
}); });

View File

@ -1,13 +1,14 @@
import { HttpHeaders } from '@angular/common/http'; import { TestBed } from '@/testing';
import { TestBed, waitForAsync } from '@angular/core/testing'; import type { HttpHeaders } from '@ngify/http';
import { Observable, of, throwError } from 'rxjs'; import { Observable, of, throwError } from 'rxjs';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { createRetriableStream } from '../../test/create-retriable-stream.helper';
import { DataService } from '../api/data.service'; import { DataService } from '../api/data.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { CheckSessionService } from '../iframe/check-session.service'; import { CheckSessionService } from '../iframe/check-session.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { createRetriableStream } from '../testing/create-retriable-stream.helper';
import { mockProvider } from '../testing/mock';
import { RedirectService } from '../utils/redirect/redirect.service'; import { RedirectService } from '../utils/redirect/redirect.service';
import { UrlService } from '../utils/url/url.service'; import { UrlService } from '../utils/url/url.service';
import { LogoffRevocationService } from './logoff-revocation.service'; import { LogoffRevocationService } from './logoff-revocation.service';
@ -55,70 +56,70 @@ describe('Logout and Revoke Service', () => {
it('uses token parameter if token as parameter is passed in the method', () => { it('uses token parameter if token as parameter is passed in the method', () => {
// Arrange // Arrange
const paramToken = 'passedTokenAsParam'; const paramToken = 'passedTokenAsParam';
const revocationSpy = spyOn( const revocationSpy = vi.spyOn(
urlService, urlService,
'createRevocationEndpointBodyAccessToken' 'createRevocationEndpointBodyAccessToken'
); );
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(dataService, 'post').and.returnValue(of(null)); vi.spyOn(dataService, 'post').mockReturnValue(of(null));
// Act // Act
service.revokeAccessToken(config, paramToken); service.revokeAccessToken(config, paramToken);
// Assert // Assert
expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config); expect(revocationSpy).toHaveBeenCalledExactlyOnceWith(paramToken, config);
}); });
it('uses token parameter from persistence if no param is provided', () => { it('uses token parameter from persistence if no param is provided', () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
paramToken paramToken
); );
const revocationSpy = spyOn( const revocationSpy = vi.spyOn(
urlService, urlService,
'createRevocationEndpointBodyAccessToken' 'createRevocationEndpointBodyAccessToken'
); );
spyOn(dataService, 'post').and.returnValue(of(null)); vi.spyOn(dataService, 'post').mockReturnValue(of(null));
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
service.revokeAccessToken(config); service.revokeAccessToken(config);
// Assert // Assert
expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config); expect(revocationSpy).toHaveBeenCalledExactlyOnceWith(paramToken, config);
}); });
it('returns type observable', () => { it('returns type observable', () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
spyOn(dataService, 'post').and.returnValue(of(null)); vi.spyOn(dataService, 'post').mockReturnValue(of(null));
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
const result = service.revokeAccessToken(config); const result = service.revokeAccessToken(config);
// Assert // Assert
expect(result).toEqual(jasmine.any(Observable)); expect(result).toEqual(expect.any(Observable));
}); });
it('loggs and returns unmodified response if request is positive', waitForAsync(() => { it('loggs and returns unmodified response if request is positive', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logDebug'); const loggerSpy = vi.spyOn(loggerService, 'logDebug');
spyOn(dataService, 'post').and.returnValue(of({ data: 'anything' })); vi.spyOn(dataService, 'post').mockReturnValue(of({ data: 'anything' }));
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
@ -127,20 +128,20 @@ describe('Logout and Revoke Service', () => {
expect(result).toEqual({ data: 'anything' }); expect(result).toEqual({ data: 'anything' });
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}); });
})); });
it('loggs error when request is negative', waitForAsync(() => { it('loggs error when request is negative', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(dataService, 'post').and.returnValue( vi.spyOn(dataService, 'post').mockReturnValue(
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
@ -151,20 +152,20 @@ describe('Logout and Revoke Service', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('should retry once', waitForAsync(() => { it('should retry once', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logDebug'); const loggerSpy = vi.spyOn(loggerService, 'logDebug');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(dataService, 'post').and.returnValue( vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
of({ data: 'anything' }) of({ data: 'anything' })
@ -179,20 +180,20 @@ describe('Logout and Revoke Service', () => {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}, },
}); });
})); });
it('should retry twice', waitForAsync(() => { it('should retry twice', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logDebug'); const loggerSpy = vi.spyOn(loggerService, 'logDebug');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(dataService, 'post').and.returnValue( vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
@ -208,20 +209,20 @@ describe('Logout and Revoke Service', () => {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}, },
}); });
})); });
it('should fail after three tries', waitForAsync(() => { it('should fail after three tries', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(dataService, 'post').and.returnValue( vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
@ -236,76 +237,76 @@ describe('Logout and Revoke Service', () => {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}, },
}); });
})); });
}); });
describe('revokeRefreshToken', () => { describe('revokeRefreshToken', () => {
it('uses refresh token parameter if token as parameter is passed in the method', () => { it('uses refresh token parameter if token as parameter is passed in the method', () => {
// Arrange // Arrange
const paramToken = 'passedTokenAsParam'; const paramToken = 'passedTokenAsParam';
const revocationSpy = spyOn( const revocationSpy = vi.spyOn(
urlService, urlService,
'createRevocationEndpointBodyRefreshToken' 'createRevocationEndpointBodyRefreshToken'
); );
spyOn(dataService, 'post').and.returnValue(of(null)); vi.spyOn(dataService, 'post').mockReturnValue(of(null));
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
service.revokeRefreshToken(config, paramToken); service.revokeRefreshToken(config, paramToken);
// Assert // Assert
expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config); expect(revocationSpy).toHaveBeenCalledExactlyOnceWith(paramToken, config);
}); });
it('uses refresh token parameter from persistence if no param is provided', () => { it('uses refresh token parameter from persistence if no param is provided', () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const revocationSpy = spyOn( const revocationSpy = vi.spyOn(
urlService, urlService,
'createRevocationEndpointBodyRefreshToken' 'createRevocationEndpointBodyRefreshToken'
); );
spyOn(dataService, 'post').and.returnValue(of(null)); vi.spyOn(dataService, 'post').mockReturnValue(of(null));
// Act // Act
service.revokeRefreshToken(config); service.revokeRefreshToken(config);
// Assert // Assert
expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config); expect(revocationSpy).toHaveBeenCalledExactlyOnceWith(paramToken, config);
}); });
it('returns type observable', () => { it('returns type observable', () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
spyOn(dataService, 'post').and.returnValue(of(null)); vi.spyOn(dataService, 'post').mockReturnValue(of(null));
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
const result = service.revokeRefreshToken(config); const result = service.revokeRefreshToken(config);
// Assert // Assert
expect(result).toEqual(jasmine.any(Observable)); expect(result).toEqual(expect.any(Observable));
}); });
it('loggs and returns unmodified response if request is positive', waitForAsync(() => { it('loggs and returns unmodified response if request is positive', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logDebug'); const loggerSpy = vi.spyOn(loggerService, 'logDebug');
spyOn(dataService, 'post').and.returnValue(of({ data: 'anything' })); vi.spyOn(dataService, 'post').mockReturnValue(of({ data: 'anything' }));
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
@ -314,20 +315,20 @@ describe('Logout and Revoke Service', () => {
expect(result).toEqual({ data: 'anything' }); expect(result).toEqual({ data: 'anything' });
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}); });
})); });
it('loggs error when request is negative', waitForAsync(() => { it('loggs error when request is negative', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(dataService, 'post').and.returnValue( vi.spyOn(dataService, 'post').mockReturnValue(
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
@ -338,20 +339,20 @@ describe('Logout and Revoke Service', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('should retry once', waitForAsync(() => { it('should retry once', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logDebug'); const loggerSpy = vi.spyOn(loggerService, 'logDebug');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(dataService, 'post').and.returnValue( vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
of({ data: 'anything' }) of({ data: 'anything' })
@ -366,20 +367,20 @@ describe('Logout and Revoke Service', () => {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}, },
}); });
})); });
it('should retry twice', waitForAsync(() => { it('should retry twice', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logDebug'); const loggerSpy = vi.spyOn(loggerService, 'logDebug');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(dataService, 'post').and.returnValue( vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
@ -395,20 +396,20 @@ describe('Logout and Revoke Service', () => {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}, },
}); });
})); });
it('should fail after three tries', waitForAsync(() => { it('should fail after three tries', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken');
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(dataService, 'post').and.returnValue( vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream( createRetriableStream(
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
throwError(() => new Error('Error')), throwError(() => new Error('Error')),
@ -423,15 +424,15 @@ describe('Logout and Revoke Service', () => {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}, },
}); });
})); });
}); });
describe('logoff', () => { describe('logoff', () => {
it('logs and returns if `endSessionUrl` is false', waitForAsync(() => { it('logs and returns if `endSessionUrl` is false', async () => {
// Arrange // Arrange
spyOn(urlService, 'getEndSessionUrl').and.returnValue(''); vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('');
const serverStateChangedSpy = spyOn( const serverStateChangedSpy = vi.spyOn(
checkSessionService, checkSessionService,
'serverStateChanged' 'serverStateChanged'
); );
@ -444,14 +445,14 @@ describe('Logout and Revoke Service', () => {
result$.subscribe(() => { result$.subscribe(() => {
expect(serverStateChangedSpy).not.toHaveBeenCalled(); expect(serverStateChangedSpy).not.toHaveBeenCalled();
}); });
})); });
it('logs and returns if `serverStateChanged` is true', waitForAsync(() => { it('logs and returns if `serverStateChanged` is true', async () => {
// Arrange // Arrange
spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue');
const redirectSpy = spyOn(redirectService, 'redirectTo'); const redirectSpy = vi.spyOn(redirectService, 'redirectTo');
spyOn(checkSessionService, 'serverStateChanged').and.returnValue(true); vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue(true);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
@ -461,22 +462,24 @@ describe('Logout and Revoke Service', () => {
result$.subscribe(() => { result$.subscribe(() => {
expect(redirectSpy).not.toHaveBeenCalled(); expect(redirectSpy).not.toHaveBeenCalled();
}); });
})); });
it('calls urlHandler if urlhandler is passed', waitForAsync(() => { it('calls urlHandler if urlhandler is passed', async () => {
// Arrange // Arrange
spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue');
const spy = jasmine.createSpy(); const spy = jasmine.createSpy();
const urlHandler = (url: string): void => { const urlHandler = (url: string): void => {
spy(url); spy(url);
}; };
const redirectSpy = spyOn(redirectService, 'redirectTo'); const redirectSpy = vi.spyOn(redirectService, 'redirectTo');
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue(
false
);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
@ -485,18 +488,20 @@ describe('Logout and Revoke Service', () => {
// Assert // Assert
result$.subscribe(() => { result$.subscribe(() => {
expect(redirectSpy).not.toHaveBeenCalled(); expect(redirectSpy).not.toHaveBeenCalled();
expect(spy).toHaveBeenCalledOnceWith('someValue'); expect(spy).toHaveBeenCalledExactlyOnceWith('someValue');
expect(resetAuthorizationDataSpy).toHaveBeenCalled(); expect(resetAuthorizationDataSpy).toHaveBeenCalled();
}); });
})); });
it('calls redirect service if no logoutOptions are passed', waitForAsync(() => { it('calls redirect service if no logoutOptions are passed', async () => {
// Arrange // Arrange
spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue');
const redirectSpy = spyOn(redirectService, 'redirectTo'); const redirectSpy = vi.spyOn(redirectService, 'redirectTo');
spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue(
false
);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
@ -504,17 +509,19 @@ describe('Logout and Revoke Service', () => {
// Assert // Assert
result$.subscribe(() => { result$.subscribe(() => {
expect(redirectSpy).toHaveBeenCalledOnceWith('someValue'); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue');
}); });
})); });
it('calls redirect service if logoutOptions are passed and method is GET', waitForAsync(() => { it('calls redirect service if logoutOptions are passed and method is GET', async () => {
// Arrange // Arrange
spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue');
const redirectSpy = spyOn(redirectService, 'redirectTo'); const redirectSpy = vi.spyOn(redirectService, 'redirectTo');
spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue(
false
);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
@ -522,28 +529,30 @@ describe('Logout and Revoke Service', () => {
// Assert // Assert
result$.subscribe(() => { result$.subscribe(() => {
expect(redirectSpy).toHaveBeenCalledOnceWith('someValue'); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue');
}); });
})); });
it('calls dataservice post if logoutOptions are passed and method is POST', waitForAsync(() => { it('calls dataservice post if logoutOptions are passed and method is POST', async () => {
// Arrange // Arrange
spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue');
const redirectSpy = spyOn(redirectService, 'redirectTo'); const redirectSpy = vi.spyOn(redirectService, 'redirectTo');
spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue(
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( false
);
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'id-token' 'id-token'
); );
spyOn(urlService, 'getPostLogoutRedirectUrl').and.returnValue( vi.spyOn(urlService, 'getPostLogoutRedirectUrl').mockReturnValue(
'post-logout-redirect-url' 'post-logout-redirect-url'
); );
spyOn(urlService, 'getEndSessionEndpoint').and.returnValue({ vi.spyOn(urlService, 'getEndSessionEndpoint').mockReturnValue({
url: 'some-url', url: 'some-url',
existingParams: '', existingParams: '',
}); });
const postSpy = spyOn(dataService, 'post').and.returnValue(of(null)); const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of(null));
const config = { configId: 'configId1', clientId: 'clientId' }; const config = { configId: 'configId1', clientId: 'clientId' };
// Act // Act
@ -554,7 +563,7 @@ describe('Logout and Revoke Service', () => {
// Assert // Assert
result$.subscribe(() => { result$.subscribe(() => {
expect(redirectSpy).not.toHaveBeenCalled(); expect(redirectSpy).not.toHaveBeenCalled();
expect(postSpy).toHaveBeenCalledOnceWith( expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'some-url', 'some-url',
{ {
id_token_hint: 'id-token', id_token_hint: 'id-token',
@ -562,36 +571,38 @@ describe('Logout and Revoke Service', () => {
post_logout_redirect_uri: 'post-logout-redirect-url', post_logout_redirect_uri: 'post-logout-redirect-url',
}, },
config, config,
jasmine.anything() expect.anything()
); );
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTrue(); expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
})); });
it('calls dataservice post if logoutOptions with customParams are passed and method is POST', waitForAsync(() => { it('calls dataservice post if logoutOptions with customParams are passed and method is POST', async () => {
// Arrange // Arrange
spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue');
const redirectSpy = spyOn(redirectService, 'redirectTo'); const redirectSpy = vi.spyOn(redirectService, 'redirectTo');
spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue(
spyOn(storagePersistenceService, 'getIdToken').and.returnValue( false
);
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'id-token' 'id-token'
); );
spyOn(urlService, 'getPostLogoutRedirectUrl').and.returnValue( vi.spyOn(urlService, 'getPostLogoutRedirectUrl').mockReturnValue(
'post-logout-redirect-url' 'post-logout-redirect-url'
); );
spyOn(urlService, 'getEndSessionEndpoint').and.returnValue({ vi.spyOn(urlService, 'getEndSessionEndpoint').mockReturnValue({
url: 'some-url', url: 'some-url',
existingParams: '', existingParams: '',
}); });
const postSpy = spyOn(dataService, 'post').and.returnValue(of(null)); const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of(null));
const config = { configId: 'configId1', clientId: 'clientId' }; const config = { configId: 'configId1', clientId: 'clientId' };
// Act // Act
@ -607,7 +618,7 @@ describe('Logout and Revoke Service', () => {
// Assert // Assert
result$.subscribe(() => { result$.subscribe(() => {
expect(redirectSpy).not.toHaveBeenCalled(); expect(redirectSpy).not.toHaveBeenCalled();
expect(postSpy).toHaveBeenCalledOnceWith( expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'some-url', 'some-url',
{ {
id_token_hint: 'id-token', id_token_hint: 'id-token',
@ -618,23 +629,23 @@ describe('Logout and Revoke Service', () => {
ui_locales: 'de fr en', ui_locales: 'de fr en',
}, },
config, config,
jasmine.anything() expect.anything()
); );
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTrue(); expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
})); });
}); });
describe('logoffLocal', () => { describe('logoffLocal', () => {
it('calls flowsService.resetAuthorizationData', () => { it('calls flowsService.resetAuthorizationData', () => {
// Arrange // Arrange
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
@ -649,25 +660,25 @@ describe('Logout and Revoke Service', () => {
}); });
describe('logoffAndRevokeTokens', () => { describe('logoffAndRevokeTokens', () => {
it('calls revokeRefreshToken and revokeAccessToken when storage holds a refreshtoken', waitForAsync(() => { it('calls revokeRefreshToken and revokeAccessToken when storage holds a refreshtoken', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ revocationEndpoint: 'revocationEndpoint' }); ['authWellKnownEndPoints', config],
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( () => ({ revocationEndpoint: 'revocationEndpoint' })
);
vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
const revokeRefreshTokenSpy = spyOn( const revokeRefreshTokenSpy = vi
service, .spyOn(service, 'revokeRefreshToken')
'revokeRefreshToken' .mockReturnValue(of({ any: 'thing' }));
).and.returnValue(of({ any: 'thing' })); const revokeAccessTokenSpy = vi
const revokeAccessTokenSpy = spyOn( .spyOn(service, 'revokeAccessToken')
service, .mockReturnValue(of({ any: 'thing' }));
'revokeAccessToken'
).and.returnValue(of({ any: 'thing' }));
// Act // Act
service.logoffAndRevokeTokens(config, [config]).subscribe(() => { service.logoffAndRevokeTokens(config, [config]).subscribe(() => {
@ -675,25 +686,27 @@ describe('Logout and Revoke Service', () => {
expect(revokeRefreshTokenSpy).toHaveBeenCalled(); expect(revokeRefreshTokenSpy).toHaveBeenCalled();
expect(revokeAccessTokenSpy).toHaveBeenCalled(); expect(revokeAccessTokenSpy).toHaveBeenCalled();
}); });
})); });
it('logs error when revokeaccesstoken throws an error', waitForAsync(() => { it('logs error when revokeaccesstoken throws an error', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ revocationEndpoint: 'revocationEndpoint' }); ['authWellKnownEndPoints', config],
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( () => ({ revocationEndpoint: 'revocationEndpoint' })
);
vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
spyOn(service, 'revokeRefreshToken').and.returnValue( vi.spyOn(service, 'revokeRefreshToken').mockReturnValue(
of({ any: 'thing' }) of({ any: 'thing' })
); );
const loggerSpy = spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
spyOn(service, 'revokeAccessToken').and.returnValue( vi.spyOn(service, 'revokeAccessToken').mockReturnValue(
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
@ -704,20 +717,22 @@ describe('Logout and Revoke Service', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
it('calls logoff in case of success', waitForAsync(() => { it('calls logoff in case of success', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
spyOn(service, 'revokeRefreshToken').and.returnValue( vi.spyOn(service, 'revokeRefreshToken').mockReturnValue(
of({ any: 'thing' }) of({ any: 'thing' })
); );
spyOn(service, 'revokeAccessToken').and.returnValue(of({ any: 'thing' })); vi.spyOn(service, 'revokeAccessToken').mockReturnValue(
const logoffSpy = spyOn(service, 'logoff').and.returnValue(of(null)); of({ any: 'thing' })
);
const logoffSpy = vi.spyOn(service, 'logoff').mockReturnValue(of(null));
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
@ -725,20 +740,22 @@ describe('Logout and Revoke Service', () => {
// Assert // Assert
expect(logoffSpy).toHaveBeenCalled(); expect(logoffSpy).toHaveBeenCalled();
}); });
})); });
it('calls logoff with urlhandler in case of success', waitForAsync(() => { it('calls logoff with urlhandler in case of success', async () => {
// Arrange // Arrange
const paramToken = 'damien'; const paramToken = 'damien';
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
paramToken paramToken
); );
spyOn(service, 'revokeRefreshToken').and.returnValue( vi.spyOn(service, 'revokeRefreshToken').mockReturnValue(
of({ any: 'thing' }) of({ any: 'thing' })
); );
spyOn(service, 'revokeAccessToken').and.returnValue(of({ any: 'thing' })); vi.spyOn(service, 'revokeAccessToken').mockReturnValue(
const logoffSpy = spyOn(service, 'logoff').and.returnValue(of(null)); of({ any: 'thing' })
);
const logoffSpy = vi.spyOn(service, 'logoff').mockReturnValue(of(null));
const urlHandler = (_url: string): void => undefined; const urlHandler = (_url: string): void => undefined;
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
@ -747,26 +764,29 @@ describe('Logout and Revoke Service', () => {
.logoffAndRevokeTokens(config, [config], { urlHandler }) .logoffAndRevokeTokens(config, [config], { urlHandler })
.subscribe(() => { .subscribe(() => {
// Assert // Assert
expect(logoffSpy).toHaveBeenCalledOnceWith(config, [config], { expect(logoffSpy).toHaveBeenCalledExactlyOnceWith(config, [config], {
urlHandler, urlHandler,
}); });
}); });
})); });
it('calls revokeAccessToken when storage does not hold a refreshtoken', waitForAsync(() => { it('calls revokeAccessToken when storage does not hold a refreshtoken', async () => {
// Arrange // Arrange
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ revocationEndpoint: 'revocationEndpoint' }); ['authWellKnownEndPoints', config],
() => ({ revocationEndpoint: 'revocationEndpoint' })
);
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue(''); vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
const revokeRefreshTokenSpy = spyOn(service, 'revokeRefreshToken'); ''
const revokeAccessTokenSpy = spyOn( );
service, const revokeRefreshTokenSpy = vi.spyOn(service, 'revokeRefreshToken');
'revokeAccessToken' const revokeAccessTokenSpy = vi
).and.returnValue(of({ any: 'thing' })); .spyOn(service, 'revokeAccessToken')
.mockReturnValue(of({ any: 'thing' }));
// Act // Act
service.logoffAndRevokeTokens(config, [config]).subscribe(() => { service.logoffAndRevokeTokens(config, [config]).subscribe(() => {
@ -774,19 +794,23 @@ describe('Logout and Revoke Service', () => {
expect(revokeRefreshTokenSpy).not.toHaveBeenCalled(); expect(revokeRefreshTokenSpy).not.toHaveBeenCalled();
expect(revokeAccessTokenSpy).toHaveBeenCalled(); expect(revokeAccessTokenSpy).toHaveBeenCalled();
}); });
})); });
it('logs error when revokeaccesstoken throws an error', waitForAsync(() => { it('logs error when revokeaccesstoken throws an error', async () => {
// Arrange // Arrange
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('authWellKnownEndPoints', config) vi.spyOn(storagePersistenceService, 'read'),
.and.returnValue({ revocationEndpoint: 'revocationEndpoint' }); ['authWellKnownEndPoints', config],
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue(''); () => ({ revocationEndpoint: 'revocationEndpoint' })
const loggerSpy = spyOn(loggerService, 'logError'); );
vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
''
);
const loggerSpy = vi.spyOn(loggerService, 'logError');
spyOn(service, 'revokeAccessToken').and.returnValue( vi.spyOn(service, 'revokeAccessToken').mockReturnValue(
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
@ -797,18 +821,18 @@ describe('Logout and Revoke Service', () => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, },
}); });
})); });
}); });
describe('logoffLocalMultiple', () => { describe('logoffLocalMultiple', () => {
it('calls logoffLocal for every config which is present', () => { it('calls logoffLocal for every config which is present', () => {
// Arrange // Arrange
const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }]; const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }];
const resetAuthorizationDataSpy = spyOn( const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService, resetAuthDataService,
'resetAuthorizationData' 'resetAuthorizationData'
); );
const checkSessionServiceSpy = spyOn(checkSessionService, 'stop'); const checkSessionServiceSpy = vi.spyOn(checkSessionService, 'stop');
// Act // Act
service.logoffLocalMultiple(allConfigs); service.logoffLocalMultiple(allConfigs);
@ -816,8 +840,8 @@ describe('Logout and Revoke Service', () => {
// Assert // Assert
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(2); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(2);
expect(checkSessionServiceSpy).toHaveBeenCalledTimes(2); expect(checkSessionServiceSpy).toHaveBeenCalledTimes(2);
expect(resetAuthorizationDataSpy.calls.allArgs()).toEqual([ expect(resetAuthorizationDataSpy).toBeCalledWith([
[allConfigs[0], allConfigs], [allConfigs[0]!, allConfigs],
[allConfigs[1], allConfigs], [allConfigs[1], allConfigs],
]); ]);
}); });

View File

@ -1,10 +1,10 @@
import { HttpHeaders } from '@ngify/http'; import { HttpHeaders } from '@ngify/http';
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, of, throwError } from 'rxjs'; import { type Observable, of, throwError } from 'rxjs';
import { catchError, concatMap, retry, switchMap } from 'rxjs/operators'; import { catchError, concatMap, retry, switchMap } from 'rxjs/operators';
import { DataService } from '../api/data.service'; import { DataService } from '../api/data.service';
import { LogoutAuthOptions } from '../auth-options'; import type { LogoutAuthOptions } from '../auth-options';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { CheckSessionService } from '../iframe/check-session.service'; import { CheckSessionService } from '../iframe/check-session.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,28 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable } from 'rxjs'; import { toSignal } from 'injection-js/rxjs-interop';
import { concatMap, map } from 'rxjs/operators'; import type { Observable } from 'rxjs';
import { AuthOptions, LogoutAuthOptions } from './auth-options'; import { concatMap, map, shareReplay } from 'rxjs/operators';
import { AuthenticatedResult } from './auth-state/auth-result'; import type { AuthOptions, LogoutAuthOptions } from './auth-options';
import type { AuthenticatedResult } from './auth-state/auth-result';
import { AuthStateService } from './auth-state/auth-state.service'; import { AuthStateService } from './auth-state/auth-state.service';
import { CheckAuthService } from './auth-state/check-auth.service'; import { CheckAuthService } from './auth-state/check-auth.service';
import { CallbackService } from './callback/callback.service'; import { CallbackService } from './callback/callback.service';
import { RefreshSessionService } from './callback/refresh-session.service'; import { RefreshSessionService } from './callback/refresh-session.service';
import { AuthWellKnownEndpoints } from './config/auth-well-known/auth-well-known-endpoints'; import type { AuthWellKnownEndpoints } from './config/auth-well-known/auth-well-known-endpoints';
import { AuthWellKnownService } from './config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from './config/auth-well-known/auth-well-known.service';
import { ConfigurationService } from './config/config.service'; import { ConfigurationService } from './config/config.service';
import { OpenIdConfiguration } from './config/openid-configuration'; import type { OpenIdConfiguration } from './config/openid-configuration';
import { AuthResult } from './flows/callback-context'; import type { AuthResult } from './flows/callback-context';
import { FlowsDataService } from './flows/flows-data.service'; import { FlowsDataService } from './flows/flows-data.service';
import { CheckSessionService } from './iframe/check-session.service'; import { CheckSessionService } from './iframe/check-session.service';
import { LoginResponse } from './login/login-response'; import type { LoginResponse } from './login/login-response';
import { LoginService } from './login/login.service'; import { LoginService } from './login/login.service';
import { PopupOptions } from './login/popup/popup-options'; import type { PopupOptions } from './login/popup/popup-options';
import { LogoffRevocationService } from './logoff-revoke/logoff-revocation.service'; import { LogoffRevocationService } from './logoff-revoke/logoff-revocation.service';
import { UserService } from './user-data/user.service'; import { UserService } from './user-data/user.service';
import { UserDataResult } from './user-data/userdata-result'; import type { UserDataResult } from './user-data/userdata-result';
import { TokenHelperService } from './utils/tokenHelper/token-helper.service'; import { TokenHelperService } from './utils/tokenHelper/token-helper.service';
import { UrlService } from './utils/url/url.service'; import { UrlService } from './utils/url/url.service';
import { toSignal } from 'injection-js/rxjs-interop';
@Injectable() @Injectable()
export class OidcSecurityService { export class OidcSecurityService {
@ -355,10 +355,17 @@ export class OidcSecurityService {
* @param configId The configId to perform the action in behalf of. If not passed, the first configs will be taken * @param configId The configId to perform the action in behalf of. If not passed, the first configs will be taken
* @param authOptions The custom options for the the authentication request. * @param authOptions The custom options for the the authentication request.
*/ */
authorize(configId?: string, authOptions?: AuthOptions): void { authorize(configId?: string, authOptions?: AuthOptions): Observable<void> {
this.configurationService const result$ = this.configurationService
.getOpenIDConfiguration(configId) .getOpenIDConfiguration(configId)
.subscribe((config) => this.loginService.login(config, authOptions)); .pipe(
map((config) => this.loginService.login(config, authOptions)),
shareReplay(1)
);
result$.subscribe();
return result$;
} }
/** /**
@ -471,24 +478,34 @@ export class OidcSecurityService {
* *
* @param configId The configId to perform the action in behalf of. If not passed, the first configs will be taken * @param configId The configId to perform the action in behalf of. If not passed, the first configs will be taken
*/ */
logoffLocal(configId?: string): void { logoffLocal(configId?: string): Observable<void> {
this.configurationService const result$ = this.configurationService
.getOpenIDConfigurations(configId) .getOpenIDConfigurations(configId)
.subscribe(({ allConfigs, currentConfig }) => .pipe(
this.logoffRevocationService.logoffLocal(currentConfig, allConfigs) map(({ allConfigs, currentConfig }) =>
this.logoffRevocationService.logoffLocal(currentConfig, allConfigs)
),
shareReplay(1)
); );
result$.subscribe();
return result$;
} }
/** /**
* Logs the user out of the application for all configs without logging them out of the server. * Logs the user out of the application for all configs without logging them out of the server.
* Use this method if you have _multiple_ configs enabled. * Use this method if you have _multiple_ configs enabled.
*/ */
logoffLocalMultiple(): void { logoffLocalMultiple(): Observable<void> {
this.configurationService const result$ = this.configurationService.getOpenIDConfigurations().pipe(
.getOpenIDConfigurations() map(({ allConfigs }) =>
.subscribe(({ allConfigs }) =>
this.logoffRevocationService.logoffLocalMultiple(allConfigs) this.logoffRevocationService.logoffLocalMultiple(allConfigs)
); ),
shareReplay(1)
);
result$.subscribe();
return result$;
} }
/** /**

View File

@ -1,7 +1,7 @@
import { APP_INITIALIZER } from '@angular/core'; import { TestBed, createSpyObj } from '@/testing';
import { TestBed, waitForAsync } from '@angular/core/testing'; import { mockProvider } from '@/testing/mock';
import { APP_INITIALIZER } from 'oidc-client-rx';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { mockProvider } from '../test/auto-mock';
import { PASSED_CONFIG } from './auth-config'; import { PASSED_CONFIG } from './auth-config';
import { ConfigurationService } from './config/config.service'; import { ConfigurationService } from './config/config.service';
import { import {
@ -14,14 +14,14 @@ import { provideAuth, withAppInitializerAuthCheck } from './provide-auth';
describe('provideAuth', () => { describe('provideAuth', () => {
describe('APP_CONFIG', () => { describe('APP_CONFIG', () => {
beforeEach(waitForAsync(() => { beforeEach(async () => {
TestBed.configureTestingModule({ await TestBed.configureTestingModule({
providers: [ providers: [
provideAuth({ config: { authority: 'something' } }), provideAuth({ config: { authority: 'something' } }),
mockProvider(ConfigurationService), mockProvider(ConfigurationService),
], ],
}).compileComponents(); }).compileComponents();
})); });
it('should provide config', () => { it('should provide config', () => {
const config = TestBed.inject(PASSED_CONFIG); const config = TestBed.inject(PASSED_CONFIG);
@ -37,8 +37,8 @@ describe('provideAuth', () => {
}); });
describe('StsConfigHttpLoader', () => { describe('StsConfigHttpLoader', () => {
beforeEach(waitForAsync(() => { beforeEach(async () => {
TestBed.configureTestingModule({ await TestBed.configureTestingModule({
providers: [ providers: [
provideAuth({ provideAuth({
loader: { loader: {
@ -49,7 +49,7 @@ describe('provideAuth', () => {
mockProvider(ConfigurationService), mockProvider(ConfigurationService),
], ],
}).compileComponents(); }).compileComponents();
})); });
it('should create StsConfigStaticLoader if config is passed', () => { it('should create StsConfigStaticLoader if config is passed', () => {
const configLoader = TestBed.inject(StsConfigLoader); const configLoader = TestBed.inject(StsConfigLoader);
@ -59,14 +59,14 @@ describe('provideAuth', () => {
}); });
describe('features', () => { describe('features', () => {
let oidcSecurityServiceMock: jasmine.SpyObj<OidcSecurityService>; let oidcSecurityServiceMock: OidcSecurityService;
beforeEach(waitForAsync(() => { beforeEach(async () => {
oidcSecurityServiceMock = jasmine.createSpyObj<OidcSecurityService>( oidcSecurityServiceMock = createSpyObj<OidcSecurityService>(
'OidcSecurityService', 'OidcSecurityService',
['checkAuthMultiple'] ['checkAuthMultiple']
); );
TestBed.configureTestingModule({ await TestBed.configureTestingModule({
providers: [ providers: [
provideAuth( provideAuth(
{ config: { authority: 'something' } }, { config: { authority: 'something' } },
@ -79,14 +79,15 @@ describe('provideAuth', () => {
}, },
], ],
}).compileComponents(); }).compileComponents();
})); });
it('should provide APP_INITIALIZER config', () => { it('should provide APP_INITIALIZER config', () => {
const config = TestBed.inject(APP_INITIALIZER); const config = TestBed.inject(APP_INITIALIZER);
expect(config.length) expect(
.withContext('Expected an APP_INITIALIZER to be registered') config.length,
.toBe(1); 'Expected an APP_INITIALIZER to be registered'
).toBe(1);
expect(oidcSecurityServiceMock.checkAuthMultiple).toHaveBeenCalledTimes( expect(oidcSecurityServiceMock.checkAuthMultiple).toHaveBeenCalledTimes(
1 1
); );

View File

@ -1,15 +1,11 @@
import type { Provider } from 'injection-js';
import { import {
APP_INITIALIZER,
EnvironmentProviders,
makeEnvironmentProviders,
Provider,
} from 'injection-js';
import {
createStaticLoader,
PASSED_CONFIG, PASSED_CONFIG,
PassedInitialConfig, type PassedInitialConfig,
createStaticLoader,
} from './auth-config'; } from './auth-config';
import { StsConfigLoader } from './config/loader/config-loader'; import { StsConfigLoader } from './config/loader/config-loader';
import { APP_INITIALIZER } from './injection';
import { AbstractLoggerService } from './logging/abstract-logger.service'; import { AbstractLoggerService } from './logging/abstract-logger.service';
import { ConsoleLoggerService } from './logging/console-logger.service'; import { ConsoleLoggerService } from './logging/console-logger.service';
import { OidcSecurityService } from './oidc.security.service'; import { OidcSecurityService } from './oidc.security.service';
@ -26,14 +22,14 @@ export interface AuthFeature {
export function provideAuth( export function provideAuth(
passedConfig: PassedInitialConfig, passedConfig: PassedInitialConfig,
...features: AuthFeature[] ...features: AuthFeature[]
): EnvironmentProviders { ): Provider[] {
const providers = _provideAuth(passedConfig); const providers = _provideAuth(passedConfig);
for (const feature of features) { for (const feature of features) {
providers.push(...feature.ɵproviders); providers.push(...feature.ɵproviders);
} }
return makeEnvironmentProviders(providers); return providers;
} }
export function _provideAuth(passedConfig: PassedInitialConfig): Provider[] { export function _provideAuth(passedConfig: PassedInitialConfig): Provider[] {

View File

@ -1,17 +1,18 @@
// biome-ignore lint/nursery/noEnum: <explanation>
export enum EventTypes { export enum EventTypes {
/** /**
* This only works in the AppModule Constructor * This only works in the AppModule Constructor
*/ */
ConfigLoaded, ConfigLoaded = 0,
CheckingAuth, CheckingAuth = 1,
CheckingAuthFinished, CheckingAuthFinished = 2,
CheckingAuthFinishedWithError, CheckingAuthFinishedWithError = 3,
ConfigLoadingFailed, ConfigLoadingFailed = 4,
CheckSessionReceived, CheckSessionReceived = 5,
UserDataChanged, UserDataChanged = 6,
NewAuthenticationResult, NewAuthenticationResult = 7,
TokenExpired, TokenExpired = 8,
IdTokenExpired, IdTokenExpired = 9,
SilentRenewStarted, SilentRenewStarted = 10,
SilentRenewFailed, SilentRenewFailed = 11,
} }

View File

@ -1,5 +1,6 @@
import { TestBed, waitForAsync } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { vi } from 'vitest';
import { EventTypes } from './event-types'; import { EventTypes } from './event-types';
import { PublicEventsService } from './public-events.service'; import { PublicEventsService } from './public-events.service';
@ -20,7 +21,7 @@ describe('Events Service', () => {
expect(eventsService).toBeTruthy(); expect(eventsService).toBeTruthy();
}); });
it('registering to single event with one event emit works', waitForAsync(() => { it('registering to single event with one event emit works', async () => {
eventsService.registerForEvents().subscribe((firedEvent) => { eventsService.registerForEvents().subscribe((firedEvent) => {
expect(firedEvent).toBeTruthy(); expect(firedEvent).toBeTruthy();
expect(firedEvent).toEqual({ expect(firedEvent).toEqual({
@ -29,9 +30,9 @@ describe('Events Service', () => {
}); });
}); });
eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' }); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' });
})); });
it('registering to single event with multiple same event emit works', waitForAsync(() => { it('registering to single event with multiple same event emit works', async () => {
const spy = jasmine.createSpy('spy'); const spy = jasmine.createSpy('spy');
eventsService.registerForEvents().subscribe((firedEvent) => { eventsService.registerForEvents().subscribe((firedEvent) => {
@ -50,9 +51,9 @@ describe('Events Service', () => {
type: EventTypes.ConfigLoaded, type: EventTypes.ConfigLoaded,
value: { myKey: 'myValue2' }, value: { myKey: 'myValue2' },
}); });
})); });
it('registering to single event with multiple emit works', waitForAsync(() => { it('registering to single event with multiple emit works', async () => {
eventsService eventsService
.registerForEvents() .registerForEvents()
.pipe(filter((x) => x.type === EventTypes.ConfigLoaded)) .pipe(filter((x) => x.type === EventTypes.ConfigLoaded))
@ -65,5 +66,5 @@ describe('Events Service', () => {
}); });
eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' }); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' });
eventsService.fireEvent(EventTypes.NewAuthenticationResult, true); eventsService.fireEvent(EventTypes.NewAuthenticationResult, true);
})); });
}); });

27
src/router/index.ts Normal file
View File

@ -0,0 +1,27 @@
export type RouteData = {
[key: string | symbol]: any;
};
export interface ActivatedRouteSnapshot {
data: RouteData;
}
export interface RouterStateSnapshot {
url: string;
}
export abstract class AbstractRouter {
navigateByUrl(url: string): void {
// TODO
// Implementation of navigating to a URL
}
getCurrentNavigation(): any {
// TODO
// Implementation of getting the current navigation
return null;
}
// TODO
parseUrl(url: string) {}
}

View File

@ -1,6 +1,7 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { mockClass, mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { mockClass, mockProvider } from '../testing/mock';
import { AbstractSecurityStorage } from './abstract-security-storage'; import { AbstractSecurityStorage } from './abstract-security-storage';
import { BrowserStorageService } from './browser-storage.service'; import { BrowserStorageService } from './browser-storage.service';
import { DefaultSessionStorageService } from './default-sessionstorage.service'; import { DefaultSessionStorageService } from './default-sessionstorage.service';
@ -34,7 +35,7 @@ describe('BrowserStorageService', () => {
it('returns null if there is no storage', () => { it('returns null if there is no storage', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(false); vi.spyOn(service as any, 'hasStorage').mockReturnValue(false);
expect(service.read('anything', config)).toBeNull(); expect(service.read('anything', config)).toBeNull();
}); });
@ -42,7 +43,7 @@ describe('BrowserStorageService', () => {
it('returns null if getItem returns null', () => { it('returns null if getItem returns null', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const result = service.read('anything', config); const result = service.read('anything', config);
@ -52,10 +53,10 @@ describe('BrowserStorageService', () => {
it('returns the item if getItem returns an item', () => { it('returns the item if getItem returns an item', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const returnValue = `{ "name":"John", "age":30, "city":"New York"}`; const returnValue = `{ "name":"John", "age":30, "city":"New York"}`;
spyOn(abstractSecurityStorage, 'read').and.returnValue(returnValue); vi.spyOn(abstractSecurityStorage, 'read').mockReturnValue(returnValue);
const result = service.read('anything', config); const result = service.read('anything', config);
expect(result).toEqual(JSON.parse(returnValue)); expect(result).toEqual(JSON.parse(returnValue));
@ -66,24 +67,21 @@ describe('BrowserStorageService', () => {
it('returns false if there is no storage', () => { it('returns false if there is no storage', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(false); vi.spyOn(service as any, 'hasStorage').mockReturnValue(false);
expect(service.write('anyvalue', config)).toBeFalse(); expect(service.write('anyvalue', config)).toBeFalsy();
}); });
it('writes object correctly with configId', () => { it('writes object correctly with configId', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const writeSpy = spyOn( const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')();
abstractSecurityStorage,
'write'
).and.callThrough();
const result = service.write({ anyKey: 'anyvalue' }, config); const result = service.write({ anyKey: 'anyvalue' }, config);
expect(result).toBe(true); expect(result).toBe(true);
expect(writeSpy).toHaveBeenCalledOnceWith( expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
'configId1', 'configId1',
JSON.stringify({ anyKey: 'anyvalue' }) JSON.stringify({ anyKey: 'anyvalue' })
); );
@ -92,18 +90,15 @@ describe('BrowserStorageService', () => {
it('writes null if item is falsy', () => { it('writes null if item is falsy', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const writeSpy = spyOn( const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')();
abstractSecurityStorage,
'write'
).and.callThrough();
const somethingFalsy = ''; const somethingFalsy = '';
const result = service.write(somethingFalsy, config); const result = service.write(somethingFalsy, config);
expect(result).toBe(true); expect(result).toBe(true);
expect(writeSpy).toHaveBeenCalledOnceWith( expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
'configId1', 'configId1',
JSON.stringify(null) JSON.stringify(null)
); );
@ -114,41 +109,35 @@ describe('BrowserStorageService', () => {
it('returns false if there is no storage', () => { it('returns false if there is no storage', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(false); vi.spyOn(service as any, 'hasStorage').mockReturnValue(false);
expect(service.remove('anything', config)).toBeFalse(); expect(service.remove('anything', config)).toBeFalsy();
}); });
it('returns true if removeItem is called', () => { it('returns true if removeItem is called', () => {
spyOn(service as any, 'hasStorage').and.returnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const setItemSpy = spyOn( const setItemSpy = vi.spyOn(abstractSecurityStorage, 'remove')();
abstractSecurityStorage,
'remove'
).and.callThrough();
const result = service.remove('anyKey', config); const result = service.remove('anyKey', config);
expect(result).toBe(true); expect(result).toBe(true);
expect(setItemSpy).toHaveBeenCalledOnceWith('anyKey'); expect(setItemSpy).toHaveBeenCalledExactlyOnceWith('anyKey');
}); });
}); });
describe('clear', () => { describe('clear', () => {
it('returns false if there is no storage', () => { it('returns false if there is no storage', () => {
spyOn(service as any, 'hasStorage').and.returnValue(false); vi.spyOn(service as any, 'hasStorage').mockReturnValue(false);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
expect(service.clear(config)).toBeFalse(); expect(service.clear(config)).toBeFalsy();
}); });
it('returns true if clear is called', () => { it('returns true if clear is called', () => {
spyOn(service as any, 'hasStorage').and.returnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const setItemSpy = spyOn( const setItemSpy = vi.spyOn(abstractSecurityStorage, 'clear')();
abstractSecurityStorage,
'clear'
).and.callThrough();
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const result = service.clear(config); const result = service.clear(config);
@ -161,7 +150,7 @@ describe('BrowserStorageService', () => {
describe('hasStorage', () => { describe('hasStorage', () => {
it('returns false if there is no storage', () => { it('returns false if there is no storage', () => {
(Storage as any) = undefined; (Storage as any) = undefined;
expect((service as any).hasStorage()).toBeFalse(); expect((service as any).hasStorage()).toBeFalsy();
Storage = Storage; Storage = Storage;
}); });
}); });

View File

@ -1,4 +1,5 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { DefaultLocalStorageService } from './default-localstorage.service'; import { DefaultLocalStorageService } from './default-localstorage.service';
describe('DefaultLocalStorageService', () => { describe('DefaultLocalStorageService', () => {
@ -20,37 +21,37 @@ describe('DefaultLocalStorageService', () => {
describe('read', () => { describe('read', () => {
it('should call localstorage.getItem', () => { it('should call localstorage.getItem', () => {
const spy = spyOn(localStorage, 'getItem'); const spy = vi.spyOn(localStorage, 'getItem');
service.read('henlo'); service.read('henlo');
expect(spy).toHaveBeenCalledOnceWith('henlo'); expect(spy).toHaveBeenCalledExactlyOnceWith('henlo');
}); });
}); });
describe('write', () => { describe('write', () => {
it('should call localstorage.setItem', () => { it('should call localstorage.setItem', () => {
const spy = spyOn(localStorage, 'setItem'); const spy = vi.spyOn(localStorage, 'setItem');
service.write('henlo', 'furiend'); service.write('henlo', 'furiend');
expect(spy).toHaveBeenCalledOnceWith('henlo', 'furiend'); expect(spy).toHaveBeenCalledExactlyOnceWith('henlo', 'furiend');
}); });
}); });
describe('remove', () => { describe('remove', () => {
it('should call localstorage.removeItem', () => { it('should call localstorage.removeItem', () => {
const spy = spyOn(localStorage, 'removeItem'); const spy = vi.spyOn(localStorage, 'removeItem');
service.remove('henlo'); service.remove('henlo');
expect(spy).toHaveBeenCalledOnceWith('henlo'); expect(spy).toHaveBeenCalledExactlyOnceWith('henlo');
}); });
}); });
describe('clear', () => { describe('clear', () => {
it('should call localstorage.clear', () => { it('should call localstorage.clear', () => {
const spy = spyOn(localStorage, 'clear'); const spy = vi.spyOn(localStorage, 'clear');
service.clear(); service.clear();

View File

@ -1,4 +1,5 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { DefaultSessionStorageService } from './default-sessionstorage.service'; import { DefaultSessionStorageService } from './default-sessionstorage.service';
describe('DefaultSessionStorageService', () => { describe('DefaultSessionStorageService', () => {
@ -20,37 +21,37 @@ describe('DefaultSessionStorageService', () => {
describe('read', () => { describe('read', () => {
it('should call sessionstorage.getItem', () => { it('should call sessionstorage.getItem', () => {
const spy = spyOn(sessionStorage, 'getItem'); const spy = vi.spyOn(sessionStorage, 'getItem');
service.read('henlo'); service.read('henlo');
expect(spy).toHaveBeenCalledOnceWith('henlo'); expect(spy).toHaveBeenCalledExactlyOnceWith('henlo');
}); });
}); });
describe('write', () => { describe('write', () => {
it('should call sessionstorage.setItem', () => { it('should call sessionstorage.setItem', () => {
const spy = spyOn(sessionStorage, 'setItem'); const spy = vi.spyOn(sessionStorage, 'setItem');
service.write('henlo', 'furiend'); service.write('henlo', 'furiend');
expect(spy).toHaveBeenCalledOnceWith('henlo', 'furiend'); expect(spy).toHaveBeenCalledExactlyOnceWith('henlo', 'furiend');
}); });
}); });
describe('remove', () => { describe('remove', () => {
it('should call sessionstorage.removeItem', () => { it('should call sessionstorage.removeItem', () => {
const spy = spyOn(sessionStorage, 'removeItem'); const spy = vi.spyOn(sessionStorage, 'removeItem');
service.remove('henlo'); service.remove('henlo');
expect(spy).toHaveBeenCalledOnceWith('henlo'); expect(spy).toHaveBeenCalledExactlyOnceWith('henlo');
}); });
}); });
describe('clear', () => { describe('clear', () => {
it('should call sessionstorage.clear', () => { it('should call sessionstorage.clear', () => {
const spy = spyOn(sessionStorage, 'clear'); const spy = vi.spyOn(sessionStorage, 'clear');
service.clear(); service.clear();

View File

@ -1,5 +1,6 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@/testing';
import { mockProvider } from '../../test/auto-mock'; import { vi } from 'vitest';
import { mockProvider } from '../testing/mock';
import { BrowserStorageService } from './browser-storage.service'; import { BrowserStorageService } from './browser-storage.service';
import { StoragePersistenceService } from './storage-persistence.service'; import { StoragePersistenceService } from './storage-persistence.service';
@ -25,16 +26,16 @@ describe('Storage Persistence Service', () => {
describe('read', () => { describe('read', () => {
it('reads from oidcSecurityStorage with configId', () => { it('reads from oidcSecurityStorage with configId', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const spy = spyOn(securityStorage, 'read'); const spy = vi.spyOn(securityStorage, 'read');
service.read('authNonce', config); service.read('authNonce', config);
expect(spy).toHaveBeenCalledOnceWith('authNonce', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authNonce', config);
}); });
it('returns undefined (not throws exception) if key to read is not present on config', () => { it('returns undefined (not throws exception) if key to read is not present on config', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
spyOn(securityStorage, 'read').and.returnValue({ some: 'thing' }); vi.spyOn(securityStorage, 'read').mockReturnValue({ some: 'thing' });
const result = service.read('authNonce', config); const result = service.read('authNonce', config);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
@ -44,13 +45,13 @@ describe('Storage Persistence Service', () => {
describe('write', () => { describe('write', () => {
it('writes to oidcSecurityStorage with correct key and correct config', () => { it('writes to oidcSecurityStorage with correct key and correct config', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const readSpy = spyOn(securityStorage, 'read'); const readSpy = vi.spyOn(securityStorage, 'read');
const writeSpy = spyOn(securityStorage, 'write'); const writeSpy = vi.spyOn(securityStorage, 'write');
service.write('authNonce', 'anyValue', config); service.write('authNonce', 'anyValue', config);
expect(readSpy).toHaveBeenCalledOnceWith('authNonce', config); expect(readSpy).toHaveBeenCalledExactlyOnceWith('authNonce', config);
expect(writeSpy).toHaveBeenCalledOnceWith( expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
{ authNonce: 'anyValue' }, { authNonce: 'anyValue' },
config config
); );
@ -60,32 +61,32 @@ describe('Storage Persistence Service', () => {
describe('remove', () => { describe('remove', () => {
it('should remove key from config', () => { it('should remove key from config', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const readSpy = spyOn(securityStorage, 'read').and.returnValue({ const readSpy = vi.spyOn(securityStorage, 'read').mockReturnValue({
authNonce: 'anyValue', authNonce: 'anyValue',
}); });
const writeSpy = spyOn(securityStorage, 'write'); const writeSpy = vi.spyOn(securityStorage, 'write');
service.remove('authNonce', config); service.remove('authNonce', config);
expect(readSpy).toHaveBeenCalledOnceWith('authNonce', config); expect(readSpy).toHaveBeenCalledExactlyOnceWith('authNonce', config);
expect(writeSpy).toHaveBeenCalledOnceWith({}, config); expect(writeSpy).toHaveBeenCalledExactlyOnceWith({}, config);
}); });
it('does not crash when read with configId returns null', () => { it('does not crash when read with configId returns null', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const readSpy = spyOn(securityStorage, 'read').and.returnValue(null); const readSpy = vi.spyOn(securityStorage, 'read').mockReturnValue(null);
const writeSpy = spyOn(securityStorage, 'write'); const writeSpy = vi.spyOn(securityStorage, 'write');
service.remove('authNonce', config); service.remove('authNonce', config);
expect(readSpy).toHaveBeenCalledOnceWith('authNonce', config); expect(readSpy).toHaveBeenCalledExactlyOnceWith('authNonce', config);
expect(writeSpy).toHaveBeenCalledOnceWith({}, config); expect(writeSpy).toHaveBeenCalledExactlyOnceWith({}, config);
}); });
}); });
describe('clear', () => { describe('clear', () => {
it('should call oidcSecurityStorage.clear()', () => { it('should call oidcSecurityStorage.clear()', () => {
const clearSpy = spyOn(securityStorage, 'clear'); const clearSpy = vi.spyOn(securityStorage, 'clear');
service.clear({}); service.clear({});
@ -96,49 +97,58 @@ describe('Storage Persistence Service', () => {
describe('resetStorageFlowData', () => { describe('resetStorageFlowData', () => {
it('resets the correct values', () => { it('resets the correct values', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const spy = spyOn(service, 'remove'); const spy = vi.spyOn(service, 'remove');
service.resetStorageFlowData(config); service.resetStorageFlowData(config);
expect(spy).toHaveBeenCalledTimes(10); expect(spy).toHaveBeenCalledTimes(10);
expect(spy.calls.argsFor(0)).toEqual(['session_state', config]); expect(vi.mocked(spy).mock.calls[0]).toEqual(['session_state', config]);
expect(spy.calls.argsFor(1)).toEqual([ expect(vi.mocked(spy).mock.calls[1]).toEqual([
'storageSilentRenewRunning', 'storageSilentRenewRunning',
config, config,
]); ]);
expect(spy.calls.argsFor(2)).toEqual([ expect(vi.mocked(spy).mock.calls[2]).toEqual([
'storageCodeFlowInProgress', 'storageCodeFlowInProgress',
config, config,
]); ]);
expect(spy.calls.argsFor(3)).toEqual(['codeVerifier', config]); expect(vi.mocked(spy).mock.calls[3]).toEqual(['codeVerifier', config]);
expect(spy.calls.argsFor(4)).toEqual(['userData', config]); expect(vi.mocked(spy).mock.calls[4]).toEqual(['userData', config]);
expect(spy.calls.argsFor(5)).toEqual([ expect(vi.mocked(spy).mock.calls[5]).toEqual([
'storageCustomParamsAuthRequest', 'storageCustomParamsAuthRequest',
config, config,
]); ]);
expect(spy.calls.argsFor(6)).toEqual(['access_token_expires_at', config]); expect(vi.mocked(spy).mock.calls[6]).toEqual([
expect(spy.calls.argsFor(7)).toEqual([ 'access_token_expires_at',
config,
]);
expect(vi.mocked(spy).mock.calls[7]).toEqual([
'storageCustomParamsRefresh', 'storageCustomParamsRefresh',
config, config,
]); ]);
expect(spy.calls.argsFor(8)).toEqual([ expect(vi.mocked(spy).mock.calls[8]).toEqual([
'storageCustomParamsEndSession', 'storageCustomParamsEndSession',
config, config,
]); ]);
expect(spy.calls.argsFor(9)).toEqual(['reusable_refresh_token', config]); expect(vi.mocked(spy).mock.calls[9]).toEqual([
'reusable_refresh_token',
config,
]);
}); });
}); });
describe('resetAuthStateInStorage', () => { describe('resetAuthStateInStorage', () => {
it('resets the correct values', () => { it('resets the correct values', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const spy = spyOn(service, 'remove'); const spy = vi.spyOn(service, 'remove');
service.resetAuthStateInStorage(config); service.resetAuthStateInStorage(config);
expect(spy.calls.argsFor(0)).toEqual(['authzData', config]); expect(vi.mocked(spy).mock.calls[0]).toEqual(['authzData', config]);
expect(spy.calls.argsFor(1)).toEqual(['reusable_refresh_token', config]); expect(vi.mocked(spy).mock.calls[1]).toEqual([
expect(spy.calls.argsFor(2)).toEqual(['authnResult', config]); 'reusable_refresh_token',
config,
]);
expect(vi.mocked(spy).mock.calls[2]).toEqual(['authnResult', config]);
}); });
}); });
@ -146,41 +156,45 @@ describe('Storage Persistence Service', () => {
it('get calls oidcSecurityStorage.read with correct key and returns the value', () => { it('get calls oidcSecurityStorage.read with correct key and returns the value', () => {
const returnValue = { authzData: 'someValue' }; const returnValue = { authzData: 'someValue' };
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); const spy = vi
.spyOn(securityStorage, 'read')
.mockReturnValue(returnValue);
const result = service.getAccessToken(config); const result = service.getAccessToken(config);
expect(result).toBe('someValue'); expect(result).toBe('someValue');
expect(spy).toHaveBeenCalledOnceWith('authzData', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authzData', config);
}); });
it('get calls oidcSecurityStorage.read with correct key and returns null', () => { it('get calls oidcSecurityStorage.read with correct key and returns null', () => {
const spy = spyOn(securityStorage, 'read').and.returnValue(null); const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const result = service.getAccessToken(config); const result = service.getAccessToken(config);
expect(result).toBeFalsy(); expect(result).toBeFalsy();
expect(spy).toHaveBeenCalledOnceWith('authzData', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authzData', config);
}); });
}); });
describe('getIdToken', () => { describe('getIdToken', () => {
it('get calls oidcSecurityStorage.read with correct key and returns the value', () => { it('get calls oidcSecurityStorage.read with correct key and returns the value', () => {
const returnValue = { authnResult: { id_token: 'someValue' } }; const returnValue = { authnResult: { id_token: 'someValue' } };
const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); const spy = vi
.spyOn(securityStorage, 'read')
.mockReturnValue(returnValue);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const result = service.getIdToken(config); const result = service.getIdToken(config);
expect(result).toBe('someValue'); expect(result).toBe('someValue');
expect(spy).toHaveBeenCalledOnceWith('authnResult', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config);
}); });
it('get calls oidcSecurityStorage.read with correct key and returns null', () => { it('get calls oidcSecurityStorage.read with correct key and returns null', () => {
const spy = spyOn(securityStorage, 'read').and.returnValue(null); const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const result = service.getIdToken(config); const result = service.getIdToken(config);
expect(result).toBeFalsy(); expect(result).toBeFalsy();
expect(spy).toHaveBeenCalledOnceWith('authnResult', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config);
}); });
}); });
@ -188,32 +202,36 @@ describe('Storage Persistence Service', () => {
it('get calls oidcSecurityStorage.read with correct key and returns the value', () => { it('get calls oidcSecurityStorage.read with correct key and returns the value', () => {
const returnValue = { authnResult: { id_token: 'someValue' } }; const returnValue = { authnResult: { id_token: 'someValue' } };
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); const spy = vi
.spyOn(securityStorage, 'read')
.mockReturnValue(returnValue);
const result = service.getAuthenticationResult(config); const result = service.getAuthenticationResult(config);
expect(result.id_token).toBe('someValue'); expect(result.id_token).toBe('someValue');
expect(spy).toHaveBeenCalledOnceWith('authnResult', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config);
}); });
it('get calls oidcSecurityStorage.read with correct key and returns null', () => { it('get calls oidcSecurityStorage.read with correct key and returns null', () => {
const spy = spyOn(securityStorage, 'read').and.returnValue(null); const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const result = service.getAuthenticationResult(config); const result = service.getAuthenticationResult(config);
expect(result).toBeFalsy(); expect(result).toBeFalsy();
expect(spy).toHaveBeenCalledOnceWith('authnResult', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config);
}); });
}); });
describe('getRefreshToken', () => { describe('getRefreshToken', () => {
it('get calls oidcSecurityStorage.read with correct key and returns the value (refresh token with mandatory rotation - default)', () => { it('get calls oidcSecurityStorage.read with correct key and returns the value (refresh token with mandatory rotation - default)', () => {
const returnValue = { authnResult: { refresh_token: 'someValue' } }; const returnValue = { authnResult: { refresh_token: 'someValue' } };
const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); const spy = vi
.spyOn(securityStorage, 'read')
.mockReturnValue(returnValue);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const result = service.getRefreshToken(config); const result = service.getRefreshToken(config);
expect(result).toBe('someValue'); expect(result).toBe('someValue');
expect(spy).toHaveBeenCalledOnceWith('authnResult', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config);
}); });
it('get calls oidcSecurityStorage.read with correct key and returns the value (refresh token without rotation)', () => { it('get calls oidcSecurityStorage.read with correct key and returns the value (refresh token without rotation)', () => {
@ -222,12 +240,12 @@ describe('Storage Persistence Service', () => {
configId: 'configId1', configId: 'configId1',
allowUnsafeReuseRefreshToken: true, allowUnsafeReuseRefreshToken: true,
}; };
const spy = spyOn(securityStorage, 'read'); const spy = vi.spyOn(securityStorage, 'read');
spy spy
.withArgs('reusable_refresh_token', config) .withArgs('reusable_refresh_token', config)
.and.returnValue(returnValue); .mockReturnValue(returnValue);
spy.withArgs('authnResult', config).and.returnValue(undefined); spy.withArgs('authnResult', config).mockReturnValue(undefined);
const result = service.getRefreshToken(config); const result = service.getRefreshToken(config);
expect(result).toBe(returnValue.reusable_refresh_token); expect(result).toBe(returnValue.reusable_refresh_token);
@ -238,21 +256,23 @@ describe('Storage Persistence Service', () => {
it('get calls oidcSecurityStorage.read with correct key and returns null', () => { it('get calls oidcSecurityStorage.read with correct key and returns null', () => {
const returnValue = { authnResult: { NO_refresh_token: 'someValue' } }; const returnValue = { authnResult: { NO_refresh_token: 'someValue' } };
const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); const spy = vi
.spyOn(securityStorage, 'read')
.mockReturnValue(returnValue);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const result = service.getRefreshToken(config); const result = service.getRefreshToken(config);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(spy).toHaveBeenCalledOnceWith('authnResult', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config);
}); });
it('get calls oidcSecurityStorage.read with correct key and returns null', () => { it('get calls oidcSecurityStorage.read with correct key and returns null', () => {
const spy = spyOn(securityStorage, 'read').and.returnValue(null); const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const result = service.getRefreshToken(config); const result = service.getRefreshToken(config);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(spy).toHaveBeenCalledOnceWith('authnResult', config); expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config);
}); });
}); });
}); });

View File

@ -1,6 +1,6 @@
import { inject, Injectable } from 'injection-js'; import { inject } from 'injection-js';
import { AuthResult } from '../flows/callback-context'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { AuthResult } from '../flows/callback-context';
import { BrowserStorageService } from './browser-storage.service'; import { BrowserStorageService } from './browser-storage.service';
export type StorageKeys = export type StorageKeys =
@ -24,7 +24,6 @@ export type StorageKeys =
| 'jwtKeys' | 'jwtKeys'
| 'popupauth'; | 'popupauth';
export class StoragePersistenceService { export class StoragePersistenceService {
private readonly browserStorageService = inject(BrowserStorageService); private readonly browserStorageService = inject(BrowserStorageService);

Some files were not shown because too many files have changed in this diff Show More