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
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)
<p align="center">
@ -155,7 +157,7 @@ const token = this.oidcSecurityService.getAccessToken().subscribe(...);
And then you can use it in the HttpHeaders
```ts
import { HttpHeaders } from '@angular/common/http';
import { HttpHeaders } from '@ngify/http';
const token = this.oidcSecurityServices.getAccessToken().subscribe((token) => {
const httpOptions = {

View File

@ -18,8 +18,14 @@
}
},
"files": {
"ignore": [
".vscode/*.json"
]
}
"ignore": [".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",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"files": ["dist"],
"scripts": {
"build": "rslib build",
"dev": "rslib build --watch",
@ -39,19 +37,24 @@
"dependencies": {
"@ngify/http": "^2.0.4",
"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": {
"@evilmartians/lefthook": "^1.0.3",
"@playwright/test": "^1.49.1",
"@rslib/core": "^0.3.1",
"@types/jasmine": "^4.0.0",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.10.1",
"@vitest/coverage-v8": "^3.0.1",
"lodash-es": "^4.17.21",
"rfc4648": "^1.5.0",
"typescript": "^5.7.3",
"ultracite": "^4.1.15",
"vitest": "^3.0.1"
"vitest": "^3.0.1",
"rxjs": "^7.4.0"
},
"keywords": [
"rxjs",

41
pnpm-lock.yaml generated
View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,15 @@
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 { OpenIdConfiguration } from '../config/openid-configuration';
import { AuthResult } from '../flows/callback-context';
import type { OpenIdConfiguration } from '../config/openid-configuration';
import type { AuthResult } from '../flows/callback-context';
import { LoggerService } from '../logging/logger.service';
import { EventTypes } from '../public-events/event-types';
import { PublicEventsService } from '../public-events/public-events.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { TokenValidationService } from '../validation/token-validation.service';
import { AuthenticatedResult } from './auth-result';
import { AuthStateResult } from './auth-state';
import type { AuthenticatedResult } from './auth-result';
import type { AuthStateResult } from './auth-state';
const DEFAULT_AUTHRESULT = {
isAuthenticated: false,
@ -293,7 +293,7 @@ export class AuthStateService {
};
}
return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs);
return this.checkallConfigsIfTheyAreAuthenticated(allConfigs);
}
private composeUnAuthenticatedResult(
@ -310,10 +310,10 @@ export class AuthStateService {
};
}
return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs);
return this.checkallConfigsIfTheyAreAuthenticated(allConfigs);
}
private checkAllConfigsIfTheyAreAuthenticated(
private checkallConfigsIfTheyAreAuthenticated(
allConfigs: OpenIdConfiguration[]
): AuthenticatedResult {
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 { forkJoin, Observable, of, throwError } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import { type Observable, forkJoin, of, throwError } from 'rxjs';
import { catchError, map, switchMap, tap } from 'rxjs/operators';
import { AutoLoginService } from '../auto-login/auto-login.service';
import { CallbackService } from '../callback/callback.service';
import { PeriodicallyTokenCheckService } from '../callback/periodically-token-check.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 { SilentRenewService } from '../iframe/silent-renew.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 { EventTypes } from '../public-events/event-types';
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 { mockProvider } from '../test/auto-mock';
import { vi } from 'vitest';
import { PASSED_CONFIG } from './auth-config';
import { AuthModule } from './auth.module';
import { ConfigurationService } from './config/config.service';
@ -9,19 +9,20 @@ import {
StsConfigLoader,
StsConfigStaticLoader,
} from './config/loader/config-loader';
import { mockProvider } from './testing/mock';
describe('AuthModule', () => {
describe('APP_CONFIG', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AuthModule.forRoot({ config: { authority: 'something' } })],
providers: [mockProvider(ConfigurationService)],
}).compileComponents();
}));
});
it('should create', () => {
expect(AuthModule).toBeDefined();
expect(AuthModule.forRoot({})).toBeDefined();
expect(new AuthModule()).toBeDefined();
});
it('should provide config', () => {
@ -38,8 +39,8 @@ describe('AuthModule', () => {
});
describe('StsConfigHttpLoader', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
AuthModule.forRoot({
loader: {
@ -50,7 +51,7 @@ describe('AuthModule', () => {
],
providers: [mockProvider(ConfigurationService)],
}).compileComponents();
}));
});
it('should create StsConfigStaticLoader if config is passed', () => {
const configLoader = TestBed.inject(StsConfigLoader);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import { TestBed, waitForAsync } from '@angular/core/testing';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { TestBed, mockRouterProvider } from '@/testing';
import { AbstractRouter } from 'oidc-client-rx';
import { of, throwError } from 'rxjs';
import { mockProvider } from '../../test/auto-mock';
import { CallbackContext } from '../flows/callback-context';
import { vi } from 'vitest';
import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service';
import { FlowsService } from '../flows/flows.service';
import { mockProvider } from '../testing/mock';
import { ImplicitFlowCallbackService } from './implicit-flow-callback.service';
import { IntervalService } from './interval.service';
@ -14,25 +14,23 @@ describe('ImplicitFlowCallbackService ', () => {
let intervalService: IntervalService;
let flowsService: FlowsService;
let flowsDataService: FlowsDataService;
let router: Router;
let router: AbstractRouter;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
imports: [],
providers: [
mockRouterProvider(),
mockProvider(FlowsService),
mockProvider(FlowsDataService),
mockProvider(IntervalService),
],
});
});
beforeEach(() => {
implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService);
intervalService = TestBed.inject(IntervalService);
flowsDataService = TestBed.inject(FlowsDataService);
flowsService = TestBed.inject(FlowsService);
router = TestBed.inject(Router);
router = TestBed.inject(AbstractRouter);
});
it('should create', () => {
@ -41,10 +39,9 @@ describe('ImplicitFlowCallbackService ', () => {
describe('authorizedImplicitFlowCallback', () => {
it('calls flowsService.processImplicitFlowCallback with hash if given', () => {
const spy = spyOn(
flowsService,
'processImplicitFlowCallback'
).and.returnValue(of({} as CallbackContext));
const spy = vi
.spyOn(flowsService, 'processImplicitFlowCallback')
.mockReturnValue(of({} as CallbackContext));
const config = {
configId: 'configId1',
triggerAuthorizationResultEvent: true,
@ -56,10 +53,14 @@ describe('ImplicitFlowCallbackService ', () => {
'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 = {
code: '',
refreshToken: '',
@ -71,11 +72,10 @@ describe('ImplicitFlowCallbackService ', () => {
validationResult: null,
existingIdToken: '',
};
const spy = spyOn(
flowsService,
'processImplicitFlowCallback'
).and.returnValue(of(callbackContext));
const routerSpy = spyOn(router, 'navigateByUrl');
const spy = vi
.spyOn(flowsService, 'processImplicitFlowCallback')
.mockReturnValue(of(callbackContext));
const routerSpy = vi.spyOn(router, 'navigateByUrl');
const config = {
configId: 'configId1',
triggerAuthorizationResultEvent: true,
@ -84,12 +84,16 @@ describe('ImplicitFlowCallbackService ', () => {
implicitFlowCallbackService
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
.subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash');
expect(spy).toHaveBeenCalledExactlyOnceWith(
config,
[config],
'some-hash'
);
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 = {
code: '',
refreshToken: '',
@ -101,11 +105,10 @@ describe('ImplicitFlowCallbackService ', () => {
validationResult: null,
existingIdToken: '',
};
const spy = spyOn(
flowsService,
'processImplicitFlowCallback'
).and.returnValue(of(callbackContext));
const routerSpy = spyOn(router, 'navigateByUrl');
const spy = vi
.spyOn(flowsService, 'processImplicitFlowCallback')
.mockReturnValue(of(callbackContext));
const routerSpy = vi.spyOn(router, 'navigateByUrl');
const config = {
configId: 'configId1',
triggerAuthorizationResultEvent: false,
@ -115,20 +118,24 @@ describe('ImplicitFlowCallbackService ', () => {
implicitFlowCallbackService
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
.subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash');
expect(routerSpy).toHaveBeenCalledOnceWith('postLoginRoute');
expect(spy).toHaveBeenCalledExactlyOnceWith(
config,
[config],
'some-hash'
);
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
});
}));
});
it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', waitForAsync(() => {
spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue(
it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', async () => {
vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue(
throwError(() => new Error('error'))
);
const resetSilentRenewRunningSpy = spyOn(
const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService,
'resetSilentRenewRunning'
);
const stopPeriodicallyTokenCheckSpy = spyOn(
const stopPeriodicallyTokenCheckSpy = vi.spyOn(
intervalService,
'stopPeriodicTokenCheck'
);
@ -147,23 +154,23 @@ describe('ImplicitFlowCallbackService ', () => {
expect(err).toBeTruthy();
},
});
}));
});
it(`navigates to unauthorizedRoute in case of error and in case of error and
triggerAuthorizationResultEvent is false`, waitForAsync(() => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue(
triggerAuthorizationResultEvent is false`, async () => {
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue(
throwError(() => new Error('error'))
);
const resetSilentRenewRunningSpy = spyOn(
const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService,
'resetSilentRenewRunning'
);
const stopPeriodicallTokenCheckSpy = spyOn(
const stopPeriodicallTokenCheckSpy = vi.spyOn(
intervalService,
'stopPeriodicTokenCheck'
);
const routerSpy = spyOn(router, 'navigateByUrl');
const routerSpy = vi.spyOn(router, 'navigateByUrl');
const config = {
configId: 'configId1',
triggerAuthorizationResultEvent: false,
@ -177,9 +184,11 @@ describe('ImplicitFlowCallbackService ', () => {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
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 { Observable, throwError } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import { type Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context';
import type { OpenIdConfiguration } from '../config/openid-configuration';
import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service';
import { FlowsService } from '../flows/flows.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 { vi } from 'vitest';
import { IntervalService } from './interval.service';
describe('IntervalService', () => {
@ -31,7 +32,7 @@ describe('IntervalService', () => {
describe('stopPeriodicTokenCheck', () => {
it('calls unsubscribe and sets to null', () => {
intervalService.runTokenValidationRunning = new Subscription();
const spy = spyOn(
const spy = vi.spyOn(
intervalService.runTokenValidationRunning,
'unsubscribe'
);
@ -44,7 +45,7 @@ describe('IntervalService', () => {
it('does nothing if `runTokenValidationRunning` is null', () => {
intervalService.runTokenValidationRunning = new Subscription();
const spy = spyOn(
const spy = vi.spyOn(
intervalService.runTokenValidationRunning,
'unsubscribe'
);
@ -57,7 +58,7 @@ describe('IntervalService', () => {
});
describe('startPeriodicTokenCheck', () => {
it('starts check after correct milliseconds', fakeAsync(() => {
it('starts check after correct milliseconds', async () => {
const periodicCheck = intervalService.startPeriodicTokenCheck(0.5);
const spy = jasmine.createSpy();
const sub = periodicCheck.subscribe(() => {
@ -71,6 +72,6 @@ describe('IntervalService', () => {
expect(spy).toHaveBeenCalledTimes(2);
sub.unsubscribe();
}));
});
});
});

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import { inject, Injectable } from 'injection-js';
import { forkJoin, Observable, of, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
import { Injectable, inject } from 'injection-js';
import { type Observable, forkJoin, of, throwError } from 'rxjs';
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
import { AuthStateService } from '../auth-state/auth-state.service';
import { ConfigurationService } from '../config/config.service';
import { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context';
import type { OpenIdConfiguration } from '../config/openid-configuration';
import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
@ -52,7 +52,7 @@ export class PeriodicallyTokenCheckService {
startTokenValidationPeriodically(
allConfigs: OpenIdConfiguration[],
currentConfig: OpenIdConfiguration
): void {
): Observable<void> {
const configsWithSilentRenewEnabled =
this.getConfigsWithSilentRenewEnabled(allConfigs);
@ -75,46 +75,51 @@ export class PeriodicallyTokenCheckService {
[id: string]: Observable<boolean | CallbackContext | null>;
} = {};
configsWithSilentRenewEnabled.forEach((config) => {
for (const config of configsWithSilentRenewEnabled) {
const identifier = config.configId as string;
const refreshEvent = this.getRefreshEvent(config, allConfigs);
objectWithConfigIdsAndRefreshEvent[identifier] = refreshEvent;
});
}
return forkJoin(objectWithConfigIdsAndRefreshEvent);
})
);
this.intervalService.runTokenValidationRunning = periodicallyCheck$
.pipe(catchError((error) => throwError(() => new Error(error))))
.subscribe({
next: (objectWithConfigIds) => {
for (const [configId, _] of Object.entries(objectWithConfigIds)) {
this.configurationService
.getOpenIDConfiguration(configId)
.subscribe((config) => {
this.loggerService.logDebug(
config,
'silent renew, periodic check finished!'
);
const o$ = periodicallyCheck$.pipe(
catchError((error) => throwError(() => new Error(error))),
map((objectWithConfigIds) => {
for (const [configId, _] of Object.entries(objectWithConfigIds)) {
this.configurationService
.getOpenIDConfiguration(configId)
.subscribe((config) => {
this.loggerService.logDebug(
config,
'silent renew, periodic check finished!'
);
if (
this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config)
) {
this.flowsDataService.resetSilentRenewRunning(config);
}
});
}
},
error: (error) => {
this.loggerService.logError(
currentConfig,
'silent renew failed!',
error
);
},
});
if (
this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config)
) {
this.flowsDataService.resetSilentRenewRunning(config);
}
});
}
}),
catchError((error) => {
this.loggerService.logError(
currentConfig,
'silent renew failed!',
error
);
return throwError(() => error);
}),
shareReplay(1)
);
this.intervalService.runTokenValidationRunning = o$.subscribe();
return o$;
}
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 { mockProvider } from '../../test/auto-mock';
import { CallbackContext } from '../flows/callback-context';
import { vi } from 'vitest';
import type { CallbackContext } from '../flows/callback-context';
import { FlowsService } from '../flows/flows.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { IntervalService } from './interval.service';
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
@ -25,9 +26,6 @@ describe('RefreshSessionRefreshTokenService', () => {
mockProvider(IntervalService),
],
});
});
beforeEach(() => {
flowsService = TestBed.inject(FlowsService);
refreshSessionRefreshTokenService = TestBed.inject(
RefreshSessionRefreshTokenService
@ -41,10 +39,10 @@ describe('RefreshSessionRefreshTokenService', () => {
});
describe('refreshSessionWithRefreshTokens', () => {
it('calls flowsService.processRefreshToken()', waitForAsync(() => {
const spy = spyOn(flowsService, 'processRefreshToken').and.returnValue(
of({} as CallbackContext)
);
it('calls flowsService.processRefreshToken()', async () => {
const spy = vi
.spyOn(flowsService, 'processRefreshToken')
.mockReturnValue(of({} as CallbackContext));
refreshSessionRefreshTokenService
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [
@ -53,13 +51,13 @@ describe('RefreshSessionRefreshTokenService', () => {
.subscribe(() => {
expect(spy).toHaveBeenCalled();
});
}));
});
it('resetAuthorizationData in case of error', waitForAsync(() => {
spyOn(flowsService, 'processRefreshToken').and.returnValue(
it('resetAuthorizationData in case of error', async () => {
vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue(
throwError(() => new Error('error'))
);
const resetSilentRenewRunningSpy = spyOn(
const resetSilentRenewRunningSpy = vi.spyOn(
resetAuthDataService,
'resetAuthorizationData'
);
@ -74,13 +72,13 @@ describe('RefreshSessionRefreshTokenService', () => {
expect(err).toBeTruthy();
},
});
}));
});
it('finalize with stopPeriodicTokenCheck in case of error', fakeAsync(() => {
spyOn(flowsService, 'processRefreshToken').and.returnValue(
it('finalize with stopPeriodicTokenCheck in case of error', async () => {
vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue(
throwError(() => new Error('error'))
);
const stopPeriodicallyTokenCheckSpy = spyOn(
const stopPeriodicallyTokenCheckSpy = vi.spyOn(
intervalService,
'stopPeriodicTokenCheck'
);
@ -94,8 +92,8 @@ describe('RefreshSessionRefreshTokenService', () => {
expect(err).toBeTruthy();
},
});
tick();
await vi.advanceTimersByTimeAsync(0);
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
}));
});
});
});

View File

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

View File

@ -1,10 +1,10 @@
import { inject, Injectable } from 'injection-js';
import { Injectable, inject } from 'injection-js';
import {
type Observable,
TimeoutError,
forkJoin,
Observable,
of,
throwError,
TimeoutError,
timer,
} from 'rxjs';
import {
@ -18,13 +18,13 @@ import {
} from 'rxjs/operators';
import { AuthStateService } from '../auth-state/auth-state.service';
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
import { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context';
import type { OpenIdConfiguration } from '../config/openid-configuration';
import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service';
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
import { SilentRenewService } from '../iframe/silent-renew.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 { UserService } from '../user-data/user.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 { mockProvider } from '../../../test/auto-mock';
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
import { vi } from 'vitest';
import { DataService } from '../../api/data.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 { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
const DUMMY_WELL_KNOWN_DOCUMENT = {
issuer: 'https://identity-server.test/realms/main',
@ -51,56 +52,65 @@ describe('AuthWellKnownDataService', () => {
});
describe('getWellKnownDocument', () => {
it('should add suffix if it does not exist on current URL', waitForAsync(() => {
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
of(null)
);
it('should add suffix if it does not exist on current URL', async () => {
const dataServiceSpy = vi
.spyOn(dataService, 'get')
.mockReturnValue(of(null));
const urlWithoutSuffix = 'myUrl';
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/openid-configuration`;
(service as any)
.getWellKnownDocument(urlWithoutSuffix, { configId: 'configId1' })
.subscribe(() => {
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
configId: 'configId1',
});
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
urlWithSuffix,
{
configId: 'configId1',
}
);
});
}));
});
it('should not add suffix if it does exist on current url', waitForAsync(() => {
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
of(null)
);
it('should not add suffix if it does exist on current url', async () => {
const dataServiceSpy = vi
.spyOn(dataService, 'get')
.mockReturnValue(of(null));
const urlWithSuffix = `myUrl/.well-known/openid-configuration`;
(service as any)
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
.subscribe(() => {
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
configId: 'configId1',
});
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
urlWithSuffix,
{
configId: 'configId1',
}
);
});
}));
});
it('should not add suffix if it does exist in the middle of current url', waitForAsync(() => {
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
of(null)
);
it('should not add suffix if it does exist in the middle of current url', async () => {
const dataServiceSpy = vi
.spyOn(dataService, 'get')
.mockReturnValue(of(null));
const urlWithSuffix = `myUrl/.well-known/openid-configuration/and/some/more/stuff`;
(service as any)
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
.subscribe(() => {
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
configId: 'configId1',
});
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
urlWithSuffix,
{
configId: 'configId1',
}
);
});
}));
});
it('should use the custom suffix provided in the config', waitForAsync(() => {
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
of(null)
);
it('should use the custom suffix provided in the config', async () => {
const dataServiceSpy = vi
.spyOn(dataService, 'get')
.mockReturnValue(of(null));
const urlWithoutSuffix = `myUrl`;
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`;
@ -110,15 +120,18 @@ describe('AuthWellKnownDataService', () => {
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
})
.subscribe(() => {
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
configId: 'configId1',
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
});
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
urlWithSuffix,
{
configId: 'configId1',
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
}
);
});
}));
});
it('should retry once', waitForAsync(() => {
spyOn(dataService, 'get').and.returnValue(
it('should retry once', async () => {
vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream(
throwError(() => new Error('one')),
of(DUMMY_WELL_KNOWN_DOCUMENT)
@ -133,10 +146,10 @@ describe('AuthWellKnownDataService', () => {
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
},
});
}));
});
it('should retry twice', waitForAsync(() => {
spyOn(dataService, 'get').and.returnValue(
it('should retry twice', async () => {
vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream(
throwError(() => new Error('one')),
throwError(() => new Error('two')),
@ -152,10 +165,10 @@ describe('AuthWellKnownDataService', () => {
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
},
});
}));
});
it('should fail after three tries', waitForAsync(() => {
spyOn(dataService, 'get').and.returnValue(
it('should fail after three tries', async () => {
vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream(
throwError(() => new Error('one')),
throwError(() => new Error('two')),
@ -169,17 +182,16 @@ describe('AuthWellKnownDataService', () => {
expect(err).toBeTruthy();
},
});
}));
});
});
describe('getWellKnownEndPointsForConfig', () => {
it('calling internal getWellKnownDocument and maps', waitForAsync(() => {
spyOn(dataService, 'get').and.returnValue(of({ jwks_uri: 'jwks_uri' }));
it('calling internal getWellKnownDocument and maps', async () => {
vi.spyOn(dataService, 'get').mockReturnValue(
of({ jwks_uri: 'jwks_uri' })
);
const spy = spyOn(
service as any,
'getWellKnownDocument'
).and.callThrough();
const spy = vi.spyOn(service as any, 'getWellKnownDocument')();
service
.getWellKnownEndPointsForConfig({
@ -191,10 +203,10 @@ describe('AuthWellKnownDataService', () => {
expect((result as any).jwks_uri).toBeUndefined();
expect(result.jwksUri).toBe('jwks_uri');
});
}));
});
it('throws error and logs if no authwellknownUrl is given', waitForAsync(() => {
const loggerSpy = spyOn(loggerService, 'logError');
it('throws error and logs if no authwellknownUrl is given', async () => {
const loggerSpy = vi.spyOn(loggerService, 'logError');
const config = {
configId: 'configId1',
authWellknownEndpointUrl: undefined,
@ -202,17 +214,19 @@ describe('AuthWellKnownDataService', () => {
service.getWellKnownEndPointsForConfig(config).subscribe({
error: (error) => {
expect(loggerSpy).toHaveBeenCalledOnceWith(
expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
config,
'no authWellknownEndpoint given!'
);
expect(error.message).toEqual('no authWellknownEndpoint given!');
},
});
}));
});
it('should merge the mapped endpoints with the provided endpoints', waitForAsync(() => {
spyOn(dataService, 'get').and.returnValue(of(DUMMY_WELL_KNOWN_DOCUMENT));
it('should merge the mapped endpoints with the provided endpoints', async () => {
vi.spyOn(dataService, 'get').mockReturnValue(
of(DUMMY_WELL_KNOWN_DOCUMENT)
);
const expected: AuthWellKnownEndpoints = {
endSessionEndpoint: 'config-endSessionEndpoint',
@ -232,6 +246,6 @@ describe('AuthWellKnownDataService', () => {
.subscribe((result) => {
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 { mockProvider } from '../../../test/auto-mock';
import { vi } from 'vitest';
import { EventTypes } from '../../public-events/event-types';
import { PublicEventsService } from '../../public-events/public-events.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { mockProvider } from '../../testing/mock';
import { AuthWellKnownDataService } from './auth-well-known-data.service';
import { AuthWellKnownService } from './auth-well-known.service';
@ -22,9 +23,6 @@ describe('AuthWellKnownService', () => {
mockProvider(StoragePersistenceService),
],
});
});
beforeEach(() => {
service = TestBed.inject(AuthWellKnownService);
dataService = TestBed.inject(AuthWellKnownDataService);
storagePersistenceService = TestBed.inject(StoragePersistenceService);
@ -36,7 +34,7 @@ describe('AuthWellKnownService', () => {
});
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({
error: (error) => {
expect(error).toEqual(
@ -46,17 +44,18 @@ describe('AuthWellKnownService', () => {
);
},
});
}));
});
it('getAuthWellKnownEndPoints calls always dataservice', waitForAsync(() => {
const dataServiceSpy = spyOn(
dataService,
'getWellKnownEndPointsForConfig'
).and.returnValue(of({ issuer: 'anything' }));
it('getAuthWellKnownEndPoints calls always dataservice', async () => {
const dataServiceSpy = vi
.spyOn(dataService, 'getWellKnownEndPointsForConfig')
.mockReturnValue(of({ issuer: 'anything' }));
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue({ issuer: 'anything' });
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ issuer: 'anything' })
);
service
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
@ -65,18 +64,19 @@ describe('AuthWellKnownService', () => {
expect(dataServiceSpy).toHaveBeenCalled();
expect(result).toEqual({ issuer: 'anything' });
});
}));
});
it('getAuthWellKnownEndPoints stored the result if http call is made', waitForAsync(() => {
const dataServiceSpy = spyOn(
dataService,
'getWellKnownEndPointsForConfig'
).and.returnValue(of({ issuer: 'anything' }));
it('getAuthWellKnownEndPoints stored the result if http call is made', async () => {
const dataServiceSpy = vi
.spyOn(dataService, 'getWellKnownEndPointsForConfig')
.mockReturnValue(of({ issuer: 'anything' }));
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue(null);
const storeSpy = spyOn(service, 'storeWellKnownEndpoints');
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => null
);
const storeSpy = vi.spyOn(service, 'storeWellKnownEndpoints');
service
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
@ -85,13 +85,13 @@ describe('AuthWellKnownService', () => {
expect(storeSpy).toHaveBeenCalled();
expect(result).toEqual({ issuer: 'anything' });
});
}));
});
it('throws `ConfigLoadingFailed` event when error happens from http', waitForAsync(() => {
spyOn(dataService, 'getWellKnownEndPointsForConfig').and.returnValue(
it('throws `ConfigLoadingFailed` event when error happens from http', async () => {
vi.spyOn(dataService, 'getWellKnownEndPointsForConfig').mockReturnValue(
throwError(() => new Error('error'))
);
const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent');
const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent');
service
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
@ -99,12 +99,12 @@ describe('AuthWellKnownService', () => {
error: (err) => {
expect(err).toBeTruthy();
expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1);
expect(publicEventsServiceSpy).toHaveBeenCalledOnceWith(
expect(publicEventsServiceSpy).toHaveBeenCalledExactlyOnceWith(
EventTypes.ConfigLoadingFailed,
null
);
},
});
}));
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { LogLevel } from '../logging/log-level';
import { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints';
import type { LogLevel } from '../logging/log-level';
import type { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints';
export interface OpenIdConfiguration {
/**
@ -207,5 +207,5 @@ export interface OpenIdConfiguration {
/**
* 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 { mockProvider } from '../../../test/auto-mock';
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { vi } from 'vitest';
import { LogLevel } from '../../logging/log-level';
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 { allMultipleConfigRules } from './rules';
@ -14,6 +15,8 @@ describe('Config Validation Service', () => {
TestBed.configureTestingModule({
providers: [ConfigValidationService, mockProvider(LoggerService)],
});
configValidationService = TestBed.inject(ConfigValidationService);
loggerService = TestBed.inject(LoggerService);
});
const VALID_CONFIG = {
@ -29,11 +32,6 @@ describe('Config Validation Service', () => {
logLevel: LogLevel.Debug,
};
beforeEach(() => {
configValidationService = TestBed.inject(ConfigValidationService);
loggerService = TestBed.inject(LoggerService);
});
it('should create', () => {
expect(configValidationService).toBeTruthy();
});
@ -42,26 +40,27 @@ describe('Config Validation Service', () => {
const config = {};
const result = configValidationService.validateConfig(config);
expect(result).toBeFalse();
expect(result).toBeFalsy();
});
it('should return true for valid config', () => {
const result = configValidationService.validateConfig(VALID_CONFIG);
expect(result).toBeTrue();
expect(result).toBeTruthy();
});
it('calls `logWarning` if one rule has warning level', () => {
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
const messageTypeSpy = spyOn(
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
const messageTypeSpy = vi.spyOn(
configValidationService as any,
'getAllMessagesOfType'
);
messageTypeSpy
.withArgs('warning', jasmine.any(Array))
.and.returnValue(['A warning message']);
messageTypeSpy.withArgs('error', jasmine.any(Array)).and.callThrough();
mockImplementationWhenArgsEqual(
messageTypeSpy,
(arg1: any, arg2: any) => arg1 === 'warning' && Array.isArray(arg2),
() => ['A warning message']
);
configValidationService.validateConfig(VALID_CONFIG);
expect(loggerWarningSpy).toHaveBeenCalled();
@ -72,7 +71,7 @@ describe('Config Validation Service', () => {
const config = { ...VALID_CONFIG, clientId: '' } as OpenIdConfiguration;
const result = configValidationService.validateConfig(config);
expect(result).toBeFalse();
expect(result).toBeFalsy();
});
});
@ -84,7 +83,7 @@ describe('Config Validation Service', () => {
} as OpenIdConfiguration;
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 result = configValidationService.validateConfig(config);
expect(result).toBeFalse();
expect(result).toBeFalsy();
});
});
@ -107,7 +106,7 @@ describe('Config Validation Service', () => {
} as OpenIdConfiguration;
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',
};
const loggerSpy = spyOn(loggerService, 'logError');
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
const loggerSpy = vi.spyOn(loggerService, 'logError');
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
const result = configValidationService.validateConfig(config);
expect(result).toBeTrue();
expect(result).toBeTruthy();
expect(loggerSpy).not.toHaveBeenCalled();
expect(loggerWarningSpy).toHaveBeenCalled();
});
@ -146,47 +145,47 @@ describe('Config Validation Service', () => {
scopes: 'scope1 scope2 but_no_offline_access',
};
const loggerErrorSpy = spyOn(loggerService, 'logError');
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
const loggerErrorSpy = vi.spyOn(loggerService, 'logError');
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
const result = configValidationService.validateConfigs([
config1,
config2,
]);
expect(result).toBeTrue();
expect(result).toBeTruthy();
expect(loggerErrorSpy).not.toHaveBeenCalled();
expect(loggerWarningSpy.calls.argsFor(0)).toEqual([
expect(vi.mocked(loggerWarningSpy).mock.calls[0]).toEqual([
config1,
'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,
'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', () => {
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
const result = configValidationService.validateConfigs([]);
expect(result).toBeFalse();
expect(result).toBeFalsy();
expect(loggerWarningSpy).not.toHaveBeenCalled();
});
});
describe('validateConfigs', () => {
it('calls internal method with empty array if something falsy is passed', () => {
const spy = spyOn(
const spy = vi.spyOn(
configValidationService as any,
'validateConfigsInternal'
).and.callThrough();
);
const result = configValidationService.validateConfigs([]);
expect(result).toBeFalse();
expect(spy).toHaveBeenCalledOnceWith([], allMultipleConfigRules);
expect(result).toBeFalsy();
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 {
JwkExtractor,

View File

@ -1,14 +1,15 @@
import { HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { HttpErrorResponse, HttpHeaders } from '@ngify/http';
import { of, throwError } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock';
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
import { vi } from 'vitest';
import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { createRetriableStream } from '../../testing/create-retriable-stream.helper';
import { mockProvider } from '../../testing/mock';
import { UrlService } from '../../utils/url/url.service';
import { TokenValidationService } from '../../validation/token-validation.service';
import { CallbackContext } from '../callback-context';
import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service';
import { CodeFlowCallbackHandlerService } from './code-flow-callback-handler.service';
@ -46,13 +47,16 @@ describe('CodeFlowCallbackHandlerService', () => {
});
describe('codeFlowCallback', () => {
it('throws error if no state is given', waitForAsync(() => {
const getUrlParameterSpy = spyOn(
urlService,
'getUrlParameter'
).and.returnValue('params');
it('throws error if no state is given', async () => {
const getUrlParameterSpy = vi
.spyOn(urlService, 'getUrlParameter')
.mockReturnValue('params');
getUrlParameterSpy.withArgs('test-url', 'state').and.returnValue('');
mockImplementationWhenArgsEqual(
getUrlParameterSpy,
['test-url', 'state'],
() => ''
);
service
.codeFlowCallback('test-url', { configId: 'configId1' })
@ -61,15 +65,14 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy();
},
});
}));
});
it('throws error if no code is given', waitForAsync(() => {
const getUrlParameterSpy = spyOn(
urlService,
'getUrlParameter'
).and.returnValue('params');
it('throws error if no code is given', async () => {
const getUrlParameterSpy = vi
.spyOn(urlService, 'getUrlParameter')
.mockReturnValue('params');
getUrlParameterSpy.withArgs('test-url', 'code').and.returnValue('');
getUrlParameterSpy.withArgs('test-url', 'code').mockReturnValue('');
service
.codeFlowCallback('test-url', { configId: 'configId1' })
@ -78,10 +81,10 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy();
},
});
}));
});
it('returns callbackContext if all params are good', waitForAsync(() => {
spyOn(urlService, 'getUrlParameter').and.returnValue('params');
it('returns callbackContext if all params are good', async () => {
vi.spyOn(urlService, 'getUrlParameter').mockReturnValue('params');
const expectedCallbackContext = {
code: 'params',
@ -100,7 +103,7 @@ describe('CodeFlowCallbackHandlerService', () => {
.subscribe((callbackContext) => {
expect(callbackContext).toEqual(expectedCallbackContext);
});
}));
});
});
describe('codeFlowCodeRequest ', () => {
@ -112,11 +115,11 @@ describe('CodeFlowCallbackHandlerService', () => {
url: 'https://identity-server.test/openid-connect/token',
});
it('throws error if state is not correct', waitForAsync(() => {
spyOn(
it('throws error if state is not correct', async () => {
vi.spyOn(
tokenValidationService,
'validateStateFromHashCallback'
).and.returnValue(false);
).mockReturnValue(false);
service
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
@ -125,16 +128,18 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy();
},
});
}));
});
it('throws error if authWellknownEndpoints is null is given', waitForAsync(() => {
spyOn(
it('throws error if authWellknownEndpoints is null is given', async () => {
vi.spyOn(
tokenValidationService,
'validateStateFromHashCallback'
).and.returnValue(true);
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue(null);
).mockReturnValue(true);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => null
);
service
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
@ -143,16 +148,18 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy();
},
});
}));
});
it('throws error if tokenendpoint is null is given', waitForAsync(() => {
spyOn(
it('throws error if tokenendpoint is null is given', async () => {
vi.spyOn(
tokenValidationService,
'validateStateFromHashCallback'
).and.returnValue(true);
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue({ tokenEndpoint: null });
).mockReturnValue(true);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ tokenEndpoint: null })
);
service
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
@ -161,34 +168,36 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeTruthy();
},
});
}));
});
it('calls dataService if all params are good', waitForAsync(() => {
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
it('calls dataService if all params are good', async () => {
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn(
vi.spyOn(
tokenValidationService,
'validateStateFromHashCallback'
).and.returnValue(true);
).mockReturnValue(true);
service
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
.subscribe(() => {
expect(postSpy).toHaveBeenCalledOnceWith(
expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'tokenEndpoint',
undefined,
{ configId: 'configId1' },
jasmine.any(HttpHeaders)
expect.any(HttpHeaders)
);
});
}));
});
it('calls url service with custom token params', waitForAsync(() => {
const urlServiceSpy = spyOn(
it('calls url service with custom token params', async () => {
const urlServiceSpy = vi.spyOn(
urlService,
'createBodyForCodeFlowCodeRequest'
);
@ -197,76 +206,84 @@ describe('CodeFlowCallbackHandlerService', () => {
customParamsCodeRequest: { foo: 'bar' },
};
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn(
vi.spyOn(
tokenValidationService,
'validateStateFromHashCallback'
).and.returnValue(true);
).mockReturnValue(true);
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
service
.codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config)
.subscribe(() => {
expect(urlServiceSpy).toHaveBeenCalledOnceWith('foo', config, {
expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith('foo', config, {
foo: 'bar',
});
expect(postSpy).toHaveBeenCalledTimes(1);
});
}));
});
it('calls dataService with correct headers if all params are good', waitForAsync(() => {
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
it('calls dataService with correct headers if all params are good', async () => {
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
const config = {
configId: 'configId1',
customParamsCodeRequest: { foo: 'bar' },
};
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn(
vi.spyOn(
tokenValidationService,
'validateStateFromHashCallback'
).and.returnValue(true);
).mockReturnValue(true);
service
.codeFlowCodeRequest({} as CallbackContext, config)
.subscribe(() => {
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(
'application/x-www-form-urlencoded'
);
});
}));
});
it('returns error in case of http error', waitForAsync(() => {
spyOn(dataService, 'post').and.returnValue(throwError(() => HTTP_ERROR));
it('returns error in case of http error', async () => {
vi.spyOn(dataService, 'post').mockReturnValue(
throwError(() => HTTP_ERROR)
);
const config = {
configId: 'configId1',
customParamsCodeRequest: { foo: 'bar' },
authority: 'authority',
};
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
error: (err) => {
expect(err).toBeTruthy();
},
});
}));
});
it('retries request in case of no connection http error and succeeds', waitForAsync(() => {
const postSpy = spyOn(dataService, 'post').and.returnValue(
it('retries request in case of no connection http error and succeeds', async () => {
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream(
throwError(() => CONNECTION_ERROR),
of({})
@ -278,14 +295,16 @@ describe('CodeFlowCallbackHandlerService', () => {
authority: 'authority',
};
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn(
vi.spyOn(
tokenValidationService,
'validateStateFromHashCallback'
).and.returnValue(true);
).mockReturnValue(true);
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
next: (res) => {
@ -297,10 +316,10 @@ describe('CodeFlowCallbackHandlerService', () => {
expect(err).toBeFalsy();
},
});
}));
});
it('retries request in case of no connection http error and fails because of http error afterwards', waitForAsync(() => {
const postSpy = spyOn(dataService, 'post').and.returnValue(
it('retries request in case of no connection http error and fails because of http error afterwards', async () => {
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
createRetriableStream(
throwError(() => CONNECTION_ERROR),
throwError(() => HTTP_ERROR)
@ -312,14 +331,16 @@ describe('CodeFlowCallbackHandlerService', () => {
authority: 'authority',
};
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ tokenEndpoint: 'tokenEndpoint' })
);
spyOn(
vi.spyOn(
tokenValidationService,
'validateStateFromHashCallback'
).and.returnValue(true);
).mockReturnValue(true);
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
next: (res) => {
@ -331,6 +352,6 @@ describe('CodeFlowCallbackHandlerService', () => {
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';
describe('error helper', () => {
@ -27,31 +27,31 @@ describe('error helper', () => {
});
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', () => {
expect(isNetworkError(UNKNOWN_CONNECTION_ERROR)).toBeTrue();
expect(isNetworkError(UNKNOWN_CONNECTION_ERROR)).toBeTruthy();
});
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', () => {
expect(isNetworkError(new Error('not a HttpErrorResponse'))).toBeFalse();
expect(isNetworkError(new Error('not a HttpErrorResponse'))).toBeFalsy();
});
it('returns false on string error', () => {
expect(isNetworkError('not a HttpErrorResponse')).toBeFalse();
expect(isNetworkError('not a HttpErrorResponse')).toBeFalsy();
});
it('returns false on undefined', () => {
expect(isNetworkError(undefined)).toBeFalse();
expect(isNetworkError(undefined)).toBeFalsy();
});
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 { mockProvider } from '../../../test/auto-mock';
import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service';
import { LoggerService } from '../../logging/logger.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 { AuthResult, CallbackContext } from '../callback-context';
import type { AuthResult, CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-data.service';
import { SigninKeyDataService } from '../signin-key-data.service';
@ -46,9 +47,6 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
mockProvider(ResetAuthDataService),
],
});
});
beforeEach(() => {
service = TestBed.inject(HistoryJwtKeysCallbackHandlerService);
storagePersistenceService = TestBed.inject(StoragePersistenceService);
resetAuthDataService = TestBed.inject(ResetAuthDataService);
@ -62,8 +60,8 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
});
describe('callbackHistoryAndResetJwtKeys', () => {
it('writes authResult into the storage', waitForAsync(() => {
const storagePersistenceServiceSpy = spyOn(
it('writes authResult into the storage', async () => {
const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService,
'write'
);
@ -75,86 +73,86 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
const callbackContext = {
authResult: DUMMY_AUTH_RESULT,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
},
];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys)
);
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe(() => {
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]],
['jwtKeys', { keys: [] }, allconfigs[0]],
expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['jwtKeys', { keys: [] }, allConfigs[0]],
]);
// write authnResult & jwtKeys
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 = {
refresh_token: 'dummy_refresh_token',
id_token: 'some-id-token',
};
const storagePersistenceServiceSpy = spyOn(
const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService,
'write'
);
const callbackContext = {
authResult: DUMMY_AUTH_RESULT,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
},
];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys)
);
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe(() => {
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]],
['jwtKeys', { keys: [] }, allconfigs[0]],
expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['jwtKeys', { keys: [] }, allConfigs[0]],
]);
// write authnResult & refresh_token & jwtKeys
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 = {
refresh_token: 'dummy_refresh_token',
id_token: 'some-id-token',
};
const storagePersistenceServiceSpy = spyOn(
const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService,
'write'
);
const callbackContext = {
authResult: DUMMY_AUTH_RESULT,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
@ -162,27 +160,27 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
},
];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys)
);
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe(() => {
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]],
['reusable_refresh_token', 'dummy_refresh_token', allconfigs[0]],
['jwtKeys', { keys: [] }, allconfigs[0]],
expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['reusable_refresh_token', 'dummy_refresh_token', allConfigs[0]],
['jwtKeys', { keys: [] }, allConfigs[0]],
]);
// write authnResult & refresh_token & jwtKeys
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 = {
id_token: 'some-id-token',
};
@ -190,30 +188,30 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
isRenewProcess: false,
authResult: DUMMY_AUTH_RESULT,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
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)
);
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe(() => {
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 = {
id_token: 'some-id-token',
};
@ -222,21 +220,21 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
isRenewProcess: false,
authResult: DUMMY_AUTH_RESULT,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: false,
},
];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [{ kty: 'henlo' } as JwtKey] } as JwtKeys)
);
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe((result) => {
expect(result).toEqual({
@ -245,9 +243,9 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
jwtKeys: { keys: [{ kty: 'henlo' }] },
} 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 = {
id_token: 'some-id-token',
};
@ -256,32 +254,32 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
isRenewProcess: false,
authResult: DUMMY_AUTH_RESULT,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: false,
},
];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({} as JwtKeys)
);
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe({
error: (err) => {
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 = {
id_token: 'some-id-token',
};
@ -289,36 +287,36 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
isRenewProcess: false,
authResult: DUMMY_AUTH_RESULT,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: false,
},
];
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
throwError(() => new Error('error'))
);
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe({
error: (err) => {
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 = {
authResult: { error: 'someError' },
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
@ -328,36 +326,36 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe({
error: (err) => {
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 = {
authResult: { error: 'someError' },
isRenewProcess: false,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
},
];
const resetAuthorizationDataSpy = spyOn(
const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService,
'resetAuthorizationData'
);
const setNonceSpy = spyOn(flowsDataService, 'setNonce');
const updateAndPublishAuthStateSpy = spyOn(
const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce');
const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService,
'updateAndPublishAuthState'
);
@ -365,40 +363,42 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe({
error: () => {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect(setNonceSpy).toHaveBeenCalledTimes(1);
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
expect(
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false,
validationResult: ValidationResult.SecureTokenServerError,
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 = {
authResult: { error: 'login_required' },
isRenewProcess: false,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
},
];
const resetAuthorizationDataSpy = spyOn(
const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService,
'resetAuthorizationData'
);
const setNonceSpy = spyOn(flowsDataService, 'setNonce');
const updateAndPublishAuthStateSpy = spyOn(
const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce');
const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService,
'updateAndPublishAuthState'
);
@ -406,23 +406,25 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
service
.callbackHistoryAndResetJwtKeys(
callbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe({
error: () => {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect(setNonceSpy).toHaveBeenCalledTimes(1);
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
expect(
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false,
validationResult: ValidationResult.LoginRequired,
isRenewProcess: false,
});
},
});
}));
});
it('should store jwtKeys', waitForAsync(() => {
it('should store jwtKeys', async () => {
const DUMMY_AUTH_RESULT = {
id_token: 'some-id-token',
};
@ -430,33 +432,33 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
const initialCallbackContext = {
authResult: DUMMY_AUTH_RESULT,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
},
];
const storagePersistenceServiceSpy = spyOn(
const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService,
'write'
);
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of(DUMMY_JWT_KEYS)
);
service
.callbackHistoryAndResetJwtKeys(
initialCallbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe({
next: (callbackContext: CallbackContext) => {
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]],
['jwtKeys', DUMMY_JWT_KEYS, allconfigs[0]],
expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['jwtKeys', DUMMY_JWT_KEYS, allConfigs[0]],
]);
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
@ -465,9 +467,9 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
expect(err).toBeFalsy();
},
});
}));
});
it('should not store jwtKeys on error', waitForAsync(() => {
it('should not store jwtKeys on error', async () => {
const authResult = {
id_token: 'some-id-token',
access_token: 'some-access-token',
@ -476,26 +478,26 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
authResult,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
},
];
const storagePersistenceServiceSpy = spyOn(
const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService,
'write'
);
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
throwError(() => new Error('Error'))
);
service
.callbackHistoryAndResetJwtKeys(
initialCallbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe({
next: (callbackContext: CallbackContext) => {
@ -505,16 +507,18 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
expect(err).toBeTruthy();
// storagePersistenceService.write() should not have been called with jwtKeys
expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith(
expect(
storagePersistenceServiceSpy
).toHaveBeenCalledExactlyOnceWith(
'authnResult',
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 = {
id_token: 'some-id-token',
access_token: 'some-access-token',
@ -523,66 +527,65 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
authResult,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
},
];
const storagePersistenceServiceSpy = spyOn(
const storagePersistenceServiceSpy = vi.spyOn(
storagePersistenceService,
'read'
);
storagePersistenceServiceSpy.and.returnValue(DUMMY_JWT_KEYS);
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
storagePersistenceServiceSpy.mockReturnValue(DUMMY_JWT_KEYS);
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
throwError(() => new Error('Error'))
);
service
.callbackHistoryAndResetJwtKeys(
initialCallbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe({
next: (callbackContext: CallbackContext) => {
expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith(
'jwtKeys',
allconfigs[0]
);
expect(
storagePersistenceServiceSpy
).toHaveBeenCalledExactlyOnceWith('jwtKeys', allConfigs[0]);
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
},
error: (err) => {
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 = {
id_token: 'some-id-token',
access_token: 'some-access-token',
} as AuthResult;
const initialCallbackContext = { authResult } as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
historyCleanupOff: true,
},
];
spyOn(storagePersistenceService, 'read').and.returnValue(null);
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null);
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
throwError(() => new Error('Error'))
);
service
.callbackHistoryAndResetJwtKeys(
initialCallbackContext,
allconfigs[0],
allconfigs
allConfigs[0]!,
allConfigs
)
.subscribe({
next: (callbackContext: CallbackContext) => {
@ -592,7 +595,7 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
expect(err).toBeTruthy();
},
});
}));
});
});
describe('historyCleanUpTurnedOn ', () => {

View File

@ -1,14 +1,14 @@
import { DOCUMENT } from '../../dom';
import { inject, Injectable } from 'injection-js';
import { Observable, of, throwError } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import { type Observable, of, throwError } from 'rxjs';
import { catchError, switchMap, tap } from 'rxjs/operators';
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 { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { JwtKeys } from '../../validation/jwtkeys';
import type { JwtKeys } from '../../validation/jwtkeys';
import { ValidationResult } from '../../validation/validation-result';
import { CallbackContext } from '../callback-context';
import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-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 { TestBed, waitForAsync } from '@angular/core/testing';
import { mockProvider } from '../../../test/auto-mock';
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 { ResetAuthDataService } from '../reset-auth-data.service';
import { ImplicitFlowCallbackHandlerService } from './implicit-flow-callback-handler.service';
@ -34,9 +35,6 @@ describe('ImplicitFlowCallbackHandlerService', () => {
},
],
});
});
beforeEach(() => {
service = TestBed.inject(ImplicitFlowCallbackHandlerService);
flowsDataService = TestBed.inject(FlowsDataService);
resetAuthDataService = TestBed.inject(ResetAuthDataService);
@ -47,46 +45,46 @@ describe('ImplicitFlowCallbackHandlerService', () => {
});
describe('implicitFlowCallback', () => {
it('calls "resetAuthorizationData" if silent renew is not running', waitForAsync(() => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
const resetAuthorizationDataSpy = spyOn(
it('calls "resetAuthorizationData" if silent renew is not running', async () => {
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService,
'resetAuthorizationData'
);
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
},
];
service
.implicitFlowCallback(allconfigs[0], allconfigs, 'any-hash')
.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')
.subscribe(() => {
expect(resetAuthorizationDataSpy).toHaveBeenCalled();
});
}));
});
it('does NOT calls "resetAuthorizationData" if silent renew is running', waitForAsync(() => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
const resetAuthorizationDataSpy = spyOn(
it('does NOT calls "resetAuthorizationData" if silent renew is running', async () => {
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService,
'resetAuthorizationData'
);
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
},
];
service
.implicitFlowCallback(allconfigs[0], allconfigs, 'any-hash')
.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')
.subscribe(() => {
expect(resetAuthorizationDataSpy).not.toHaveBeenCalled();
});
}));
});
it('returns callbackContext if all params are good', waitForAsync(() => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
it('returns callbackContext if all params are good', async () => {
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
const expectedCallbackContext = {
code: '',
refreshToken: '',
@ -99,21 +97,21 @@ describe('ImplicitFlowCallbackHandlerService', () => {
existingIdToken: null,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
},
];
service
.implicitFlowCallback(allconfigs[0], allconfigs, 'anyHash')
.implicitFlowCallback(allConfigs[0]!, allConfigs, 'anyHash')
.subscribe((callbackContext) => {
expect(callbackContext).toEqual(expectedCallbackContext);
});
}));
});
it('uses window location hash if no hash is passed', waitForAsync(() => {
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
it('uses window location hash if no hash is passed', async () => {
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
const expectedCallbackContext = {
code: '',
refreshToken: '',
@ -126,17 +124,17 @@ describe('ImplicitFlowCallbackHandlerService', () => {
existingIdToken: null,
} as CallbackContext;
const allconfigs = [
const allConfigs = [
{
configId: 'configId1',
},
];
service
.implicitFlowCallback(allconfigs[0], allconfigs)
.implicitFlowCallback(allConfigs[0]!, allConfigs)
.subscribe((callbackContext) => {
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 { inject, Injectable } from 'injection-js';
import { Observable, of } from 'rxjs';
import { OpenIdConfiguration } from '../../config/openid-configuration';
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 { ResetAuthDataService } from '../reset-auth-data.service';

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
import { DOCUMENT } from '../../dom';
import { inject, Injectable } from 'injection-js';
import { Observable } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import type { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
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 { StateValidationResult } from '../../validation/state-validation-result';
import type { StateValidationResult } from '../../validation/state-validation-result';
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';
@Injectable()

View File

@ -1,12 +1,13 @@
import { TestBed, waitForAsync } from '@angular/core/testing';
import { TestBed } from '@/testing';
import { of } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock';
import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service';
import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { UserService } from '../../user-data/user.service';
import { StateValidationResult } from '../../validation/state-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 { ResetAuthDataService } from '../reset-auth-data.service';
import { UserCallbackHandlerService } from './user-callback-handler.service';
@ -44,7 +45,7 @@ describe('UserCallbackHandlerService', () => {
});
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(
'accesstoken',
'idtoken',
@ -70,17 +71,17 @@ describe('UserCallbackHandlerService', () => {
},
];
const spy = spyOn(flowsDataService, 'setSessionState');
const spy = vi.spyOn(flowsDataService, 'setSessionState');
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => {
expect(spy).toHaveBeenCalledOnceWith('mystate', allConfigs[0]);
expect(spy).toHaveBeenCalledExactlyOnceWith('mystate', allConfigs[0]);
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(
'accesstoken',
'idtoken',
@ -104,17 +105,17 @@ describe('UserCallbackHandlerService', () => {
autoUserInfo: false,
},
];
const spy = spyOn(flowsDataService, 'setSessionState');
const spy = vi.spyOn(flowsDataService, 'setSessionState');
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => {
expect(spy).not.toHaveBeenCalled();
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(
'accesstoken',
'idtoken',
@ -138,17 +139,17 @@ describe('UserCallbackHandlerService', () => {
autoUserInfo: false,
},
];
const spy = spyOn(flowsDataService, 'setSessionState');
const spy = vi.spyOn(flowsDataService, 'setSessionState');
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => {
expect(spy).not.toHaveBeenCalled();
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 callbackContext = {
code: '',
@ -168,17 +169,17 @@ describe('UserCallbackHandlerService', () => {
},
];
const spy = spyOn(flowsDataService, 'setSessionState');
const spy = vi.spyOn(flowsDataService, 'setSessionState');
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => {
expect(spy).not.toHaveBeenCalled();
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(
'accesstoken',
'idtoken',
@ -204,24 +205,24 @@ describe('UserCallbackHandlerService', () => {
},
];
const updateAndPublishAuthStateSpy = spyOn(
const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService,
'updateAndPublishAuthState'
);
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => {
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: true,
validationResult: ValidationResult.NotSet,
isRenewProcess: false,
});
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(
'accesstoken',
'idtoken',
@ -247,16 +248,17 @@ describe('UserCallbackHandlerService', () => {
},
];
const getAndPersistUserDataInStoreSpy = spyOn(
userService,
'getAndPersistUserDataInStore'
).and.returnValue(of({ user: 'some_data' }));
const getAndPersistUserDataInStoreSpy = vi
.spyOn(userService, 'getAndPersistUserDataInStore')
.mockReturnValue(of({ user: 'some_data' }));
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => {
expect(getAndPersistUserDataInStoreSpy).toHaveBeenCalledOnceWith(
allConfigs[0],
expect(
getAndPersistUserDataInStoreSpy
).toHaveBeenCalledExactlyOnceWith(
allConfigs[0]!,
allConfigs,
false,
'idtoken',
@ -264,9 +266,9 @@ describe('UserCallbackHandlerService', () => {
);
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(
'accesstoken',
'idtoken',
@ -293,27 +295,27 @@ describe('UserCallbackHandlerService', () => {
},
];
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue(
vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
of({ user: 'some_data' })
);
const updateAndPublishAuthStateSpy = spyOn(
const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService,
'updateAndPublishAuthState'
);
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => {
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: true,
validationResult: ValidationResult.MaxOffsetExpired,
isRenewProcess: false,
});
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(
'accesstoken',
'idtoken',
@ -340,23 +342,23 @@ describe('UserCallbackHandlerService', () => {
},
];
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue(
vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
of({ user: 'some_data' })
);
const setSessionStateSpy = spyOn(flowsDataService, 'setSessionState');
const setSessionStateSpy = vi.spyOn(flowsDataService, 'setSessionState');
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe((resultCallbackContext) => {
expect(setSessionStateSpy).toHaveBeenCalledOnceWith(
expect(setSessionStateSpy).toHaveBeenCalledExactlyOnceWith(
'mystate',
allConfigs[0]
);
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(
'accesstoken',
'idtoken',
@ -383,19 +385,21 @@ describe('UserCallbackHandlerService', () => {
},
];
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue(
vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
of(null)
);
const updateAndPublishAuthStateSpy = spyOn(
const updateAndPublishAuthStateSpy = vi.spyOn(
authStateService,
'updateAndPublishAuthState'
);
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe({
error: (err) => {
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
expect(
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false,
validationResult: ValidationResult.MaxOffsetExpired,
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(
'accesstoken',
'idtoken',
@ -434,16 +438,16 @@ describe('UserCallbackHandlerService', () => {
},
];
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue(
vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
of(null)
);
const resetAuthorizationDataSpy = spyOn(
const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService,
'resetAuthorizationData'
);
service
.callbackUser(callbackContext, allConfigs[0], allConfigs)
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
.subscribe({
error: (err) => {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
@ -452,6 +456,6 @@ describe('UserCallbackHandlerService', () => {
);
},
});
}));
});
});
});

View File

@ -1,12 +1,12 @@
import { inject, Injectable } from 'injection-js';
import { Observable, of, throwError } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import { type Observable, of, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
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 { UserService } from '../../user-data/user.service';
import { StateValidationResult } from '../../validation/state-validation-result';
import { CallbackContext } from '../callback-context';
import type { StateValidationResult } from '../../validation/state-validation-result';
import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-data.service';

View File

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

View File

@ -1,8 +1,8 @@
import { inject, Injectable } from 'injection-js';
import { OpenIdConfiguration } from '../config/openid-configuration';
import { Injectable, inject } from 'injection-js';
import type { OpenIdConfiguration } from '../config/openid-configuration';
import { LoggerService } from '../logging/logger.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';
@Injectable()
@ -18,7 +18,7 @@ export class FlowsDataService {
createNonce(configuration: OpenIdConfiguration): string {
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);
return nonce;

View File

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

View File

@ -1,8 +1,8 @@
import { inject, Injectable } from 'injection-js';
import { Observable } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import type { Observable } from 'rxjs';
import { concatMap } from 'rxjs/operators';
import { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from './callback-context';
import type { OpenIdConfiguration } from '../config/openid-configuration';
import type { CallbackContext } from './callback-context';
import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service';
import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-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 { mockProvider } from '../../../test/auto-mock';
import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { CryptoService } from '../../utils/crypto/crypto.service';
import { RandomService } from './random.service';

View File

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

View File

@ -1,11 +1,12 @@
import { HttpResponse } from '@angular/common/http';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { HttpResponse } from '@ngify/http';
import { isObservable, of, throwError } from 'rxjs';
import { mockProvider } from '../../test/auto-mock';
import { createRetriableStream } from '../../test/create-retriable-stream.helper';
import { vi } from 'vitest';
import { DataService } from '../api/data.service';
import { LoggerService } from '../logging/logger.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { createRetriableStream } from '../testing/create-retriable-stream.helper';
import { mockProvider } from '../testing/mock';
import { SigninKeyDataService } from './signin-key-data.service';
const DUMMY_JWKS = {
@ -53,10 +54,12 @@ describe('Signin Key Data Service', () => {
});
describe('getSigningKeys', () => {
it('throws error when no wellKnownEndpoints given', waitForAsync(() => {
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue(null);
it('throws error when no wellKnownEndpoints given', async () => {
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => null
);
const result = service.getSigningKeys({ configId: 'configId1' });
result.subscribe({
@ -64,12 +67,14 @@ describe('Signin Key Data Service', () => {
expect(err).toBeTruthy();
},
});
}));
});
it('throws error when no jwksUri given', waitForAsync(() => {
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue({ jwksUri: null });
it('throws error when no jwksUri given', async () => {
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ jwksUri: null })
);
const result = service.getSigningKeys({ configId: 'configId1' });
result.subscribe({
@ -77,30 +82,34 @@ describe('Signin Key Data Service', () => {
expect(err).toBeTruthy();
},
});
}));
});
it('calls dataservice if jwksurl is given', waitForAsync(() => {
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue({ jwksUri: 'someUrl' });
const spy = spyOn(dataService, 'get').and.callFake(() => of());
it('calls dataservice if jwksurl is given', async () => {
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ jwksUri: 'someUrl' })
);
const spy = vi.spyOn(dataService, 'get').mockImplementation(() => of());
const result = service.getSigningKeys({ configId: 'configId1' });
result.subscribe({
complete: () => {
expect(spy).toHaveBeenCalledOnceWith('someUrl', {
expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl', {
configId: 'configId1',
});
},
});
}));
});
it('should retry once', waitForAsync(() => {
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue({ jwksUri: 'someUrl' });
spyOn(dataService, 'get').and.returnValue(
it('should retry once', async () => {
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ jwksUri: 'someUrl' })
);
vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream(
throwError(() => new Error('Error')),
of(DUMMY_JWKS)
@ -113,13 +122,15 @@ describe('Signin Key Data Service', () => {
expect(res).toEqual(DUMMY_JWKS);
},
});
}));
});
it('should retry twice', waitForAsync(() => {
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue({ jwksUri: 'someUrl' });
spyOn(dataService, 'get').and.returnValue(
it('should retry twice', async () => {
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ jwksUri: 'someUrl' })
);
vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream(
throwError(() => new Error('Error')),
throwError(() => new Error('Error')),
@ -133,13 +144,15 @@ describe('Signin Key Data Service', () => {
expect(res).toEqual(DUMMY_JWKS);
},
});
}));
});
it('should fail after three tries', waitForAsync(() => {
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
.and.returnValue({ jwksUri: 'someUrl' });
spyOn(dataService, 'get').and.returnValue(
it('should fail after three tries', async () => {
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ jwksUri: 'someUrl' })
);
vi.spyOn(dataService, 'get').mockReturnValue(
createRetriableStream(
throwError(() => new Error('Error')),
throwError(() => new Error('Error')),
@ -153,21 +166,21 @@ describe('Signin Key Data Service', () => {
expect(err).toBeTruthy();
},
});
}));
});
});
describe('handleErrorGetSigningKeys', () => {
it('keeps observable if error is catched', waitForAsync(() => {
it('keeps observable if error is catched', async () => {
const result = (service as any).handleErrorGetSigningKeys(
new HttpResponse()
);
const hasTypeObservable = isObservable(result);
expect(hasTypeObservable).toBeTrue();
}));
expect(hasTypeObservable).toBeTruthy();
});
it('logs error if error is response', waitForAsync(() => {
const logSpy = spyOn(loggerService, 'logError');
it('logs error if error is response', async () => {
const logSpy = vi.spyOn(loggerService, 'logError');
(service as any)
.handleErrorGetSigningKeys(
@ -176,31 +189,31 @@ describe('Signin Key Data Service', () => {
)
.subscribe({
error: () => {
expect(logSpy).toHaveBeenCalledOnceWith(
expect(logSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' },
'400 - nono {}'
);
},
});
}));
});
it('logs error if error is not a response', waitForAsync(() => {
const logSpy = spyOn(loggerService, 'logError');
it('logs error if error is not a response', async () => {
const logSpy = vi.spyOn(loggerService, 'logError');
(service as any)
.handleErrorGetSigningKeys('Just some Error', { configId: 'configId1' })
.subscribe({
error: () => {
expect(logSpy).toHaveBeenCalledOnceWith(
expect(logSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' },
'Just some Error'
);
},
});
}));
});
it('logs error if error with message property is not a response', waitForAsync(() => {
const logSpy = spyOn(loggerService, 'logError');
it('logs error if error with message property is not a response', async () => {
const logSpy = vi.spyOn(loggerService, 'logError');
(service as any)
.handleErrorGetSigningKeys(
@ -209,12 +222,12 @@ describe('Signin Key Data Service', () => {
)
.subscribe({
error: () => {
expect(logSpy).toHaveBeenCalledOnceWith(
expect(logSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' },
'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 { skip } from 'rxjs/operators';
import { mockAbstractProvider, mockProvider } from '../../test/auto-mock';
import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service';
import { OidcSecurityService } from '../oidc.security.service';
import { PublicEventsService } from '../public-events/public-events.service';
import { AbstractSecurityStorage } from '../storage/abstract-security-storage';
import { DefaultSessionStorageService } from '../storage/default-sessionstorage.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockAbstractProvider, mockProvider } from '../testing/mock';
import { PlatformProvider } from '../utils/platform-provider/platform.provider';
import { CheckSessionService } from './check-session.service';
import { IFrameService } from './existing-iframe.service';
@ -67,7 +68,7 @@ describe('CheckSessionService', () => {
});
it('getOrCreateIframe calls iFrameService.addIFrameToWindowBody if no Iframe exists', () => {
spyOn(iFrameService, 'addIFrameToWindowBody').and.callThrough();
vi.spyOn(iFrameService, 'addIFrameToWindowBody')();
const result = (checkSessionService as any).getOrCreateIframe({
configId: 'configId1',
@ -89,7 +90,7 @@ describe('CheckSessionService', () => {
it('init appends iframe on body with correct values', () => {
expect((checkSessionService as any).sessionIframe).toBeFalsy();
spyOn<any>(loggerService, 'logDebug').and.callFake(() => undefined);
vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined);
(checkSessionService as any).init();
const iframe = (checkSessionService as any).getOrCreateIframe({
@ -105,29 +106,34 @@ describe('CheckSessionService', () => {
});
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' };
spyOn<any>(loggerService, 'logDebug').and.callFake(() => undefined);
spyOn(storagePersistenceService, 'read')
vi.spyOn<any>(loggerService, 'logDebug').mockImplementation(
() => undefined
);
vi.spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue({ checkSessionIframe: undefined });
.mockReturnValue({ checkSessionIframe: undefined });
(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', () => {
const spy = spyOn<any>(checkSessionService, 'pollServerSession');
const spy = vi.spyOn<any>(checkSessionService, 'pollServerSession');
const config = { clientId: 'clientId', configId: 'configId1' };
checkSessionService.start(config);
expect(spy).toHaveBeenCalledOnceWith('clientId', config);
expect(spy).toHaveBeenCalledExactlyOnceWith('clientId', config);
});
it('start() does not call pollServerSession() if scheduledHeartBeatRunning is set', () => {
const config = { configId: 'configId1' };
const spy = spyOn<any>(checkSessionService, 'pollServerSession');
const spy = vi.spyOn<any>(checkSessionService, 'pollServerSession');
(checkSessionService as any).scheduledHeartBeatRunning = (): void =>
undefined;
@ -148,10 +154,10 @@ describe('CheckSessionService', () => {
it('stopCheckingSession does nothing if scheduledHeartBeatRunning is not set', () => {
(checkSessionService as any).scheduledHeartBeatRunning = null;
const spy = spyOn<any>(checkSessionService, 'clearScheduledHeartBeat');
const spy = vi.spyOn<any>(checkSessionService, 'clearScheduledHeartBeat');
checkSessionService.stop();
expect(spy).not.toHaveBeenCalledOnceWith();
expect(spy).not.toHaveBeenCalledExactlyOnceWith();
});
describe('serverStateChanged', () => {
@ -167,7 +173,7 @@ describe('CheckSessionService', () => {
const config = { startCheckSession: true, configId: 'configId1' };
const result = checkSessionService.serverStateChanged(config);
expect(result).toBeFalse();
expect(result).toBeFalsy();
});
it('returns true if startCheckSession is configured and checkSessionReceived is true', () => {
@ -175,17 +181,17 @@ describe('CheckSessionService', () => {
const config = { startCheckSession: true, configId: 'configId1' };
const result = checkSessionService.serverStateChanged(config);
expect(result).toBeTrue();
expect(result).toBeTruthy();
});
});
describe('pollServerSession', () => {
beforeEach(() => {
spyOn<any>(checkSessionService, 'init').and.returnValue(of(undefined));
vi.spyOn<any>(checkSessionService, 'init').mockReturnValue(of(undefined));
});
it('increases outstandingMessages', () => {
spyOn<any>(checkSessionService, 'getExistingIframe').and.returnValue({
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue({
contentWindow: { postMessage: () => undefined },
});
const authWellKnownEndpoints = {
@ -193,18 +199,20 @@ describe('CheckSessionService', () => {
};
const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue(authWellKnownEndpoints)
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => authWellKnownEndpoints
)
.withArgs('session_state', config)
.and.returnValue('session_state');
spyOn(loggerService, 'logDebug').and.callFake(() => undefined);
.mockReturnValue('session_state');
vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined);
(checkSessionService as any).pollServerSession('clientId', config);
expect((checkSessionService as any).outstandingMessages).toBe(1);
});
it('logs warning if iframe does not exist', () => {
spyOn<any>(checkSessionService, 'getExistingIframe').and.returnValue(
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
null
);
const authWellKnownEndpoints = {
@ -212,77 +220,91 @@ describe('CheckSessionService', () => {
};
const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue(authWellKnownEndpoints);
const spyLogWarning = spyOn(loggerService, 'logWarning').and.callFake(
() => undefined
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => authWellKnownEndpoints
);
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);
expect(spyLogWarning).toHaveBeenCalledOnceWith(
expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith(
config,
jasmine.any(String)
expect.any(String)
);
});
it('logs warning if clientId is not set', () => {
spyOn<any>(checkSessionService, 'getExistingIframe').and.returnValue({});
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
{}
);
const authWellKnownEndpoints = {
checkSessionIframe: 'https://some-testing-url.com',
};
const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue(authWellKnownEndpoints);
const spyLogWarning = spyOn(loggerService, 'logWarning').and.callFake(
() => undefined
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => authWellKnownEndpoints
);
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);
expect(spyLogWarning).toHaveBeenCalledOnceWith(
expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith(
config,
jasmine.any(String)
expect.any(String)
);
});
it('logs debug if session_state is not set', () => {
spyOn<any>(checkSessionService, 'getExistingIframe').and.returnValue({});
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
{}
);
const authWellKnownEndpoints = {
checkSessionIframe: 'https://some-testing-url.com',
};
const config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue(authWellKnownEndpoints)
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => authWellKnownEndpoints
)
.withArgs('session_state', config)
.and.returnValue(null);
.mockReturnValue(null);
const spyLogDebug = spyOn(loggerService, 'logDebug').and.callFake(
() => undefined
);
const spyLogDebug = vi
.spyOn(loggerService, 'logDebug')
.mockImplementation(() => undefined);
(checkSessionService as any).pollServerSession('clientId', config);
expect(spyLogDebug).toHaveBeenCalledTimes(2);
});
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 config = { configId: 'configId1' };
spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config)
.and.returnValue(authWellKnownEndpoints)
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => authWellKnownEndpoints
)
.withArgs('session_state', config)
.and.returnValue('some_session_state');
const spyLogDebug = spyOn(loggerService, 'logDebug').and.callFake(
() => undefined
);
.mockReturnValue('some_session_state');
const spyLogDebug = vi
.spyOn(loggerService, 'logDebug')
.mockImplementation(() => undefined);
(checkSessionService as any).pollServerSession('clientId', config);
expect(spyLogDebug).toHaveBeenCalledTimes(2);
@ -290,7 +312,7 @@ describe('CheckSessionService', () => {
});
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 dateNow = new Date();
const lastRefresh = dateNow.setMinutes(dateNow.getMinutes() + 30);
@ -301,7 +323,7 @@ describe('CheckSessionService', () => {
serviceAsAny.init().subscribe((result: any) => {
expect(result).toBeUndefined();
});
}));
});
});
describe('isCheckSessionConfigured', () => {
@ -323,7 +345,7 @@ describe('CheckSessionService', () => {
});
describe('checkSessionChanged$', () => {
it('emits when internal event is thrown', waitForAsync(() => {
it('emits when internal event is thrown', async () => {
checkSessionService.checkSessionChanged$
.pipe(skip(1))
.subscribe((result) => {
@ -333,15 +355,15 @@ describe('CheckSessionService', () => {
const serviceAsAny = checkSessionService as any;
serviceAsAny.checkSessionChangedInternal$.next(true);
}));
});
it('emits false initially', waitForAsync(() => {
it('emits false initially', async () => {
checkSessionService.checkSessionChanged$.subscribe((result) => {
expect(result).toBe(false);
});
}));
});
it('emits false then true when emitted', waitForAsync(() => {
it('emits false then true when emitted', async () => {
const expectedResultsInOrder = [false, true];
let counter = 0;
@ -351,6 +373,6 @@ describe('CheckSessionService', () => {
});
(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 { mockProvider } from '../../test/auto-mock';
import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { UrlService } from '../utils/url/url.service';
import { RefreshSessionIframeService } from './refresh-session-iframe.service';
import { SilentRenewService } from './silent-renew.service';
@ -31,37 +32,43 @@ describe('RefreshSessionIframeService ', () => {
});
describe('refreshSessionWithIframe', () => {
it('calls sendAuthorizeRequestUsingSilentRenew with created url', waitForAsync(() => {
spyOn(urlService, 'getRefreshSessionSilentRenewUrl').and.returnValue(
it('calls sendAuthorizeRequestUsingSilentRenew with created url', async () => {
vi.spyOn(urlService, 'getRefreshSessionSilentRenewUrl').mockReturnValue(
of('a-url')
);
const sendAuthorizeRequestUsingSilentRenewSpy = spyOn(
refreshSessionIframeService as any,
'sendAuthorizeRequestUsingSilentRenew'
).and.returnValue(of(null));
const sendAuthorizeRequestUsingSilentRenewSpy = vi
.spyOn(
refreshSessionIframeService as any,
'sendAuthorizeRequestUsingSilentRenew'
)
.mockReturnValue(of(null));
const allConfigs = [{ configId: 'configId1' }];
refreshSessionIframeService
.refreshSessionWithIframe(allConfigs[0], allConfigs)
.refreshSessionWithIframe(allConfigs[0]!, allConfigs)
.subscribe(() => {
expect(
sendAuthorizeRequestUsingSilentRenewSpy
).toHaveBeenCalledOnceWith('a-url', allConfigs[0], allConfigs);
).toHaveBeenCalledExactlyOnceWith(
'a-url',
allConfigs[0]!,
allConfigs
);
});
}));
});
});
describe('initSilentRenewRequest', () => {
it('dispatches customevent to window object', waitForAsync(() => {
const dispatchEventSpy = spyOn(window, 'dispatchEvent');
it('dispatches customevent to window object', async () => {
const dispatchEventSpy = vi.spyOn(window, 'dispatchEvent');
(refreshSessionIframeService as any).initSilentRenewRequest();
expect(dispatchEventSpy).toHaveBeenCalledOnceWith(
expect(dispatchEventSpy).toHaveBeenCalledExactlyOnceWith(
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 { Observable } from 'rxjs';
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 { UrlService } from '../utils/url/url.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 { mockProvider } from '../../test/auto-mock';
import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service';
import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.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 { FlowsService } from '../flows/flows.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
import { ValidationResult } from '../validation/validation-result';
import { IFrameService } from './existing-iframe.service';
@ -63,7 +64,7 @@ describe('SilentRenewService ', () => {
describe('refreshSessionWithIFrameCompleted', () => {
it('is of type observable', () => {
expect(silentRenewService.refreshSessionWithIFrameCompleted$).toEqual(
jasmine.any(Observable)
expect.any(Observable)
);
});
});
@ -95,7 +96,7 @@ describe('SilentRenewService ', () => {
describe('getOrCreateIframe', () => {
it('returns iframe if iframe is truthy', () => {
spyOn(silentRenewService as any, 'getExistingIframe').and.returnValue({
vi.spyOn(silentRenewService as any, 'getExistingIframe').mockReturnValue({
name: 'anything',
});
@ -109,31 +110,33 @@ describe('SilentRenewService ', () => {
it('adds iframe to body if existing iframe is falsy', () => {
const config = { configId: 'configId1' };
spyOn(silentRenewService as any, 'getExistingIframe').and.returnValue(
vi.spyOn(silentRenewService as any, 'getExistingIframe').mockReturnValue(
null
);
const spy = spyOn(iFrameService, 'addIFrameToWindowBody').and.returnValue(
{ name: 'anything' } as HTMLIFrameElement
);
const spy = vi
.spyOn(iFrameService, 'addIFrameToWindowBody')
.mockReturnValue({ name: 'anything' } as HTMLIFrameElement);
const result = silentRenewService.getOrCreateIframe(config);
expect(result).toEqual({ name: 'anything' } as HTMLIFrameElement);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledOnceWith('myiFrameForSilentRenew', config);
expect(spy).toHaveBeenCalledExactlyOnceWith(
'myiFrameForSilentRenew',
config
);
});
});
describe('codeFlowCallbackSilentRenewIframe', () => {
it('calls processSilentRenewCodeFlowCallback with correct arguments', waitForAsync(() => {
it('calls processSilentRenewCodeFlowCallback with correct arguments', async () => {
const config = { configId: 'configId1' };
const allConfigs = [config];
const spy = spyOn(
flowsService,
'processSilentRenewCodeFlowCallback'
).and.returnValue(of({} as CallbackContext));
const spy = vi
.spyOn(flowsService, 'processSilentRenewCodeFlowCallback')
.mockReturnValue(of({} as CallbackContext));
const expectedContext = {
code: 'some-code',
refreshToken: '',
@ -152,32 +155,31 @@ describe('SilentRenewService ', () => {
silentRenewService
.codeFlowCallbackSilentRenewIframe([url, urlParts], config, allConfigs)
.subscribe(() => {
expect(spy).toHaveBeenCalledOnceWith(
expect(spy).toHaveBeenCalledExactlyOnceWith(
expectedContext,
config,
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 allConfigs = [config];
const spy = spyOn(
flowsService,
'processSilentRenewCodeFlowCallback'
).and.returnValue(of({} as CallbackContext));
const authStateServiceSpy = spyOn(
const spy = vi
.spyOn(flowsService, 'processSilentRenewCodeFlowCallback')
.mockReturnValue(of({} as CallbackContext));
const authStateServiceSpy = vi.spyOn(
authStateService,
'updateAndPublishAuthState'
);
const resetAuthorizationDataSpy = spyOn(
const resetAuthorizationDataSpy = vi.spyOn(
resetAuthDataService,
'resetAuthorizationData'
);
const setNonceSpy = spyOn(flowsDataService, 'setNonce');
const stopPeriodicTokenCheckSpy = spyOn(
const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce');
const stopPeriodicTokenCheckSpy = vi.spyOn(
intervalService,
'stopPeriodicTokenCheck'
);
@ -191,121 +193,116 @@ describe('SilentRenewService ', () => {
error: (error) => {
expect(error).toEqual(new Error('some_error'));
expect(spy).not.toHaveBeenCalled();
expect(authStateServiceSpy).toHaveBeenCalledOnceWith({
expect(authStateServiceSpy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false,
validationResult: ValidationResult.LoginRequired,
isRenewProcess: true,
});
expect(resetAuthorizationDataSpy).toHaveBeenCalledOnceWith(
expect(resetAuthorizationDataSpy).toHaveBeenCalledExactlyOnceWith(
config,
allConfigs
);
expect(setNonceSpy).toHaveBeenCalledOnceWith('', config);
expect(setNonceSpy).toHaveBeenCalledExactlyOnceWith('', config);
expect(stopPeriodicTokenCheckSpy).toHaveBeenCalledTimes(1);
},
});
}));
});
});
describe('silentRenewEventHandler', () => {
it('returns if no details is given', fakeAsync(() => {
const isCurrentFlowCodeFlowSpy = spyOn(
flowHelper,
'isCurrentFlowCodeFlow'
).and.returnValue(false);
it('returns if no details is given', async () => {
const isCurrentFlowCodeFlowSpy = vi
.spyOn(flowHelper, 'isCurrentFlowCodeFlow')
.mockReturnValue(false);
spyOn(
vi.spyOn(
implicitFlowCallbackService,
'authenticatedImplicitFlowCallback'
).and.returnValue(of({} as CallbackContext));
).mockReturnValue(of({} as CallbackContext));
const eventData = { detail: null } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }];
silentRenewService.silentRenewEventHandler(
eventData,
allConfigs[0],
allConfigs[0]!,
allConfigs
);
tick(1000);
await vi.advanceTimersByTimeAsync(1000);
expect(isCurrentFlowCodeFlowSpy).not.toHaveBeenCalled();
}));
});
it('calls authorizedImplicitFlowCallback if current flow is not code flow', fakeAsync(() => {
const isCurrentFlowCodeFlowSpy = spyOn(
flowHelper,
'isCurrentFlowCodeFlow'
).and.returnValue(false);
const authorizedImplicitFlowCallbackSpy = spyOn(
implicitFlowCallbackService,
'authenticatedImplicitFlowCallback'
).and.returnValue(of({} as CallbackContext));
it('calls authorizedImplicitFlowCallback if current flow is not code flow', async () => {
const isCurrentFlowCodeFlowSpy = vi
.spyOn(flowHelper, 'isCurrentFlowCodeFlow')
.mockReturnValue(false);
const authorizedImplicitFlowCallbackSpy = vi
.spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback')
.mockReturnValue(of({} as CallbackContext));
const eventData = { detail: 'detail' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }];
silentRenewService.silentRenewEventHandler(
eventData,
allConfigs[0],
allConfigs[0]!,
allConfigs
);
tick(1000);
await vi.advanceTimersByTimeAsync(1000);
expect(isCurrentFlowCodeFlowSpy).toHaveBeenCalled();
expect(authorizedImplicitFlowCallbackSpy).toHaveBeenCalledOnceWith(
allConfigs[0],
expect(authorizedImplicitFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith(
allConfigs[0]!,
allConfigs,
'detail'
);
}));
});
it('calls codeFlowCallbackSilentRenewIframe if current flow is code flow', fakeAsync(() => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
const codeFlowCallbackSilentRenewIframe = spyOn(
silentRenewService,
'codeFlowCallbackSilentRenewIframe'
).and.returnValue(of({} as CallbackContext));
it('calls codeFlowCallbackSilentRenewIframe if current flow is code flow', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const codeFlowCallbackSilentRenewIframe = vi
.spyOn(silentRenewService, 'codeFlowCallbackSilentRenewIframe')
.mockReturnValue(of({} as CallbackContext));
const eventData = { detail: 'detail?detail2' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }];
silentRenewService.silentRenewEventHandler(
eventData,
allConfigs[0],
allConfigs[0]!,
allConfigs
);
tick(1000);
expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledOnceWith(
await vi.advanceTimersByTimeAsync(1000);
expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledExactlyOnceWith(
['detail', 'detail2'],
allConfigs[0],
allConfigs[0]!,
allConfigs
);
}));
});
it('calls authorizedImplicitFlowCallback if current flow is not code flow', fakeAsync(() => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
const codeFlowCallbackSilentRenewIframe = spyOn(
silentRenewService,
'codeFlowCallbackSilentRenewIframe'
).and.returnValue(of({} as CallbackContext));
it('calls authorizedImplicitFlowCallback if current flow is not code flow', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const codeFlowCallbackSilentRenewIframe = vi
.spyOn(silentRenewService, 'codeFlowCallbackSilentRenewIframe')
.mockReturnValue(of({} as CallbackContext));
const eventData = { detail: 'detail?detail2' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }];
silentRenewService.silentRenewEventHandler(
eventData,
allConfigs[0],
allConfigs[0]!,
allConfigs
);
tick(1000);
expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledOnceWith(
await vi.advanceTimersByTimeAsync(1000);
expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledExactlyOnceWith(
['detail', 'detail2'],
allConfigs[0],
allConfigs[0]!,
allConfigs
);
}));
});
it('calls next on refreshSessionWithIFrameCompleted with callbackcontext', fakeAsync(() => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
spyOn(
it('calls next on refreshSessionWithIFrameCompleted with callbackcontext', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
vi.spyOn(
silentRenewService,
'codeFlowCallbackSilentRenewIframe'
).and.returnValue(
).mockReturnValue(
of({ refreshToken: 'callbackContext' } as CallbackContext)
);
const eventData = { detail: 'detail?detail2' } as CustomEvent;
@ -321,42 +318,42 @@ describe('SilentRenewService ', () => {
silentRenewService.silentRenewEventHandler(
eventData,
allConfigs[0],
allConfigs[0]!,
allConfigs
);
tick(1000);
}));
await vi.advanceTimersByTimeAsync(1000);
});
it('loggs and calls flowsDataService.resetSilentRenewRunning in case of an error', fakeAsync(() => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
spyOn(
it('loggs and calls flowsDataService.resetSilentRenewRunning in case of an error', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
vi.spyOn(
silentRenewService,
'codeFlowCallbackSilentRenewIframe'
).and.returnValue(throwError(() => new Error('ERROR')));
const resetSilentRenewRunningSpy = spyOn(
).mockReturnValue(throwError(() => new Error('ERROR')));
const resetSilentRenewRunningSpy = vi.spyOn(
flowsDataService,
'resetSilentRenewRunning'
);
const logErrorSpy = spyOn(loggerService, 'logError');
const logErrorSpy = vi.spyOn(loggerService, 'logError');
const allConfigs = [{ configId: 'configId1' }];
const eventData = { detail: 'detail?detail2' } as CustomEvent;
silentRenewService.silentRenewEventHandler(
eventData,
allConfigs[0],
allConfigs[0]!,
allConfigs
);
tick(1000);
await vi.advanceTimersByTimeAsync(1000);
expect(resetSilentRenewRunningSpy).toHaveBeenCalledTimes(1);
expect(logErrorSpy).toHaveBeenCalledTimes(1);
}));
});
it('calls next on refreshSessionWithIFrameCompleted with null in case of error', fakeAsync(() => {
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
spyOn(
it('calls next on refreshSessionWithIFrameCompleted with null in case of error', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
vi.spyOn(
silentRenewService,
'codeFlowCallbackSilentRenewIframe'
).and.returnValue(throwError(() => new Error('ERROR')));
).mockReturnValue(throwError(() => new Error('ERROR')));
const eventData = { detail: 'detail?detail2' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }];
@ -368,10 +365,10 @@ describe('SilentRenewService ', () => {
silentRenewService.silentRenewEventHandler(
eventData,
allConfigs[0],
allConfigs[0]!,
allConfigs
);
tick(1000);
}));
await vi.advanceTimersByTimeAsync(1000);
});
});
});

View File

@ -1,12 +1,12 @@
import { HttpParams } from '@ngify/http';
import { inject, Injectable } from 'injection-js';
import { Observable, Subject, throwError } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import { type Observable, Subject, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AuthStateService } from '../auth-state/auth-state.service';
import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service';
import { IntervalService } from '../callback/interval.service';
import { OpenIdConfiguration } from '../config/openid-configuration';
import { CallbackContext } from '../flows/callback-context';
import type { OpenIdConfiguration } from '../config/openid-configuration';
import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service';
import { FlowsService } from '../flows/flows.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-state';
export * from './auth.module';
export * from './auto-login/auto-login-all-routes.guard';
export * from './auto-login/auto-login-partial-routes.guard';
export * from './config/auth-well-known/auth-well-known-endpoints';
export * from './config/config.service';
@ -29,3 +28,5 @@ export * from './user-data/userdata-result';
export * from './validation/jwtkeys';
export * from './validation/state-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 {
HTTP_INTERCEPTORS,
HttpClient,
provideHttpClient,
withInterceptors,
withInterceptorsFromDi,
} from '@angular/common/http';
} from '@ngify/http';
import {
HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { mockProvider } from '../../test/auto-mock';
} from '@ngify/http/testing';
import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service';
import { ConfigurationService } from '../config/config.service';
import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { AuthInterceptor, authInterceptor } from './auth.interceptor';
import { ClosestMatchingRouteService } from './closest-matching-route.service';
@ -85,18 +86,22 @@ describe(`AuthHttpInterceptor`, () => {
});
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/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{
secureRoutes: [actionUrl],
configId: 'configId1',
},
]);
spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken');
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true);
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
'thisIsAToken'
);
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy();
@ -108,18 +113,22 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something');
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/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{
configId: 'configId1',
},
]);
spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken');
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true);
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
'thisIsAToken'
);
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy();
@ -131,20 +140,24 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something');
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/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{
secureRoutes: [],
configId: 'configId1',
},
]);
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true);
spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken');
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
'thisIsAToken'
);
httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy();
@ -156,19 +169,21 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something');
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/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{
secureRoutes: [],
configId: 'configId1',
},
]);
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true);
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy();
@ -180,20 +195,22 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something');
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/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{
secureRoutes: [actionUrl],
configId: 'configId1',
},
]);
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true);
spyOn(authStateService, 'getAccessToken').and.returnValue('');
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('');
httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy();
@ -205,12 +222,14 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something');
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/`;
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(false);
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
false
);
httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy();
@ -222,22 +241,24 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something');
httpTestingController.verify();
}));
});
it('should not add an Authorization header when no configured route is matching the request', waitForAsync(() => {
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true);
it('should not add an Authorization header when no configured route is matching the request', async () => {
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
const actionUrl = `https://jsonplaceholder.typicode.com/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{
secureRoutes: [actionUrl],
configId: 'configId1',
},
]);
spyOn(
vi.spyOn(
closestMatchingRouteService,
'getConfigIdForClosestMatchingRoute'
).and.returnValue({
).mockReturnValue({
matchingRoute: null,
matchingConfig: null,
});
@ -252,18 +273,22 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something');
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 actionUrl2 = `https://some-other-url.com/`;
spyOn(configurationService, 'getAllConfigurations').and.returnValue([
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ secureRoutes: [actionUrl, actionUrl2], configId: 'configId1' },
]);
spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken');
spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true);
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
'thisIsAToken'
);
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true
);
httpClient.get(actionUrl).subscribe((response) => {
expect(response).toBeTruthy();
@ -284,6 +309,6 @@ describe(`AuthHttpInterceptor`, () => {
httpRequest.flush('something');
httpRequest2.flush('something');
httpTestingController.verify();
}));
});
}
});

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
import { inject, Injectable } from 'injection-js';
import { Observable, of } from 'rxjs';
import { AuthOptions } from '../auth-options';
import { OpenIdConfiguration } from '../config/openid-configuration';
import { Injectable, inject } from 'injection-js';
import { type Observable, of } from 'rxjs';
import type { AuthOptions } from '../auth-options';
import type { OpenIdConfiguration } from '../config/openid-configuration';
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 { 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 { StandardLoginService } from './standard/standard-login.service';
@ -45,12 +45,9 @@ export class LoginService {
}
if (usePushedAuthorisationRequests) {
return this.parLoginService.loginPar(configuration, authOptions);
this.parLoginService.loginPar(configuration, authOptions);
} else {
return this.standardLoginService.loginStandard(
configuration,
authOptions
);
this.standardLoginService.loginStandard(configuration, authOptions);
}
}

View File

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

View File

@ -1,19 +1,19 @@
import { inject, Injectable } from 'injection-js';
import { Observable, of, throwError } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import { type Observable, of, throwError } from 'rxjs';
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 { 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 { RedirectService } from '../../utils/redirect/redirect.service';
import { UrlService } from '../../utils/url/url.service';
import { LoginResponse } from '../login-response';
import { PopupOptions } from '../popup/popup-options';
import { PopupResult } from '../popup/popup-result';
import type { LoginResponse } from '../login-response';
import type { PopupOptions } from '../popup/popup-options';
import type { PopupResult } from '../popup/popup-result';
import { PopUpService } from '../popup/popup.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';
@Injectable()

View File

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

View File

@ -1,15 +1,16 @@
import { TestBed } from '@/testing';
import { CommonModule } from '@angular/common';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { of } from 'rxjs';
import { mockProvider } from '../../../test/auto-mock';
import { vi } from 'vitest';
import { CheckAuthService } from '../../auth-state/check-auth.service';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
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 { PopUpLoginService } from './popup-login.service';
import { PopupResult } from './popup-result';
import type { PopupResult } from './popup-result';
import { PopUpService } from './popup.service';
describe('PopUpLoginService', () => {
@ -53,14 +54,14 @@ describe('PopUpLoginService', () => {
});
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' };
spyOn(
vi.spyOn(
responseTypValidationService,
'hasConfigValidResponseType'
).and.returnValue(false);
const loggerSpy = spyOn(loggerService, 'logError');
).mockReturnValue(false);
const loggerSpy = vi.spyOn(loggerService, 'logError');
popUpLoginService.loginWithPopUpStandard(config, [config]).subscribe({
error: (err) => {
@ -68,27 +69,27 @@ describe('PopUpLoginService', () => {
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 = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
spyOnProperty(popupService, 'result$').and.returnValue(
).mockReturnValue(of({}));
vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of({} as PopupResult)
);
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl'));
spyOn(checkAuthService, 'checkAuth').and.returnValue(
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue(
of({} as LoginResponse)
);
@ -97,74 +98,78 @@ describe('PopUpLoginService', () => {
.subscribe(() => {
expect(urlService.getAuthorizeUrl).toHaveBeenCalled();
});
}));
});
it('opens popup if everything fits', waitForAsync(() => {
it('opens popup if everything fits', async () => {
const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl'));
spyOnProperty(popupService, 'result$').and.returnValue(
).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of({} as PopupResult)
);
spyOn(checkAuthService, 'checkAuth').and.returnValue(
vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue(
of({} as LoginResponse)
);
const popupSpy = spyOn(popupService, 'openPopUp');
const popupSpy = vi.spyOn(popupService, 'openPopUp');
popUpLoginService
.loginWithPopUpStandard(config, [config])
.subscribe(() => {
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 = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl'));
spyOn(popupService, 'openPopUp');
const checkAuthSpy = spyOn(checkAuthService, 'checkAuth').and.returnValue(
of({
isAuthenticated: true,
configId: 'configId1',
idToken: '',
userData: { any: 'userData' },
accessToken: 'anyAccessToken',
})
);
).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
vi.spyOn(popupService, 'openPopUp');
const checkAuthSpy = vi
.spyOn(checkAuthService, 'checkAuth')
.mockReturnValue(
of({
isAuthenticated: true,
configId: 'configId1',
idToken: '',
userData: { any: 'userData' },
accessToken: 'anyAccessToken',
})
);
const popupResult: PopupResult = {
userClosed: false,
receivedUrl: 'someUrl',
};
spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult));
vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of(popupResult)
);
popUpLoginService
.loginWithPopUpStandard(config, [config])
.subscribe((result) => {
expect(checkAuthSpy).toHaveBeenCalledOnceWith(
expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith(
config,
[config],
'someUrl'
@ -178,31 +183,33 @@ describe('PopUpLoginService', () => {
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 = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
configId: 'configId1',
};
spyOn(
vi.spyOn(
responseTypValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl'));
spyOn(popupService, 'openPopUp');
const checkAuthSpy = spyOn(checkAuthService, 'checkAuth').and.returnValue(
of({} as LoginResponse)
);
).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
vi.spyOn(popupService, 'openPopUp');
const checkAuthSpy = vi
.spyOn(checkAuthService, 'checkAuth')
.mockReturnValue(of({} as LoginResponse));
const popupResult = { userClosed: true } as PopupResult;
spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult));
vi.spyOnProperty(popupService, 'result$').mockReturnValue(
of(popupResult)
);
popUpLoginService
.loginWithPopUpStandard(config, [config])
@ -217,6 +224,6 @@ describe('PopUpLoginService', () => {
accessToken: '',
});
});
}));
});
});
});

View File

@ -1,16 +1,16 @@
import { inject, Injectable } from 'injection-js';
import { Observable, of, throwError } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import { type Observable, of, throwError } from 'rxjs';
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 { 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 { 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 { PopupOptions } from './popup-options';
import { PopupResult } from './popup-result';
import type { PopupOptions } from './popup-options';
import type { PopupResult } from './popup-result';
import { PopUpService } from './popup.service';
@Injectable()

View File

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

View File

@ -1,6 +1,7 @@
import { TestBed } from '@angular/core/testing';
import { mockProvider } from '../../../test/auto-mock';
import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { FlowHelper } from '../../utils/flowHelper/flow-helper.service';
import { ResponseTypeValidationService } from './response-type-validation.service';
@ -32,7 +33,9 @@ describe('ResponseTypeValidationService', () => {
describe('hasConfigValidResponseType', () => {
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({
configId: 'configId1',
@ -42,8 +45,10 @@ describe('ResponseTypeValidationService', () => {
});
it('returns true if current configured flow is code flow', () => {
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(false);
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
false
);
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const result = responseTypeValidationService.hasConfigValidResponseType({
configId: 'configId1',
@ -53,8 +58,10 @@ describe('ResponseTypeValidationService', () => {
});
it('returns false if current configured flow is neither code nor implicit flow', () => {
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(false);
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false);
vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
false
);
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
const result = responseTypeValidationService.hasConfigValidResponseType({
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 { mockProvider } from '../../../test/auto-mock';
import { vi } from 'vitest';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
import { FlowsDataService } from '../../flows/flows-data.service';
import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock';
import { RedirectService } from '../../utils/redirect/redirect.service';
import { UrlService } from '../../utils/url/url.service';
import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service';
@ -51,12 +52,12 @@ describe('StandardLoginService', () => {
});
describe('loginStandard', () => {
it('does nothing if it has an invalid response type', waitForAsync(() => {
spyOn(
it('does nothing if it has an invalid response type', async () => {
vi.spyOn(
responseTypeValidationService,
'hasConfigValidResponseType'
).and.returnValue(false);
const loggerSpy = spyOn(loggerService, 'logError');
).mockReturnValue(false);
const loggerSpy = vi.spyOn(loggerService, 'logError');
const result = standardLoginService.loginStandard({
configId: 'configId1',
@ -64,95 +65,92 @@ describe('StandardLoginService', () => {
expect(result).toBeUndefined();
expect(loggerSpy).toHaveBeenCalled();
}));
});
it('calls flowsDataService.setCodeFlowInProgress() if everything fits', waitForAsync(() => {
it('calls flowsDataService.setCodeFlowInProgress() if everything fits', async () => {
const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypeValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl'));
const flowsDataSpy = spyOn(flowsDataService, 'setCodeFlowInProgress');
).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const flowsDataSpy = vi.spyOn(flowsDataService, 'setCodeFlowInProgress');
const result = standardLoginService.loginStandard(config);
expect(result).toBeUndefined();
expect(flowsDataSpy).toHaveBeenCalled();
}));
});
it('calls urlService.getAuthorizeUrl() if everything fits', waitForAsync(() => {
it('calls urlService.getAuthorizeUrl() if everything fits', async () => {
const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypeValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl'));
).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const result = standardLoginService.loginStandard(config);
expect(result).toBeUndefined();
}));
});
it('redirects to URL with no URL handler', fakeAsync(() => {
it('redirects to URL with no URL handler', async () => {
const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypeValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl'));
const redirectSpy = spyOn(
redirectService,
'redirectTo'
).and.callThrough();
).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const redirectSpy = vi.spyOn(redirectService, 'redirectTo')();
standardLoginService.loginStandard(config);
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 = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypeValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl'));
const redirectSpy = spyOn(redirectService, 'redirectTo').and.callFake(
() => undefined
);
).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const redirectSpy = vi
.spyOn(redirectService, 'redirectTo')
.mockImplementation(() => undefined);
const spy = jasmine.createSpy();
const urlHandler = (url: any): void => {
spy(url);
@ -160,94 +158,96 @@ describe('StandardLoginService', () => {
standardLoginService.loginStandard(config, { urlHandler });
tick();
expect(spy).toHaveBeenCalledOnceWith('someUrl');
expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl');
expect(redirectSpy).not.toHaveBeenCalled();
}));
});
it('calls resetSilentRenewRunning', fakeAsync(() => {
it('calls resetSilentRenewRunning', async () => {
const config = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypeValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl'));
const flowsDataSpy = spyOn(flowsDataService, 'resetSilentRenewRunning');
).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const flowsDataSpy = vi.spyOn(
flowsDataService,
'resetSilentRenewRunning'
);
standardLoginService.loginStandard(config, {});
tick();
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 = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypeValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
const getAuthorizeUrlSpy = spyOn(
urlService,
'getAuthorizeUrl'
).and.returnValue(of('someUrl'));
const redirectSpy = spyOn(redirectService, 'redirectTo').and.callFake(
() => undefined
);
).mockReturnValue(of({}));
const getAuthorizeUrlSpy = vi
.spyOn(urlService, 'getAuthorizeUrl')
.mockReturnValue(of('someUrl'));
const redirectSpy = vi
.spyOn(redirectService, 'redirectTo')
.mockImplementation(() => undefined);
standardLoginService.loginStandard(config, {
customParams: { to: 'add', as: 'well' },
});
tick();
expect(redirectSpy).toHaveBeenCalledOnceWith('someUrl');
expect(getAuthorizeUrlSpy).toHaveBeenCalledOnceWith(config, {
expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl');
expect(getAuthorizeUrlSpy).toHaveBeenCalledExactlyOnceWith(config, {
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 = {
authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue',
};
spyOn(
vi.spyOn(
responseTypeValidationService,
'hasConfigValidResponseType'
).and.returnValue(true);
spyOn(
).mockReturnValue(true);
vi.spyOn(
authWellKnownService,
'queryAndStoreAuthWellKnownEndPoints'
).and.returnValue(of({}));
const loggerSpy = spyOn(loggerService, 'logError');
).mockReturnValue(of({}));
const loggerSpy = vi.spyOn(loggerService, 'logError');
spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of(''));
const redirectSpy = spyOn(redirectService, 'redirectTo').and.callFake(
() => undefined
);
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of(''));
const redirectSpy = vi
.spyOn(redirectService, 'redirectTo')
.mockImplementation(() => undefined);
standardLoginService.loginStandard(config);
tick();
expect(loggerSpy).toHaveBeenCalledOnceWith(
expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
config,
'Could not create URL',
''
);
expect(redirectSpy).not.toHaveBeenCalled();
}));
});
});
});

View File

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

View File

@ -1,10 +1,10 @@
import { HttpHeaders } from '@ngify/http';
import { inject, Injectable } from 'injection-js';
import { Observable, of, throwError } from 'rxjs';
import { Injectable, inject } from 'injection-js';
import { type Observable, of, throwError } from 'rxjs';
import { catchError, concatMap, retry, switchMap } from 'rxjs/operators';
import { DataService } from '../api/data.service';
import { LogoutAuthOptions } from '../auth-options';
import { OpenIdConfiguration } from '../config/openid-configuration';
import type { LogoutAuthOptions } from '../auth-options';
import type { OpenIdConfiguration } from '../config/openid-configuration';
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { CheckSessionService } from '../iframe/check-session.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 { Observable } from 'rxjs';
import { concatMap, map } from 'rxjs/operators';
import { AuthOptions, LogoutAuthOptions } from './auth-options';
import { AuthenticatedResult } from './auth-state/auth-result';
import { Injectable, inject } from 'injection-js';
import { toSignal } from 'injection-js/rxjs-interop';
import type { Observable } from 'rxjs';
import { concatMap, map, shareReplay } from 'rxjs/operators';
import type { AuthOptions, LogoutAuthOptions } from './auth-options';
import type { AuthenticatedResult } from './auth-state/auth-result';
import { AuthStateService } from './auth-state/auth-state.service';
import { CheckAuthService } from './auth-state/check-auth.service';
import { CallbackService } from './callback/callback.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 { ConfigurationService } from './config/config.service';
import { OpenIdConfiguration } from './config/openid-configuration';
import { AuthResult } from './flows/callback-context';
import type { OpenIdConfiguration } from './config/openid-configuration';
import type { AuthResult } from './flows/callback-context';
import { FlowsDataService } from './flows/flows-data.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 { PopupOptions } from './login/popup/popup-options';
import type { PopupOptions } from './login/popup/popup-options';
import { LogoffRevocationService } from './logoff-revoke/logoff-revocation.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 { UrlService } from './utils/url/url.service';
import { toSignal } from 'injection-js/rxjs-interop';
@Injectable()
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 authOptions The custom options for the the authentication request.
*/
authorize(configId?: string, authOptions?: AuthOptions): void {
this.configurationService
authorize(configId?: string, authOptions?: AuthOptions): Observable<void> {
const result$ = this.configurationService
.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
*/
logoffLocal(configId?: string): void {
this.configurationService
logoffLocal(configId?: string): Observable<void> {
const result$ = this.configurationService
.getOpenIDConfigurations(configId)
.subscribe(({ allConfigs, currentConfig }) =>
this.logoffRevocationService.logoffLocal(currentConfig, allConfigs)
.pipe(
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.
* Use this method if you have _multiple_ configs enabled.
*/
logoffLocalMultiple(): void {
this.configurationService
.getOpenIDConfigurations()
.subscribe(({ allConfigs }) =>
logoffLocalMultiple(): Observable<void> {
const result$ = this.configurationService.getOpenIDConfigurations().pipe(
map(({ 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, waitForAsync } from '@angular/core/testing';
import { TestBed, createSpyObj } from '@/testing';
import { mockProvider } from '@/testing/mock';
import { APP_INITIALIZER } from 'oidc-client-rx';
import { of } from 'rxjs';
import { mockProvider } from '../test/auto-mock';
import { PASSED_CONFIG } from './auth-config';
import { ConfigurationService } from './config/config.service';
import {
@ -14,14 +14,14 @@ import { provideAuth, withAppInitializerAuthCheck } from './provide-auth';
describe('provideAuth', () => {
describe('APP_CONFIG', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
provideAuth({ config: { authority: 'something' } }),
mockProvider(ConfigurationService),
],
}).compileComponents();
}));
});
it('should provide config', () => {
const config = TestBed.inject(PASSED_CONFIG);
@ -37,8 +37,8 @@ describe('provideAuth', () => {
});
describe('StsConfigHttpLoader', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
provideAuth({
loader: {
@ -49,7 +49,7 @@ describe('provideAuth', () => {
mockProvider(ConfigurationService),
],
}).compileComponents();
}));
});
it('should create StsConfigStaticLoader if config is passed', () => {
const configLoader = TestBed.inject(StsConfigLoader);
@ -59,14 +59,14 @@ describe('provideAuth', () => {
});
describe('features', () => {
let oidcSecurityServiceMock: jasmine.SpyObj<OidcSecurityService>;
let oidcSecurityServiceMock: OidcSecurityService;
beforeEach(waitForAsync(() => {
oidcSecurityServiceMock = jasmine.createSpyObj<OidcSecurityService>(
beforeEach(async () => {
oidcSecurityServiceMock = createSpyObj<OidcSecurityService>(
'OidcSecurityService',
['checkAuthMultiple']
);
TestBed.configureTestingModule({
await TestBed.configureTestingModule({
providers: [
provideAuth(
{ config: { authority: 'something' } },
@ -79,14 +79,15 @@ describe('provideAuth', () => {
},
],
}).compileComponents();
}));
});
it('should provide APP_INITIALIZER config', () => {
const config = TestBed.inject(APP_INITIALIZER);
expect(config.length)
.withContext('Expected an APP_INITIALIZER to be registered')
.toBe(1);
expect(
config.length,
'Expected an APP_INITIALIZER to be registered'
).toBe(1);
expect(oidcSecurityServiceMock.checkAuthMultiple).toHaveBeenCalledTimes(
1
);

View File

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

View File

@ -1,17 +1,18 @@
// biome-ignore lint/nursery/noEnum: <explanation>
export enum EventTypes {
/**
* This only works in the AppModule Constructor
*/
ConfigLoaded,
CheckingAuth,
CheckingAuthFinished,
CheckingAuthFinishedWithError,
ConfigLoadingFailed,
CheckSessionReceived,
UserDataChanged,
NewAuthenticationResult,
TokenExpired,
IdTokenExpired,
SilentRenewStarted,
SilentRenewFailed,
ConfigLoaded = 0,
CheckingAuth = 1,
CheckingAuthFinished = 2,
CheckingAuthFinishedWithError = 3,
ConfigLoadingFailed = 4,
CheckSessionReceived = 5,
UserDataChanged = 6,
NewAuthenticationResult = 7,
TokenExpired = 8,
IdTokenExpired = 9,
SilentRenewStarted = 10,
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 { vi } from 'vitest';
import { EventTypes } from './event-types';
import { PublicEventsService } from './public-events.service';
@ -20,7 +21,7 @@ describe('Events Service', () => {
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) => {
expect(firedEvent).toBeTruthy();
expect(firedEvent).toEqual({
@ -29,9 +30,9 @@ describe('Events Service', () => {
});
});
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');
eventsService.registerForEvents().subscribe((firedEvent) => {
@ -50,9 +51,9 @@ describe('Events Service', () => {
type: EventTypes.ConfigLoaded,
value: { myKey: 'myValue2' },
});
}));
});
it('registering to single event with multiple emit works', waitForAsync(() => {
it('registering to single event with multiple emit works', async () => {
eventsService
.registerForEvents()
.pipe(filter((x) => x.type === EventTypes.ConfigLoaded))
@ -65,5 +66,5 @@ describe('Events Service', () => {
});
eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' });
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 { mockClass, mockProvider } from '../../test/auto-mock';
import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service';
import { mockClass, mockProvider } from '../testing/mock';
import { AbstractSecurityStorage } from './abstract-security-storage';
import { BrowserStorageService } from './browser-storage.service';
import { DefaultSessionStorageService } from './default-sessionstorage.service';
@ -34,7 +35,7 @@ describe('BrowserStorageService', () => {
it('returns null if there is no storage', () => {
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();
});
@ -42,7 +43,7 @@ describe('BrowserStorageService', () => {
it('returns null if getItem returns null', () => {
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);
@ -52,10 +53,10 @@ describe('BrowserStorageService', () => {
it('returns the item if getItem returns an item', () => {
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"}`;
spyOn(abstractSecurityStorage, 'read').and.returnValue(returnValue);
vi.spyOn(abstractSecurityStorage, 'read').mockReturnValue(returnValue);
const result = service.read('anything', config);
expect(result).toEqual(JSON.parse(returnValue));
@ -66,24 +67,21 @@ describe('BrowserStorageService', () => {
it('returns false if there is no storage', () => {
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', () => {
const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(true);
const writeSpy = spyOn(
abstractSecurityStorage,
'write'
).and.callThrough();
vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')();
const result = service.write({ anyKey: 'anyvalue' }, config);
expect(result).toBe(true);
expect(writeSpy).toHaveBeenCalledOnceWith(
expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
'configId1',
JSON.stringify({ anyKey: 'anyvalue' })
);
@ -92,18 +90,15 @@ describe('BrowserStorageService', () => {
it('writes null if item is falsy', () => {
const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(true);
vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const writeSpy = spyOn(
abstractSecurityStorage,
'write'
).and.callThrough();
const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')();
const somethingFalsy = '';
const result = service.write(somethingFalsy, config);
expect(result).toBe(true);
expect(writeSpy).toHaveBeenCalledOnceWith(
expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
'configId1',
JSON.stringify(null)
);
@ -114,41 +109,35 @@ describe('BrowserStorageService', () => {
it('returns false if there is no storage', () => {
const config = { configId: 'configId1' };
spyOn(service as any, 'hasStorage').and.returnValue(false);
expect(service.remove('anything', config)).toBeFalse();
vi.spyOn(service as any, 'hasStorage').mockReturnValue(false);
expect(service.remove('anything', config)).toBeFalsy();
});
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 setItemSpy = spyOn(
abstractSecurityStorage,
'remove'
).and.callThrough();
const setItemSpy = vi.spyOn(abstractSecurityStorage, 'remove')();
const result = service.remove('anyKey', config);
expect(result).toBe(true);
expect(setItemSpy).toHaveBeenCalledOnceWith('anyKey');
expect(setItemSpy).toHaveBeenCalledExactlyOnceWith('anyKey');
});
});
describe('clear', () => {
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' };
expect(service.clear(config)).toBeFalse();
expect(service.clear(config)).toBeFalsy();
});
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(
abstractSecurityStorage,
'clear'
).and.callThrough();
const setItemSpy = vi.spyOn(abstractSecurityStorage, 'clear')();
const config = { configId: 'configId1' };
const result = service.clear(config);
@ -161,7 +150,7 @@ describe('BrowserStorageService', () => {
describe('hasStorage', () => {
it('returns false if there is no storage', () => {
(Storage as any) = undefined;
expect((service as any).hasStorage()).toBeFalse();
expect((service as any).hasStorage()).toBeFalsy();
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';
describe('DefaultLocalStorageService', () => {
@ -20,37 +21,37 @@ describe('DefaultLocalStorageService', () => {
describe('read', () => {
it('should call localstorage.getItem', () => {
const spy = spyOn(localStorage, 'getItem');
const spy = vi.spyOn(localStorage, 'getItem');
service.read('henlo');
expect(spy).toHaveBeenCalledOnceWith('henlo');
expect(spy).toHaveBeenCalledExactlyOnceWith('henlo');
});
});
describe('write', () => {
it('should call localstorage.setItem', () => {
const spy = spyOn(localStorage, 'setItem');
const spy = vi.spyOn(localStorage, 'setItem');
service.write('henlo', 'furiend');
expect(spy).toHaveBeenCalledOnceWith('henlo', 'furiend');
expect(spy).toHaveBeenCalledExactlyOnceWith('henlo', 'furiend');
});
});
describe('remove', () => {
it('should call localstorage.removeItem', () => {
const spy = spyOn(localStorage, 'removeItem');
const spy = vi.spyOn(localStorage, 'removeItem');
service.remove('henlo');
expect(spy).toHaveBeenCalledOnceWith('henlo');
expect(spy).toHaveBeenCalledExactlyOnceWith('henlo');
});
});
describe('clear', () => {
it('should call localstorage.clear', () => {
const spy = spyOn(localStorage, 'clear');
const spy = vi.spyOn(localStorage, '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';
describe('DefaultSessionStorageService', () => {
@ -20,37 +21,37 @@ describe('DefaultSessionStorageService', () => {
describe('read', () => {
it('should call sessionstorage.getItem', () => {
const spy = spyOn(sessionStorage, 'getItem');
const spy = vi.spyOn(sessionStorage, 'getItem');
service.read('henlo');
expect(spy).toHaveBeenCalledOnceWith('henlo');
expect(spy).toHaveBeenCalledExactlyOnceWith('henlo');
});
});
describe('write', () => {
it('should call sessionstorage.setItem', () => {
const spy = spyOn(sessionStorage, 'setItem');
const spy = vi.spyOn(sessionStorage, 'setItem');
service.write('henlo', 'furiend');
expect(spy).toHaveBeenCalledOnceWith('henlo', 'furiend');
expect(spy).toHaveBeenCalledExactlyOnceWith('henlo', 'furiend');
});
});
describe('remove', () => {
it('should call sessionstorage.removeItem', () => {
const spy = spyOn(sessionStorage, 'removeItem');
const spy = vi.spyOn(sessionStorage, 'removeItem');
service.remove('henlo');
expect(spy).toHaveBeenCalledOnceWith('henlo');
expect(spy).toHaveBeenCalledExactlyOnceWith('henlo');
});
});
describe('clear', () => {
it('should call sessionstorage.clear', () => {
const spy = spyOn(sessionStorage, 'clear');
const spy = vi.spyOn(sessionStorage, 'clear');
service.clear();

View File

@ -1,5 +1,6 @@
import { TestBed } from '@angular/core/testing';
import { mockProvider } from '../../test/auto-mock';
import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { mockProvider } from '../testing/mock';
import { BrowserStorageService } from './browser-storage.service';
import { StoragePersistenceService } from './storage-persistence.service';
@ -25,16 +26,16 @@ describe('Storage Persistence Service', () => {
describe('read', () => {
it('reads from oidcSecurityStorage with configId', () => {
const config = { configId: 'configId1' };
const spy = spyOn(securityStorage, 'read');
const spy = vi.spyOn(securityStorage, 'read');
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', () => {
const config = { configId: 'configId1' };
spyOn(securityStorage, 'read').and.returnValue({ some: 'thing' });
vi.spyOn(securityStorage, 'read').mockReturnValue({ some: 'thing' });
const result = service.read('authNonce', config);
expect(result).toBeUndefined();
@ -44,13 +45,13 @@ describe('Storage Persistence Service', () => {
describe('write', () => {
it('writes to oidcSecurityStorage with correct key and correct config', () => {
const config = { configId: 'configId1' };
const readSpy = spyOn(securityStorage, 'read');
const writeSpy = spyOn(securityStorage, 'write');
const readSpy = vi.spyOn(securityStorage, 'read');
const writeSpy = vi.spyOn(securityStorage, 'write');
service.write('authNonce', 'anyValue', config);
expect(readSpy).toHaveBeenCalledOnceWith('authNonce', config);
expect(writeSpy).toHaveBeenCalledOnceWith(
expect(readSpy).toHaveBeenCalledExactlyOnceWith('authNonce', config);
expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
{ authNonce: 'anyValue' },
config
);
@ -60,32 +61,32 @@ describe('Storage Persistence Service', () => {
describe('remove', () => {
it('should remove key from config', () => {
const config = { configId: 'configId1' };
const readSpy = spyOn(securityStorage, 'read').and.returnValue({
const readSpy = vi.spyOn(securityStorage, 'read').mockReturnValue({
authNonce: 'anyValue',
});
const writeSpy = spyOn(securityStorage, 'write');
const writeSpy = vi.spyOn(securityStorage, 'write');
service.remove('authNonce', config);
expect(readSpy).toHaveBeenCalledOnceWith('authNonce', config);
expect(writeSpy).toHaveBeenCalledOnceWith({}, config);
expect(readSpy).toHaveBeenCalledExactlyOnceWith('authNonce', config);
expect(writeSpy).toHaveBeenCalledExactlyOnceWith({}, config);
});
it('does not crash when read with configId returns null', () => {
const config = { configId: 'configId1' };
const readSpy = spyOn(securityStorage, 'read').and.returnValue(null);
const writeSpy = spyOn(securityStorage, 'write');
const readSpy = vi.spyOn(securityStorage, 'read').mockReturnValue(null);
const writeSpy = vi.spyOn(securityStorage, 'write');
service.remove('authNonce', config);
expect(readSpy).toHaveBeenCalledOnceWith('authNonce', config);
expect(writeSpy).toHaveBeenCalledOnceWith({}, config);
expect(readSpy).toHaveBeenCalledExactlyOnceWith('authNonce', config);
expect(writeSpy).toHaveBeenCalledExactlyOnceWith({}, config);
});
});
describe('clear', () => {
it('should call oidcSecurityStorage.clear()', () => {
const clearSpy = spyOn(securityStorage, 'clear');
const clearSpy = vi.spyOn(securityStorage, 'clear');
service.clear({});
@ -96,49 +97,58 @@ describe('Storage Persistence Service', () => {
describe('resetStorageFlowData', () => {
it('resets the correct values', () => {
const config = { configId: 'configId1' };
const spy = spyOn(service, 'remove');
const spy = vi.spyOn(service, 'remove');
service.resetStorageFlowData(config);
expect(spy).toHaveBeenCalledTimes(10);
expect(spy.calls.argsFor(0)).toEqual(['session_state', config]);
expect(spy.calls.argsFor(1)).toEqual([
expect(vi.mocked(spy).mock.calls[0]).toEqual(['session_state', config]);
expect(vi.mocked(spy).mock.calls[1]).toEqual([
'storageSilentRenewRunning',
config,
]);
expect(spy.calls.argsFor(2)).toEqual([
expect(vi.mocked(spy).mock.calls[2]).toEqual([
'storageCodeFlowInProgress',
config,
]);
expect(spy.calls.argsFor(3)).toEqual(['codeVerifier', config]);
expect(spy.calls.argsFor(4)).toEqual(['userData', config]);
expect(spy.calls.argsFor(5)).toEqual([
expect(vi.mocked(spy).mock.calls[3]).toEqual(['codeVerifier', config]);
expect(vi.mocked(spy).mock.calls[4]).toEqual(['userData', config]);
expect(vi.mocked(spy).mock.calls[5]).toEqual([
'storageCustomParamsAuthRequest',
config,
]);
expect(spy.calls.argsFor(6)).toEqual(['access_token_expires_at', config]);
expect(spy.calls.argsFor(7)).toEqual([
expect(vi.mocked(spy).mock.calls[6]).toEqual([
'access_token_expires_at',
config,
]);
expect(vi.mocked(spy).mock.calls[7]).toEqual([
'storageCustomParamsRefresh',
config,
]);
expect(spy.calls.argsFor(8)).toEqual([
expect(vi.mocked(spy).mock.calls[8]).toEqual([
'storageCustomParamsEndSession',
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', () => {
it('resets the correct values', () => {
const config = { configId: 'configId1' };
const spy = spyOn(service, 'remove');
const spy = vi.spyOn(service, 'remove');
service.resetAuthStateInStorage(config);
expect(spy.calls.argsFor(0)).toEqual(['authzData', config]);
expect(spy.calls.argsFor(1)).toEqual(['reusable_refresh_token', config]);
expect(spy.calls.argsFor(2)).toEqual(['authnResult', config]);
expect(vi.mocked(spy).mock.calls[0]).toEqual(['authzData', config]);
expect(vi.mocked(spy).mock.calls[1]).toEqual([
'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', () => {
const returnValue = { authzData: 'someValue' };
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);
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', () => {
const spy = spyOn(securityStorage, 'read').and.returnValue(null);
const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null);
const config = { configId: 'configId1' };
const result = service.getAccessToken(config);
expect(result).toBeFalsy();
expect(spy).toHaveBeenCalledOnceWith('authzData', config);
expect(spy).toHaveBeenCalledExactlyOnceWith('authzData', config);
});
});
describe('getIdToken', () => {
it('get calls oidcSecurityStorage.read with correct key and returns the value', () => {
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 result = service.getIdToken(config);
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', () => {
const spy = spyOn(securityStorage, 'read').and.returnValue(null);
const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null);
const config = { configId: 'configId1' };
const result = service.getIdToken(config);
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', () => {
const returnValue = { authnResult: { id_token: 'someValue' } };
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);
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', () => {
const spy = spyOn(securityStorage, 'read').and.returnValue(null);
const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null);
const config = { configId: 'configId1' };
const result = service.getAuthenticationResult(config);
expect(result).toBeFalsy();
expect(spy).toHaveBeenCalledOnceWith('authnResult', config);
expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config);
});
});
describe('getRefreshToken', () => {
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 spy = spyOn(securityStorage, 'read').and.returnValue(returnValue);
const spy = vi
.spyOn(securityStorage, 'read')
.mockReturnValue(returnValue);
const config = { configId: 'configId1' };
const result = service.getRefreshToken(config);
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)', () => {
@ -222,12 +240,12 @@ describe('Storage Persistence Service', () => {
configId: 'configId1',
allowUnsafeReuseRefreshToken: true,
};
const spy = spyOn(securityStorage, 'read');
const spy = vi.spyOn(securityStorage, 'read');
spy
.withArgs('reusable_refresh_token', config)
.and.returnValue(returnValue);
spy.withArgs('authnResult', config).and.returnValue(undefined);
.mockReturnValue(returnValue);
spy.withArgs('authnResult', config).mockReturnValue(undefined);
const result = service.getRefreshToken(config);
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', () => {
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 result = service.getRefreshToken(config);
expect(result).toBeUndefined();
expect(spy).toHaveBeenCalledOnceWith('authnResult', config);
expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config);
});
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 result = service.getRefreshToken(config);
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 { AuthResult } from '../flows/callback-context';
import { OpenIdConfiguration } from '../config/openid-configuration';
import { inject } from 'injection-js';
import type { OpenIdConfiguration } from '../config/openid-configuration';
import type { AuthResult } from '../flows/callback-context';
import { BrowserStorageService } from './browser-storage.service';
export type StorageKeys =
@ -24,7 +24,6 @@ export type StorageKeys =
| 'jwtKeys'
| 'popupauth';
export class StoragePersistenceService {
private readonly browserStorageService = inject(BrowserStorageService);

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