Compare commits
No commits in common. "f00c1d1aef1c1d271069fbb06a6375290e0253ad" and "da0d9855da4490c673d2336e6eed2ca004c9b8ae" have entirely different histories.
f00c1d1aef
...
da0d9855da
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
2
.gitignore
vendored
2
.gitignore
vendored
@ -32,6 +32,7 @@ speed-measure-plugin*.json
|
|||||||
.history/*
|
.history/*
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
|
/.angular/cache
|
||||||
/.sass-cache
|
/.sass-cache
|
||||||
/connect.lock
|
/connect.lock
|
||||||
/coverage
|
/coverage
|
||||||
@ -54,4 +55,3 @@ debug.log
|
|||||||
/playwright-report/
|
/playwright-report/
|
||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
/.vitest
|
|
||||||
|
|||||||
210
README.md
210
README.md
@ -1,17 +1,207 @@
|
|||||||
<h1 align="center">
|
# Angular Lib for OpenID Connect & OAuth2
|
||||||
<img src="./assets/logo-512.png" height="150" alt="OUTPOSTS">
|
|
||||||
<div style="color: #232848; font-weight: 700;">OIDC-CLIENT-RX</div>
|
|
||||||
<div align="center">
|
|
||||||
<img src="https://img.shields.io/badge/status-work--in--progress-blue" alt="status-badge" />
|
|
||||||
</div>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p align="center">ReactiveX enhanced OIDC and OAuth2 protocol support for browser-based JavaScript applications.</p>
|
 [](https://www.npmjs.com/package/oidc-client-rx) [](https://www.npmjs.com/package/oidc-client-rx) [](https://www.npmjs.com/package/oidc-client-rx) [](https://github.com/prettier/prettier) [](https://coveralls.io/github/damienbod/oidc-client-rx?branch=main)
|
||||||
|
|
||||||
## Quick Start
|
<p align="center">
|
||||||
|
<a href="https://oidc-client-rx.com/"><img src="https://raw.githubusercontent.com/damienbod/oidc-client-rx/main/.github/angular-auth-logo.png" alt="" width="350" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
@TODO Coming Soon
|
Secure your Angular app using the latest standards for OpenID Connect & OAuth2. Provides support for token refresh, all modern OIDC Identity Providers and more.
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
This library is <a href="http://openid.net/certification/#RPs">certified</a> by OpenID Foundation. (RP Implicit and Config RP)
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="http://openid.net/certification/#RPs"><img src="https://damienbod.files.wordpress.com/2017/06/oid-l-certification-mark-l-rgb-150dpi-90mm.png" alt="" width="400" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- [Code samples](https://oidc-client-rx.com/docs/samples/) for most of the common use cases
|
||||||
|
- Supports schematics via `ng add` support
|
||||||
|
- Supports all modern OIDC identity providers
|
||||||
|
- Supports OpenID Connect Code Flow with PKCE
|
||||||
|
- Supports Code Flow PKCE with Refresh tokens
|
||||||
|
- [Supports OpenID Connect Implicit Flow](http://openid.net/specs/openid-connect-implicit-1_0.html)
|
||||||
|
- [Supports OpenID Connect Session Management 1.0](http://openid.net/specs/openid-connect-session-1_0.html)
|
||||||
|
- [Supports RFC7009 - OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009)
|
||||||
|
- [Supports RFC7636 - Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636)
|
||||||
|
- [Supports OAuth 2.0 Pushed authorisation requests (PAR) draft](https://tools.ietf.org/html/draft-ietf-oauth-par-06)
|
||||||
|
- Semantic releases
|
||||||
|
- Github actions
|
||||||
|
- Modern coding guidelines with prettier, husky
|
||||||
|
- Up to date documentation
|
||||||
|
- Implements OIDC validation as specified, complete client side validation for REQUIRED features
|
||||||
|
- Supports authentication using redirect or popup
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Ng Add
|
||||||
|
|
||||||
|
You can use the schematics and `ng add` the library.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ng add oidc-client-rx
|
||||||
|
```
|
||||||
|
|
||||||
|
And answer the questions. A module will be created which encapsulates your configuration.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Npm / Yarn
|
||||||
|
|
||||||
|
Navigate to the level of your `package.json` and type
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install oidc-client-rx
|
||||||
|
```
|
||||||
|
|
||||||
|
or with yarn
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn add oidc-client-rx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[Read the docs here](https://oidc-client-rx.com/)
|
||||||
|
|
||||||
|
## Samples
|
||||||
|
|
||||||
|
[Explore the Samples here](https://oidc-client-rx.com/docs/samples/)
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
For the example of the Code Flow. For further examples please check the [Samples](https://oidc-client-rx.com/docs/samples/) Section.
|
||||||
|
|
||||||
|
> If you have done the installation with the schematics, these modules and files should be available already!
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Import the `AuthModule` in your module.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { AuthModule, LogLevel } from 'oidc-client-rx';
|
||||||
|
// ...
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
// ...
|
||||||
|
imports: [
|
||||||
|
// ...
|
||||||
|
AuthModule.forRoot({
|
||||||
|
config: {
|
||||||
|
authority: '<your authority address here>',
|
||||||
|
redirectUrl: window.location.origin,
|
||||||
|
postLogoutRedirectUri: window.location.origin,
|
||||||
|
clientId: '<your clientId>',
|
||||||
|
scope: 'openid profile email offline_access',
|
||||||
|
responseType: 'code',
|
||||||
|
silentRenew: true,
|
||||||
|
useRefreshToken: true,
|
||||||
|
logLevel: LogLevel.Debug,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
|
```
|
||||||
|
|
||||||
|
And call the method `checkAuth()` from your `app.component.ts`. The method `checkAuth()` is needed to process the redirect from your Security Token Service and set the correct states. This method must be used to ensure the correct functioning of the library.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
|
import { OidcSecurityService } from 'oidc-client-rx';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
/*...*/
|
||||||
|
})
|
||||||
|
export class AppComponent implements OnInit {
|
||||||
|
private readonly oidcSecurityService = inject(OidcSecurityService);
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.oidcSecurityService
|
||||||
|
.checkAuth()
|
||||||
|
.subscribe((loginResponse: LoginResponse) => {
|
||||||
|
const { isAuthenticated, userData, accessToken, idToken, configId } =
|
||||||
|
loginResponse;
|
||||||
|
|
||||||
|
/*...*/
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
this.oidcSecurityService.authorize();
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.oidcSecurityService
|
||||||
|
.logoff()
|
||||||
|
.subscribe((result) => console.log(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the access token
|
||||||
|
|
||||||
|
You can get the access token by calling the method `getAccessToken()` on the `OidcSecurityService`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const token = this.oidcSecurityService.getAccessToken().subscribe(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
And then you can use it in the HttpHeaders
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
|
|
||||||
|
const token = this.oidcSecurityServices.getAccessToken().subscribe((token) => {
|
||||||
|
const httpOptions = {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
Authorization: 'Bearer ' + token,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use the built in interceptor to add the accesstokens to your request
|
||||||
|
|
||||||
|
```ts
|
||||||
|
AuthModule.forRoot({
|
||||||
|
config: {
|
||||||
|
// ...
|
||||||
|
secureRoutes: ['https://my-secure-url.com/', 'https://my-second-secure-url.com/'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
providers: [
|
||||||
|
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
## Versions
|
||||||
|
|
||||||
|
Current Version is Version 19.x
|
||||||
|
|
||||||
|
- [Info about Version 18](https://github.com/damienbod/oidc-client-rx/tree/version-18)
|
||||||
|
- [Info about Version 17](https://github.com/damienbod/oidc-client-rx/tree/version-17)
|
||||||
|
- [Info about Version 16](https://github.com/damienbod/oidc-client-rx/tree/version-16)
|
||||||
|
- [Info about Version 15](https://github.com/damienbod/oidc-client-rx/tree/version-15)
|
||||||
|
- [Info about Version 14](https://github.com/damienbod/oidc-client-rx/tree/version-14)
|
||||||
|
- [Info about Version 13](https://github.com/damienbod/oidc-client-rx/tree/version-13)
|
||||||
|
- [Info about Version 12](https://github.com/damienbod/oidc-client-rx/tree/version-12)
|
||||||
|
- [Info about Version 11](https://github.com/damienbod/oidc-client-rx/tree/version-11)
|
||||||
|
- [Info about Version 10](https://github.com/damienbod/oidc-client-rx/tree/version-10)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT](https://choosealicense.com/licenses/mit/)
|
[MIT](https://choosealicense.com/licenses/mit/)
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
- [@DamienBod](https://www.github.com/damienbod)
|
||||||
|
- [@FabianGosebrink](https://www.github.com/FabianGosebrink)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB |
25
biome.json
Normal file
25
biome.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||||
|
"extends": ["ultracite"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"style": {
|
||||||
|
"noNonNullAssertion": "off"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noExplicitAny": "off"
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"noUnusedImports": {
|
||||||
|
"fix": "none",
|
||||||
|
"level": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignore": [
|
||||||
|
".vscode/*.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
41
biome.jsonc
41
biome.jsonc
@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
||||||
"extends": ["ultracite"],
|
|
||||||
"linter": {
|
|
||||||
"rules": {
|
|
||||||
"style": {
|
|
||||||
"noNonNullAssertion": "off",
|
|
||||||
"noParameterAssign": "off",
|
|
||||||
"useFilenamingConvention": "warn",
|
|
||||||
"noParameterProperties": "off"
|
|
||||||
},
|
|
||||||
"suspicious": {
|
|
||||||
"noExplicitAny": "off"
|
|
||||||
},
|
|
||||||
"complexity": {
|
|
||||||
"noForEach": "off"
|
|
||||||
},
|
|
||||||
"correctness": {
|
|
||||||
"noUnusedImports": {
|
|
||||||
"fix": "none",
|
|
||||||
"level": "warn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nursery": {
|
|
||||||
"noEnum": "off",
|
|
||||||
"useConsistentMemberAccessibility": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"files": {
|
|
||||||
"ignore": [".vscode/*.json"]
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"include": ["src/**/*.spec.ts", "src/test.ts", "test"],
|
|
||||||
"javascript": {
|
|
||||||
"globals": ["describe", "beforeEach", "it", "expect"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
50
karma.conf.js
Normal file
50
karma.conf.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 damienbod
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
The MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2010-2025 Google LLC. https://angular.dev/license
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
3006
package-lock.json
generated
3006
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@ -22,54 +22,36 @@
|
|||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"files": ["dist"],
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rslib build",
|
"build": "rslib build",
|
||||||
"dev": "rslib build --watch",
|
"dev": "rslib build --watch",
|
||||||
"test": "vitest --coverage",
|
"test": "vitest --code-coverage",
|
||||||
"test-ci": "vitest --watch=false --coverage",
|
"test-ci": "vitest --watch=false --browsers=ChromeHeadlessNoSandbox --code-coverage",
|
||||||
"pack": "npm run build && npm pack ./dist",
|
"pack": "npm run build && npm pack ./dist",
|
||||||
"publish": "npm run build && npm publish ./dist",
|
"publish": "npm run build && npm publish ./dist",
|
||||||
"coverage": "vitest run --coverage",
|
"coverage": "vitest run --coverage",
|
||||||
"lint": "ultracite lint",
|
"lint": "ultracite lint",
|
||||||
"format": "ultracite format",
|
"format": "ultracite format"
|
||||||
"cli": "tsx scripts/cli.ts"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ngify/http": "^2.0.4",
|
"@ngify/http": "^2.0.4",
|
||||||
"injection-js": "git+https://github.com/mgechev/injection-js.git#81a10e0",
|
"injection-js": "git+https://github.com/mgechev/injection-js.git#81a10e0",
|
||||||
"reflect-metadata": "^0.2.2"
|
"rxjs": ">=7.4.0"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"rxjs": "^7.4.0||>=8.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
|
||||||
"@biomejs/js-api": "0.7.1",
|
|
||||||
"@biomejs/wasm-nodejs": "^1.9.4",
|
|
||||||
"@evilmartians/lefthook": "^1.0.3",
|
"@evilmartians/lefthook": "^1.0.3",
|
||||||
"@playwright/test": "^1.49.1",
|
"@playwright/test": "^1.49.1",
|
||||||
"@rslib/core": "^0.3.1",
|
"@rslib/core": "^0.3.1",
|
||||||
"@swc/core": "^1.10.12",
|
"@types/jasmine": "^4.0.0",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/node": "^22.10.1",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@vitest/coverage-v8": "^3.0.1",
|
||||||
"@types/node": "^22.12.0",
|
|
||||||
"@vitest/browser": "^3.0.4",
|
|
||||||
"@vitest/coverage-v8": "^3.0.4",
|
|
||||||
"commander": "^13.1.0",
|
|
||||||
"jsdom": "^26.0.0",
|
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"oxc-parser": "^0.48.1",
|
|
||||||
"oxc-walker": "^0.2.2",
|
|
||||||
"playwright": "^1.50.0",
|
|
||||||
"rfc4648": "^1.5.0",
|
"rfc4648": "^1.5.0",
|
||||||
"rxjs": "^7.4.0",
|
|
||||||
"tsx": "^4.19.2",
|
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"ultracite": "^4.1.15",
|
"ultracite": "^4.1.15",
|
||||||
"unplugin-swc": "^1.5.1",
|
"vitest": "^3.0.1"
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
|
||||||
"vitest": "^3.0.4"
|
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"rxjs",
|
"rxjs",
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test';
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
* https://github.com/motdotla/dotenv
|
* https://github.com/motdotla/dotenv
|
||||||
|
|||||||
3366
pnpm-lock.yaml
generated
3366
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import { Command } from 'commander';
|
|
||||||
import { rewriteAllObservableSubscribeTofirstValueFrom } from './code-transform';
|
|
||||||
|
|
||||||
const program = new Command();
|
|
||||||
|
|
||||||
program
|
|
||||||
.version('1.0.0')
|
|
||||||
.description('A CLI tool to help develop oidc-client-rx');
|
|
||||||
|
|
||||||
program
|
|
||||||
.command('rewrite <pattern>')
|
|
||||||
.description('Rewrite files matching the given glob pattern')
|
|
||||||
.action(async (pattern: string) => {
|
|
||||||
await rewriteAllObservableSubscribeTofirstValueFrom(pattern);
|
|
||||||
});
|
|
||||||
|
|
||||||
program.parse(process.argv);
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
import assert from 'node:assert/strict';
|
|
||||||
import { describe, it } from 'node:test';
|
|
||||||
import { Biome, Distribution } from '@biomejs/js-api';
|
|
||||||
import { rewriteObservableSubscribeTofirstValueFrom } from './code-transform';
|
|
||||||
|
|
||||||
describe('rewriteSpecObservableSubscribeTofirstValueFrom', () => {
|
|
||||||
it('should transform simple example valid string', async () => {
|
|
||||||
const actual = await rewriteObservableSubscribeTofirstValueFrom(
|
|
||||||
'index.ts',
|
|
||||||
`refreshSessionIframeService
|
|
||||||
.refreshSessionWithIframe(allConfigs[0]!, allConfigs)
|
|
||||||
.subscribe((result) => {
|
|
||||||
expect(
|
|
||||||
result
|
|
||||||
).toHaveBeenCalledExactlyOnceWith(
|
|
||||||
'a-url',
|
|
||||||
allConfigs[0]!,
|
|
||||||
allConfigs
|
|
||||||
);
|
|
||||||
});`
|
|
||||||
);
|
|
||||||
|
|
||||||
const expect = `const result = await firstValueFrom(refreshSessionIframeService.refreshSessionWithIframe(allConfigs[0]!, allConfigs));
|
|
||||||
expect(result).toHaveBeenCalledExactlyOnceWith('a-url',allConfigs[0]!,allConfigs);`;
|
|
||||||
|
|
||||||
const biome = await Biome.create({
|
|
||||||
distribution: Distribution.NODE,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
biome.formatContent(actual, { filePath: 'index.ts' }).content,
|
|
||||||
biome.formatContent(expect, { filePath: 'index.ts' }).content
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should rewrite complex exmaple to valid string', async () => {
|
|
||||||
const actual = await rewriteObservableSubscribeTofirstValueFrom(
|
|
||||||
'index.ts',
|
|
||||||
`codeFlowCallbackService
|
|
||||||
.authenticatedCallbackWithCode('some-url4', config, [config])
|
|
||||||
.subscribe({
|
|
||||||
error: (err: any) => {
|
|
||||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
|
||||||
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
|
|
||||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
|
||||||
expect(err).toBeTruthy();
|
|
||||||
},
|
|
||||||
next: (abc) => {
|
|
||||||
expect(abc).toBeTruthy();
|
|
||||||
},
|
|
||||||
complete () {
|
|
||||||
expect.fail('complete')
|
|
||||||
}
|
|
||||||
});`
|
|
||||||
);
|
|
||||||
|
|
||||||
const expect = `
|
|
||||||
try {
|
|
||||||
const abc = await firstValueFrom(codeFlowCallbackService.authenticatedCallbackWithCode('some-url4', config, [config]));
|
|
||||||
expect(abc).toBeTruthy();
|
|
||||||
} catch (err: any) {
|
|
||||||
if (err instanceof EmptyError) {
|
|
||||||
expect.fail('complete')
|
|
||||||
} else {
|
|
||||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
|
||||||
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
|
|
||||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
|
||||||
expect(err).toBeTruthy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const biome = await Biome.create({
|
|
||||||
distribution: Distribution.NODE,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
biome.formatContent(actual, { filePath: 'index.ts' }).content,
|
|
||||||
biome.formatContent(expect, { filePath: 'index.ts' }).content
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,173 +0,0 @@
|
|||||||
import assert from 'node:assert/strict';
|
|
||||||
import fsp from 'node:fs/promises';
|
|
||||||
import {
|
|
||||||
type ArrowFunctionExpression,
|
|
||||||
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
|
||||||
type Function,
|
|
||||||
type MagicString,
|
|
||||||
type Statement,
|
|
||||||
parseSync,
|
|
||||||
} from 'oxc-parser';
|
|
||||||
import { walk } from 'oxc-walker';
|
|
||||||
|
|
||||||
function sourceTextFromNode(
|
|
||||||
context: { magicString?: MagicString },
|
|
||||||
node: { start: number; end: number }
|
|
||||||
): string {
|
|
||||||
const magicString = context.magicString;
|
|
||||||
assert(magicString, 'magicString should be defined');
|
|
||||||
const start = node.start;
|
|
||||||
const end = node.end;
|
|
||||||
return magicString.getSourceText(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function rewriteObservableSubscribeTofirstValueFrom(
|
|
||||||
filename: string,
|
|
||||||
content?: string
|
|
||||||
) {
|
|
||||||
const code = content ?? (await fsp.readFile(filename, 'utf-8'));
|
|
||||||
const parsedResult = parseSync('index.ts', code);
|
|
||||||
const magicString = parsedResult.magicString;
|
|
||||||
walk(parsedResult, {
|
|
||||||
leave(node, _, context) {
|
|
||||||
const transformExprs = <T extends Statement[]>(
|
|
||||||
children: T
|
|
||||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
|
|
||||||
): T => {
|
|
||||||
const newChildren: T = [] as any as T;
|
|
||||||
for (const child of children) {
|
|
||||||
if (
|
|
||||||
child.type === 'ExpressionStatement' &&
|
|
||||||
child.expression.type === 'CallExpression' &&
|
|
||||||
child.expression.callee.type === 'StaticMemberExpression' &&
|
|
||||||
child.expression.callee.property.name === 'subscribe'
|
|
||||||
) {
|
|
||||||
let next: ArrowFunctionExpression | Function | undefined;
|
|
||||||
let error: ArrowFunctionExpression | Function | undefined;
|
|
||||||
let complete: ArrowFunctionExpression | Function | undefined;
|
|
||||||
|
|
||||||
if (child.expression.arguments[0]?.type === 'ObjectExpression') {
|
|
||||||
const obj = child.expression.arguments[0];
|
|
||||||
for (const prop of obj.properties) {
|
|
||||||
if (
|
|
||||||
prop.type === 'ObjectProperty' &&
|
|
||||||
prop.key.type === 'Identifier' &&
|
|
||||||
(prop.value.type === 'FunctionExpression' ||
|
|
||||||
prop.value.type === 'ArrowFunctionExpression')
|
|
||||||
) {
|
|
||||||
if (prop.key.name === 'next') {
|
|
||||||
next = prop.value;
|
|
||||||
} else if (prop.key.name === 'error') {
|
|
||||||
error = prop.value;
|
|
||||||
} else if (prop.key.name === 'complete') {
|
|
||||||
complete = prop.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
child.expression.arguments.find(
|
|
||||||
(arg) =>
|
|
||||||
arg.type === 'FunctionExpression' ||
|
|
||||||
arg.type === 'ArrowFunctionExpression'
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const args: Array<
|
|
||||||
Function | ArrowFunctionExpression | undefined
|
|
||||||
> = child.expression.arguments.map((arg) =>
|
|
||||||
arg.type === 'FunctionExpression' ||
|
|
||||||
arg.type === 'ArrowFunctionExpression'
|
|
||||||
? arg
|
|
||||||
: undefined
|
|
||||||
);
|
|
||||||
next = args[0];
|
|
||||||
error = args[1];
|
|
||||||
complete = args[2];
|
|
||||||
}
|
|
||||||
let newContent = `await firstValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});`;
|
|
||||||
|
|
||||||
if (next) {
|
|
||||||
const nextParam =
|
|
||||||
next?.params?.items?.[0]?.type === 'FormalParameter'
|
|
||||||
? sourceTextFromNode(context, next.params.items[0])
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (nextParam) {
|
|
||||||
newContent = `const ${nextParam} = ${newContent}`;
|
|
||||||
}
|
|
||||||
newContent += (next.body?.statements || [])
|
|
||||||
.map((s) => sourceTextFromNode(context, s))
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error || complete) {
|
|
||||||
const errorParam =
|
|
||||||
error?.params?.items?.[0]?.type === 'FormalParameter' &&
|
|
||||||
error.params.items[0].pattern.type === 'Identifier'
|
|
||||||
? sourceTextFromNode(context, error.params.items[0])
|
|
||||||
: 'err';
|
|
||||||
const errorParamName =
|
|
||||||
error?.params?.items?.[0]?.type === 'FormalParameter' &&
|
|
||||||
error.params.items[0].pattern.type === 'Identifier'
|
|
||||||
? error.params.items[0].pattern.name
|
|
||||||
: 'err';
|
|
||||||
|
|
||||||
let errorBody = '';
|
|
||||||
if (error) {
|
|
||||||
errorBody += (error.body?.statements || [])
|
|
||||||
.map((s) => sourceTextFromNode(context, s))
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
if (complete) {
|
|
||||||
const completBody = `if (${errorParamName} instanceof EmptyError) { ${(complete.body?.statements || []).map((s) => sourceTextFromNode(context, s)).join('\n')}}`;
|
|
||||||
if (errorBody) {
|
|
||||||
errorBody = `${completBody} else { ${errorBody} }`;
|
|
||||||
} else {
|
|
||||||
errorBody = completBody;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newContent = `try { ${newContent} } catch (${errorParam}) { ${errorBody} }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newNodes = parseSync('index.html', newContent).program.body;
|
|
||||||
|
|
||||||
magicString.remove(child.start, child.end);
|
|
||||||
magicString.appendLeft(child.start, newContent);
|
|
||||||
|
|
||||||
newChildren.push(...newNodes);
|
|
||||||
} else {
|
|
||||||
newChildren.push(child as any);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newChildren;
|
|
||||||
};
|
|
||||||
if ('body' in node && Array.isArray(node.body) && node.body.length > 0) {
|
|
||||||
const children = node.body;
|
|
||||||
node.body = transformExprs(children as any)!;
|
|
||||||
} else if (
|
|
||||||
'body' in node &&
|
|
||||||
node.body &&
|
|
||||||
'type' in node.body &&
|
|
||||||
node.body.type === 'FunctionBody'
|
|
||||||
) {
|
|
||||||
const children = node.body.statements;
|
|
||||||
node.body.statements = transformExprs(children)!;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = magicString.toString();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function rewriteAllObservableSubscribeTofirstValueFrom(
|
|
||||||
pattern: string | string[]
|
|
||||||
) {
|
|
||||||
const files = fsp.glob(pattern);
|
|
||||||
for await (const file of files) {
|
|
||||||
const result = await rewriteObservableSubscribeTofirstValueFrom(file);
|
|
||||||
|
|
||||||
await fsp.writeFile(file, result, 'utf-8');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +1,13 @@
|
|||||||
import { TestBed } from '@/testing';
|
|
||||||
import {
|
import {
|
||||||
HTTP_CLIENT_TEST_CONTROLLER,
|
HttpHeaders,
|
||||||
|
provideHttpClient,
|
||||||
|
withInterceptorsFromDi,
|
||||||
|
} from '@angular/common/http';
|
||||||
|
import {
|
||||||
|
HttpTestingController,
|
||||||
provideHttpClientTesting,
|
provideHttpClientTesting,
|
||||||
} from '@/testing/http';
|
} from '@angular/common/http/testing';
|
||||||
import { HttpHeaders } from '@ngify/http';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import type { HttpTestingController } from '@ngify/http/testing';
|
|
||||||
import { ReplaySubject, firstValueFrom, share } from 'rxjs';
|
|
||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { HttpBaseService } from './http-base.service';
|
import { HttpBaseService } from './http-base.service';
|
||||||
|
|
||||||
@ -16,10 +18,18 @@ describe('Data Service', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [],
|
||||||
providers: [DataService, HttpBaseService, provideHttpClientTesting()],
|
providers: [
|
||||||
|
DataService,
|
||||||
|
HttpBaseService,
|
||||||
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
dataService = TestBed.inject(DataService);
|
dataService = TestBed.inject(DataService);
|
||||||
httpMock = TestBed.inject(HTTP_CLIENT_TEST_CONTROLLER);
|
httpMock = TestBed.inject(HttpTestingController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
@ -27,20 +37,14 @@ describe('Data Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('get', () => {
|
describe('get', () => {
|
||||||
it('get call sets the accept header', async () => {
|
it('get call sets the accept header', waitForAsync(() => {
|
||||||
const url = 'testurl';
|
const url = 'testurl';
|
||||||
|
|
||||||
const test$ = dataService.get(url, { configId: 'configId1' }).pipe(
|
dataService
|
||||||
share({
|
.get(url, { configId: 'configId1' })
|
||||||
connector: () => new ReplaySubject(1),
|
.subscribe((data: unknown) => {
|
||||||
resetOnError: false,
|
expect(data).toBe('bodyData');
|
||||||
resetOnComplete: false,
|
});
|
||||||
resetOnRefCountZero: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
|
||||||
|
|
||||||
const req = httpMock.expectOne(url);
|
const req = httpMock.expectOne(url);
|
||||||
|
|
||||||
expect(req.request.method).toBe('GET');
|
expect(req.request.method).toBe('GET');
|
||||||
@ -48,55 +52,37 @@ describe('Data Service', () => {
|
|||||||
|
|
||||||
req.flush('bodyData');
|
req.flush('bodyData');
|
||||||
|
|
||||||
const data = await firstValueFrom(test$);
|
|
||||||
expect(data).toBe('bodyData');
|
|
||||||
|
|
||||||
httpMock.verify();
|
httpMock.verify();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('get call with token the accept header and the token', async () => {
|
it('get call with token the accept header and the token', waitForAsync(() => {
|
||||||
const url = 'testurl';
|
const url = 'testurl';
|
||||||
const token = 'token';
|
const token = 'token';
|
||||||
|
|
||||||
const test$ = dataService.get(url, { configId: 'configId1' }, token).pipe(
|
dataService
|
||||||
share({
|
.get(url, { configId: 'configId1' }, token)
|
||||||
connector: () => new ReplaySubject(1),
|
.subscribe((data: unknown) => {
|
||||||
resetOnError: false,
|
expect(data).toBe('bodyData');
|
||||||
resetOnComplete: false,
|
});
|
||||||
resetOnRefCountZero: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
|
||||||
|
|
||||||
const req = httpMock.expectOne(url);
|
const req = httpMock.expectOne(url);
|
||||||
|
|
||||||
expect(req.request.method).toBe('GET');
|
expect(req.request.method).toBe('GET');
|
||||||
expect(req.request.headers.get('Accept')).toBe('application/json');
|
expect(req.request.headers.get('Accept')).toBe('application/json');
|
||||||
expect(req.request.headers.get('Authorization')).toBe(`Bearer ${token}`);
|
expect(req.request.headers.get('Authorization')).toBe('Bearer ' + token);
|
||||||
|
|
||||||
req.flush('bodyData');
|
req.flush('bodyData');
|
||||||
|
|
||||||
const data = await firstValueFrom(test$);
|
|
||||||
expect(data).toBe('bodyData');
|
|
||||||
|
|
||||||
httpMock.verify();
|
httpMock.verify();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('call without ngsw-bypass param by default', async () => {
|
it('call without ngsw-bypass param by default', waitForAsync(() => {
|
||||||
const url = 'testurl';
|
const url = 'testurl';
|
||||||
|
|
||||||
const test$ = dataService.get(url, { configId: 'configId1' }).pipe(
|
dataService
|
||||||
share({
|
.get(url, { configId: 'configId1' })
|
||||||
connector: () => new ReplaySubject(1),
|
.subscribe((data: unknown) => {
|
||||||
resetOnError: false,
|
expect(data).toBe('bodyData');
|
||||||
resetOnComplete: false,
|
});
|
||||||
resetOnRefCountZero: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
|
||||||
|
|
||||||
const req = httpMock.expectOne(url);
|
const req = httpMock.expectOne(url);
|
||||||
|
|
||||||
expect(req.request.method).toBe('GET');
|
expect(req.request.method).toBe('GET');
|
||||||
@ -105,67 +91,36 @@ describe('Data Service', () => {
|
|||||||
|
|
||||||
req.flush('bodyData');
|
req.flush('bodyData');
|
||||||
|
|
||||||
const data = await firstValueFrom(test$);
|
|
||||||
expect(data).toBe('bodyData');
|
|
||||||
|
|
||||||
httpMock.verify();
|
httpMock.verify();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('call with ngsw-bypass param', async () => {
|
it('call with ngsw-bypass param', waitForAsync(() => {
|
||||||
const url = 'testurl';
|
const url = 'testurl';
|
||||||
|
|
||||||
const test$ = dataService
|
dataService
|
||||||
.get(url, {
|
.get(url, { configId: 'configId1', ngswBypass: true })
|
||||||
configId: 'configId1',
|
.subscribe((data: unknown) => {
|
||||||
ngswBypass: true,
|
expect(data).toBe('bodyData');
|
||||||
})
|
});
|
||||||
.pipe(
|
const req = httpMock.expectOne(url + '?ngsw-bypass=');
|
||||||
share({
|
|
||||||
connector: () => new ReplaySubject(1),
|
|
||||||
resetOnError: false,
|
|
||||||
resetOnComplete: false,
|
|
||||||
resetOnRefCountZero: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
|
||||||
|
|
||||||
const req = httpMock.expectOne(`${url}?ngsw-bypass=`);
|
|
||||||
|
|
||||||
expect(req.request.method).toBe('GET');
|
expect(req.request.method).toBe('GET');
|
||||||
expect(req.request.headers.get('Accept')).toBe('application/json');
|
expect(req.request.headers.get('Accept')).toBe('application/json');
|
||||||
|
expect(req.request.params.get('ngsw-bypass')).toBe('');
|
||||||
// @TODO: should make a issue to ngify
|
|
||||||
// expect(req.request.params.('ngsw-bypass')).toBe('');
|
|
||||||
|
|
||||||
expect(req.request.params.has('ngsw-bypass')).toBeTruthy();
|
|
||||||
|
|
||||||
req.flush('bodyData');
|
req.flush('bodyData');
|
||||||
|
|
||||||
const data = await firstValueFrom(test$);
|
|
||||||
expect(data).toBe('bodyData');
|
|
||||||
|
|
||||||
httpMock.verify();
|
httpMock.verify();
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('post', () => {
|
describe('post', () => {
|
||||||
it('call sets the accept header when no other params given', async () => {
|
it('call sets the accept header when no other params given', waitForAsync(() => {
|
||||||
const url = 'testurl';
|
const url = 'testurl';
|
||||||
|
|
||||||
const test$ = dataService
|
dataService
|
||||||
.post(url, { some: 'thing' }, { configId: 'configId1' })
|
.post(url, { some: 'thing' }, { configId: 'configId1' })
|
||||||
.pipe(
|
.subscribe();
|
||||||
share({
|
|
||||||
connector: () => new ReplaySubject(1),
|
|
||||||
resetOnError: false,
|
|
||||||
resetOnComplete: false,
|
|
||||||
resetOnRefCountZero: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
|
||||||
|
|
||||||
const req = httpMock.expectOne(url);
|
const req = httpMock.expectOne(url);
|
||||||
|
|
||||||
expect(req.request.method).toBe('POST');
|
expect(req.request.method).toBe('POST');
|
||||||
@ -173,30 +128,18 @@ describe('Data Service', () => {
|
|||||||
|
|
||||||
req.flush('bodyData');
|
req.flush('bodyData');
|
||||||
|
|
||||||
await firstValueFrom(test$);
|
httpMock.verify();
|
||||||
|
}));
|
||||||
|
|
||||||
await httpMock.verify();
|
it('call sets custom headers ONLY (No ACCEPT header) when custom headers are given', waitForAsync(() => {
|
||||||
});
|
|
||||||
|
|
||||||
it('call sets custom headers ONLY (No ACCEPT header) when custom headers are given', async () => {
|
|
||||||
const url = 'testurl';
|
const url = 'testurl';
|
||||||
let headers = new HttpHeaders();
|
let headers = new HttpHeaders();
|
||||||
|
|
||||||
headers = headers.set('X-MyHeader', 'Genesis');
|
headers = headers.set('X-MyHeader', 'Genesis');
|
||||||
|
|
||||||
const test$ = dataService
|
dataService
|
||||||
.post(url, { some: 'thing' }, { configId: 'configId1' }, headers)
|
.post(url, { some: 'thing' }, { configId: 'configId1' }, headers)
|
||||||
.pipe(
|
.subscribe();
|
||||||
share({
|
|
||||||
connector: () => new ReplaySubject(1),
|
|
||||||
resetOnError: false,
|
|
||||||
resetOnComplete: false,
|
|
||||||
resetOnRefCountZero: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
|
||||||
|
|
||||||
const req = httpMock.expectOne(url);
|
const req = httpMock.expectOne(url);
|
||||||
|
|
||||||
expect(req.request.method).toBe('POST');
|
expect(req.request.method).toBe('POST');
|
||||||
@ -205,27 +148,15 @@ describe('Data Service', () => {
|
|||||||
|
|
||||||
req.flush('bodyData');
|
req.flush('bodyData');
|
||||||
|
|
||||||
await firstValueFrom(test$);
|
|
||||||
|
|
||||||
httpMock.verify();
|
httpMock.verify();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('call without ngsw-bypass param by default', async () => {
|
it('call without ngsw-bypass param by default', waitForAsync(() => {
|
||||||
const url = 'testurl';
|
const url = 'testurl';
|
||||||
|
|
||||||
const test$ = dataService
|
dataService
|
||||||
.post(url, { some: 'thing' }, { configId: 'configId1' })
|
.post(url, { some: 'thing' }, { configId: 'configId1' })
|
||||||
.pipe(
|
.subscribe();
|
||||||
share({
|
|
||||||
connector: () => new ReplaySubject(1),
|
|
||||||
resetOnError: false,
|
|
||||||
resetOnComplete: false,
|
|
||||||
resetOnRefCountZero: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
|
||||||
|
|
||||||
const req = httpMock.expectOne(url);
|
const req = httpMock.expectOne(url);
|
||||||
|
|
||||||
expect(req.request.method).toBe('POST');
|
expect(req.request.method).toBe('POST');
|
||||||
@ -234,46 +165,28 @@ describe('Data Service', () => {
|
|||||||
|
|
||||||
req.flush('bodyData');
|
req.flush('bodyData');
|
||||||
|
|
||||||
await firstValueFrom(test$);
|
|
||||||
|
|
||||||
httpMock.verify();
|
httpMock.verify();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('call with ngsw-bypass param', async () => {
|
it('call with ngsw-bypass param', waitForAsync(() => {
|
||||||
const url = 'testurl';
|
const url = 'testurl';
|
||||||
|
|
||||||
const test$ = dataService
|
dataService
|
||||||
.post(
|
.post(
|
||||||
url,
|
url,
|
||||||
{ some: 'thing' },
|
{ some: 'thing' },
|
||||||
{ configId: 'configId1', ngswBypass: true }
|
{ configId: 'configId1', ngswBypass: true }
|
||||||
)
|
)
|
||||||
.pipe(
|
.subscribe();
|
||||||
share({
|
const req = httpMock.expectOne(url + '?ngsw-bypass=');
|
||||||
connector: () => new ReplaySubject(1),
|
|
||||||
resetOnError: false,
|
|
||||||
resetOnComplete: false,
|
|
||||||
resetOnRefCountZero: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
|
||||||
|
|
||||||
const req = httpMock.expectOne(`${url}?ngsw-bypass=`);
|
|
||||||
|
|
||||||
expect(req.request.method).toBe('POST');
|
expect(req.request.method).toBe('POST');
|
||||||
expect(req.request.headers.get('Accept')).toBe('application/json');
|
expect(req.request.headers.get('Accept')).toBe('application/json');
|
||||||
|
expect(req.request.params.get('ngsw-bypass')).toBe('');
|
||||||
// @TODO: should make a issue to ngify
|
|
||||||
// expect(req.request.params.('ngsw-bypass')).toBe('');
|
|
||||||
|
|
||||||
expect(req.request.params.has('ngsw-bypass')).toBeTruthy();
|
|
||||||
|
|
||||||
req.flush('bodyData');
|
req.flush('bodyData');
|
||||||
|
|
||||||
await firstValueFrom(test$);
|
|
||||||
|
|
||||||
httpMock.verify();
|
httpMock.verify();
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { HttpHeaders } from '@ngify/http';
|
import { HttpHeaders, HttpParams } from '@ngify/http';
|
||||||
import { Injectable, inject } from 'injection-js';
|
import { Injectable, inject } from 'injection-js';
|
||||||
import type { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import { HttpParams } from '../http';
|
|
||||||
import { HttpBaseService } from './http-base.service';
|
import { HttpBaseService } from './http-base.service';
|
||||||
|
|
||||||
const NGSW_CUSTOM_PARAM = 'ngsw-bypass';
|
const NGSW_CUSTOM_PARAM = 'ngsw-bypass';
|
||||||
@ -42,10 +41,10 @@ export class DataService {
|
|||||||
|
|
||||||
headers = headers.set('Accept', 'application/json');
|
headers = headers.set('Accept', 'application/json');
|
||||||
|
|
||||||
if (token) {
|
if (!!token) {
|
||||||
headers = headers.set(
|
headers = headers.set(
|
||||||
'Authorization',
|
'Authorization',
|
||||||
`Bearer ${decodeURIComponent(token)}`
|
'Bearer ' + decodeURIComponent(token)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,30 +1,22 @@
|
|||||||
import { HttpClient, type HttpHeaders } from '@ngify/http';
|
import { HttpClient } from '@ngify/http';
|
||||||
import { Injectable, inject } from 'injection-js';
|
import { Injectable, inject } from 'injection-js';
|
||||||
import type { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import type { HttpParams } from '../http';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HttpBaseService {
|
export class HttpBaseService {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
private readonly http = inject(HttpClient);
|
private readonly http = inject(HttpClient);
|
||||||
|
|
||||||
get<T>(
|
get<T>(url: string, params?: { [key: string]: unknown }): Observable<T> {
|
||||||
url: string,
|
return this.http.get<T>(url, params);
|
||||||
options: { headers?: HttpHeaders; params?: HttpParams } = {}
|
|
||||||
): Observable<T> {
|
|
||||||
return this.http.get<T>(url, {
|
|
||||||
...options,
|
|
||||||
params: options.params.toNgify(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
post<T>(
|
post<T>(
|
||||||
url: string,
|
url: string,
|
||||||
body: unknown,
|
body: unknown,
|
||||||
options: { headers?: HttpHeaders; params?: HttpParams } = {}
|
params?: { [key: string]: unknown }
|
||||||
): Observable<T> {
|
): Observable<T> {
|
||||||
return this.http.post<T>(url, body, {
|
return this.http.post<T>(url, body, params);
|
||||||
...options,
|
|
||||||
params: options.params.toNgify(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type PassedInitialConfig, createStaticLoader } from './auth-config';
|
import { PassedInitialConfig, createStaticLoader } from './auth-config';
|
||||||
|
|
||||||
describe('AuthConfig', () => {
|
describe('AuthConfig', () => {
|
||||||
describe('createStaticLoader', () => {
|
describe('createStaticLoader', () => {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { InjectionToken, type Provider } from 'injection-js';
|
import { InjectionToken, Provider } from 'injection-js';
|
||||||
import {
|
import {
|
||||||
type StsConfigLoader,
|
StsConfigLoader,
|
||||||
StsConfigStaticLoader,
|
StsConfigStaticLoader,
|
||||||
} from './config/loader/config-loader';
|
} from './config/loader/config-loader';
|
||||||
import type { OpenIdConfiguration } from './config/openid-configuration';
|
import { OpenIdConfiguration } from './config/openid-configuration';
|
||||||
|
|
||||||
export interface PassedInitialConfig {
|
export interface PassedInitialConfig {
|
||||||
config?: OpenIdConfiguration | OpenIdConfiguration[];
|
config?: OpenIdConfiguration | OpenIdConfiguration[];
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { EventTypes } from '../public-events/event-types';
|
import { EventTypes } from '../public-events/event-types';
|
||||||
import { PublicEventsService } from '../public-events/public-events.service';
|
import { PublicEventsService } from '../public-events/public-events.service';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { PlatformProvider } from '../utils/platform-provider/platform.provider';
|
import { PlatformProvider } from '../utils/platform-provider/platform.provider';
|
||||||
import { TokenValidationService } from '../validation/token-validation.service';
|
import { TokenValidationService } from '../validation/token-validation.service';
|
||||||
import type { ValidationResult } from '../validation/validation-result';
|
import { ValidationResult } from '../validation/validation-result';
|
||||||
import { AuthStateService } from './auth-state.service';
|
import { AuthStateService } from './auth-state.service';
|
||||||
|
|
||||||
describe('Auth State Service', () => {
|
describe('Auth State Service', () => {
|
||||||
@ -28,6 +27,9 @@ describe('Auth State Service', () => {
|
|||||||
mockProvider(StoragePersistenceService),
|
mockProvider(StoragePersistenceService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
authStateService = TestBed.inject(AuthStateService);
|
authStateService = TestBed.inject(AuthStateService);
|
||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
eventsService = TestBed.inject(PublicEventsService);
|
eventsService = TestBed.inject(PublicEventsService);
|
||||||
@ -38,13 +40,13 @@ describe('Auth State Service', () => {
|
|||||||
expect(authStateService).toBeTruthy();
|
expect(authStateService).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authorize$ is observable$', () => {
|
it('public authorize$ is observable$', () => {
|
||||||
expect(authStateService.authenticated$).toBeInstanceOf(Observable);
|
expect(authStateService.authenticated$).toEqual(jasmine.any(Observable));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setAuthorizedAndFireEvent', () => {
|
describe('setAuthorizedAndFireEvent', () => {
|
||||||
it('throws correct event with single config', () => {
|
it('throws correct event with single config', () => {
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(
|
||||||
(authStateService as any).authenticatedInternal$,
|
(authStateService as any).authenticatedInternal$,
|
||||||
'next'
|
'next'
|
||||||
);
|
);
|
||||||
@ -53,7 +55,7 @@ describe('Auth State Service', () => {
|
|||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
expect(spy).toHaveBeenCalledOnceWith({
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
allConfigsAuthenticated: [
|
allConfigsAuthenticated: [
|
||||||
{ configId: 'configId1', isAuthenticated: true },
|
{ configId: 'configId1', isAuthenticated: true },
|
||||||
@ -62,7 +64,7 @@ describe('Auth State Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws correct event with multiple configs', () => {
|
it('throws correct event with multiple configs', () => {
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(
|
||||||
(authStateService as any).authenticatedInternal$,
|
(authStateService as any).authenticatedInternal$,
|
||||||
'next'
|
'next'
|
||||||
);
|
);
|
||||||
@ -72,7 +74,7 @@ describe('Auth State Service', () => {
|
|||||||
{ configId: 'configId2' },
|
{ configId: 'configId2' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
expect(spy).toHaveBeenCalledOnceWith({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
allConfigsAuthenticated: [
|
allConfigsAuthenticated: [
|
||||||
{ configId: 'configId1', isAuthenticated: false },
|
{ configId: 'configId1', isAuthenticated: false },
|
||||||
@ -84,34 +86,26 @@ describe('Auth State Service', () => {
|
|||||||
it('throws correct event with multiple configs, one is authenticated', () => {
|
it('throws correct event with multiple configs, one is authenticated', () => {
|
||||||
const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }];
|
const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }];
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'getAccessToken')
|
||||||
mockImplementationWhenArgsEqual(
|
.withArgs(allConfigs[0])
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken'),
|
.and.returnValue('someAccessToken')
|
||||||
[allConfigs[0]!],
|
.withArgs(allConfigs[1])
|
||||||
() => 'someAccessToken'
|
.and.returnValue('');
|
||||||
),
|
|
||||||
[allConfigs[1]!],
|
|
||||||
() => ''
|
|
||||||
);
|
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'getIdToken')
|
||||||
mockImplementationWhenArgsEqual(
|
.withArgs(allConfigs[0])
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken'),
|
.and.returnValue('someIdToken')
|
||||||
[allConfigs[0]!],
|
.withArgs(allConfigs[1])
|
||||||
() => 'someIdToken'
|
.and.returnValue('');
|
||||||
),
|
|
||||||
[allConfigs[1]!],
|
|
||||||
() => ''
|
|
||||||
);
|
|
||||||
|
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(
|
||||||
(authStateService as any).authenticatedInternal$,
|
(authStateService as any).authenticatedInternal$,
|
||||||
'next'
|
'next'
|
||||||
);
|
);
|
||||||
|
|
||||||
authStateService.setAuthenticatedAndFireEvent(allConfigs);
|
authStateService.setAuthenticatedAndFireEvent(allConfigs);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
expect(spy).toHaveBeenCalledOnceWith({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
allConfigsAuthenticated: [
|
allConfigsAuthenticated: [
|
||||||
{ configId: 'configId1', isAuthenticated: true },
|
{ configId: 'configId1', isAuthenticated: true },
|
||||||
@ -123,20 +117,17 @@ describe('Auth State Service', () => {
|
|||||||
|
|
||||||
describe('setUnauthorizedAndFireEvent', () => {
|
describe('setUnauthorizedAndFireEvent', () => {
|
||||||
it('persist AuthState In Storage', () => {
|
it('persist AuthState In Storage', () => {
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(storagePersistenceService, 'resetAuthStateInStorage');
|
||||||
storagePersistenceService,
|
|
||||||
'resetAuthStateInStorage'
|
|
||||||
);
|
|
||||||
|
|
||||||
authStateService.setUnauthenticatedAndFireEvent(
|
authStateService.setUnauthenticatedAndFireEvent(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
[{ configId: 'configId1' }]
|
[{ configId: 'configId1' }]
|
||||||
);
|
);
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
expect(spy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws correct event with single config', () => {
|
it('throws correct event with single config', () => {
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(
|
||||||
(authStateService as any).authenticatedInternal$,
|
(authStateService as any).authenticatedInternal$,
|
||||||
'next'
|
'next'
|
||||||
);
|
);
|
||||||
@ -146,7 +137,7 @@ describe('Auth State Service', () => {
|
|||||||
[{ configId: 'configId1' }]
|
[{ configId: 'configId1' }]
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
expect(spy).toHaveBeenCalledOnceWith({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
allConfigsAuthenticated: [
|
allConfigsAuthenticated: [
|
||||||
{ configId: 'configId1', isAuthenticated: false },
|
{ configId: 'configId1', isAuthenticated: false },
|
||||||
@ -155,7 +146,7 @@ describe('Auth State Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws correct event with multiple configs', () => {
|
it('throws correct event with multiple configs', () => {
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(
|
||||||
(authStateService as any).authenticatedInternal$,
|
(authStateService as any).authenticatedInternal$,
|
||||||
'next'
|
'next'
|
||||||
);
|
);
|
||||||
@ -165,7 +156,7 @@ describe('Auth State Service', () => {
|
|||||||
[{ configId: 'configId1' }, { configId: 'configId2' }]
|
[{ configId: 'configId1' }, { configId: 'configId2' }]
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
expect(spy).toHaveBeenCalledOnceWith({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
allConfigsAuthenticated: [
|
allConfigsAuthenticated: [
|
||||||
{ configId: 'configId1', isAuthenticated: false },
|
{ configId: 'configId1', isAuthenticated: false },
|
||||||
@ -175,27 +166,19 @@ describe('Auth State Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws correct event with multiple configs, one is authenticated', () => {
|
it('throws correct event with multiple configs, one is authenticated', () => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'getAccessToken')
|
||||||
mockImplementationWhenArgsEqual(
|
.withArgs({ configId: 'configId1' })
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken'),
|
.and.returnValue('someAccessToken')
|
||||||
[{ configId: 'configId1' }],
|
.withArgs({ configId: 'configId2' })
|
||||||
() => 'someAccessToken'
|
.and.returnValue('');
|
||||||
),
|
|
||||||
[{ configId: 'configId2' }],
|
|
||||||
() => ''
|
|
||||||
);
|
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'getIdToken')
|
||||||
mockImplementationWhenArgsEqual(
|
.withArgs({ configId: 'configId1' })
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken'),
|
.and.returnValue('someIdToken')
|
||||||
[{ configId: 'configId1' }],
|
.withArgs({ configId: 'configId2' })
|
||||||
() => 'someIdToken'
|
.and.returnValue('');
|
||||||
),
|
|
||||||
[{ configId: 'configId2' }],
|
|
||||||
() => ''
|
|
||||||
);
|
|
||||||
|
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(
|
||||||
(authStateService as any).authenticatedInternal$,
|
(authStateService as any).authenticatedInternal$,
|
||||||
'next'
|
'next'
|
||||||
);
|
);
|
||||||
@ -205,7 +188,7 @@ describe('Auth State Service', () => {
|
|||||||
[{ configId: 'configId1' }, { configId: 'configId2' }]
|
[{ configId: 'configId1' }, { configId: 'configId2' }]
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
expect(spy).toHaveBeenCalledOnceWith({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
allConfigsAuthenticated: [
|
allConfigsAuthenticated: [
|
||||||
{ configId: 'configId1', isAuthenticated: true },
|
{ configId: 'configId1', isAuthenticated: true },
|
||||||
@ -217,27 +200,24 @@ describe('Auth State Service', () => {
|
|||||||
|
|
||||||
describe('updateAndPublishAuthState', () => {
|
describe('updateAndPublishAuthState', () => {
|
||||||
it('calls eventsService', () => {
|
it('calls eventsService', () => {
|
||||||
vi.spyOn(eventsService, 'fireEvent');
|
spyOn(eventsService, 'fireEvent');
|
||||||
|
|
||||||
const arg = {
|
authStateService.updateAndPublishAuthState({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
validationResult: {} as ValidationResult,
|
validationResult: {} as ValidationResult,
|
||||||
};
|
});
|
||||||
|
|
||||||
authStateService.updateAndPublishAuthState(arg);
|
expect(eventsService.fireEvent).toHaveBeenCalledOnceWith(
|
||||||
|
|
||||||
expect(eventsService.fireEvent).toHaveBeenCalledOnce();
|
|
||||||
expect(eventsService.fireEvent).toHaveBeenCalledExactlyOnceWith(
|
|
||||||
EventTypes.NewAuthenticationResult,
|
EventTypes.NewAuthenticationResult,
|
||||||
arg
|
jasmine.any(Object)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setAuthorizationData', () => {
|
describe('setAuthorizationData', () => {
|
||||||
it('stores accessToken', () => {
|
it('stores accessToken', () => {
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
const spy = spyOn(storagePersistenceService, 'write');
|
||||||
const authResult = {
|
const authResult = {
|
||||||
id_token: 'idtoken',
|
id_token: 'idtoken',
|
||||||
access_token: 'accesstoken',
|
access_token: 'accesstoken',
|
||||||
@ -257,19 +237,18 @@ describe('Auth State Service', () => {
|
|||||||
[{ configId: 'configId1' }]
|
[{ configId: 'configId1' }]
|
||||||
);
|
);
|
||||||
expect(spy).toHaveBeenCalledTimes(2);
|
expect(spy).toHaveBeenCalledTimes(2);
|
||||||
expect(spy.mock.calls).toEqual([
|
expect(spy.calls.allArgs()).toEqual([
|
||||||
['authzData', 'accesstoken', { configId: 'configId1' }],
|
['authzData', 'accesstoken', { configId: 'configId1' }],
|
||||||
[
|
[
|
||||||
'access_token_expires_at',
|
'access_token_expires_at',
|
||||||
expect.any(Number),
|
jasmine.any(Number),
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not crash and store accessToken when authResult is null', () => {
|
it('does not crash and store accessToken when authResult is null', () => {
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
const spy = spyOn(storagePersistenceService, 'write');
|
||||||
// biome-ignore lint/suspicious/noEvolvingTypes: <explanation>
|
|
||||||
const authResult = null;
|
const authResult = null;
|
||||||
|
|
||||||
authStateService.setAuthorizationData(
|
authStateService.setAuthorizationData(
|
||||||
@ -283,7 +262,7 @@ describe('Auth State Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('calls setAuthenticatedAndFireEvent() method', () => {
|
it('calls setAuthenticatedAndFireEvent() method', () => {
|
||||||
const spy = vi.spyOn(authStateService, 'setAuthenticatedAndFireEvent');
|
const spy = spyOn(authStateService, 'setAuthenticatedAndFireEvent');
|
||||||
const authResult = {
|
const authResult = {
|
||||||
id_token: 'idtoken',
|
id_token: 'idtoken',
|
||||||
access_token: 'accesstoken',
|
access_token: 'accesstoken',
|
||||||
@ -309,29 +288,28 @@ describe('Auth State Service', () => {
|
|||||||
|
|
||||||
describe('getAccessToken', () => {
|
describe('getAccessToken', () => {
|
||||||
it('isAuthorized is false returns null', () => {
|
it('isAuthorized is false returns null', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||||
const result = authStateService.getAccessToken({ configId: 'configId1' });
|
const result = authStateService.getAccessToken({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBe('');
|
expect(result).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if storagePersistenceService returns something falsy but authorized', () => {
|
it('returns false if storagePersistenceService returns something falsy but authorized', () => {
|
||||||
vi.spyOn(authStateService, 'isAuthenticated').mockReturnValue(true);
|
spyOn(authStateService, 'isAuthenticated').and.returnValue(true);
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||||
const result = authStateService.getAccessToken({ configId: 'configId1' });
|
const result = authStateService.getAccessToken({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBe('');
|
expect(result).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isAuthorized is true returns decodeURIComponent(token)', () => {
|
it('isAuthorized is true returns decodeURIComponent(token)', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||||
'HenloLegger'
|
'HenloLegger'
|
||||||
);
|
);
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||||
'HenloFuriend'
|
'HenloFuriend'
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = authStateService.getAccessToken({ configId: 'configId1' });
|
const result = authStateService.getAccessToken({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBe(decodeURIComponent('HenloLegger'));
|
expect(result).toBe(decodeURIComponent('HenloLegger'));
|
||||||
@ -340,14 +318,12 @@ describe('Auth State Service', () => {
|
|||||||
|
|
||||||
describe('getAuthenticationResult', () => {
|
describe('getAuthenticationResult', () => {
|
||||||
it('isAuthorized is false returns null', () => {
|
it('isAuthorized is false returns null', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'getAuthenticationResult')
|
||||||
vi.spyOn(storagePersistenceService, 'getAuthenticationResult'),
|
.withArgs({ configId: 'configId1' })
|
||||||
[{ configId: 'configId1' }],
|
.and.returnValue({});
|
||||||
() => ({})
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = authStateService.getAuthenticationResult({
|
const result = authStateService.getAuthenticationResult({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -357,13 +333,10 @@ describe('Auth State Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if storagePersistenceService returns something falsy but authorized', () => {
|
it('returns false if storagePersistenceService returns something falsy but authorized', () => {
|
||||||
vi.spyOn(authStateService, 'isAuthenticated').mockReturnValue(true);
|
spyOn(authStateService, 'isAuthenticated').and.returnValue(true);
|
||||||
|
spyOn(storagePersistenceService, 'getAuthenticationResult')
|
||||||
mockImplementationWhenArgsEqual(
|
.withArgs({ configId: 'configId1' })
|
||||||
vi.spyOn(storagePersistenceService, 'getAuthenticationResult'),
|
.and.returnValue({});
|
||||||
[{ configId: 'configId1' }],
|
|
||||||
() => ({})
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = authStateService.getAuthenticationResult({
|
const result = authStateService.getAuthenticationResult({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -373,18 +346,15 @@ describe('Auth State Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('isAuthorized is true returns object', () => {
|
it('isAuthorized is true returns object', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||||
'HenloLegger'
|
'HenloLegger'
|
||||||
);
|
);
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||||
'HenloFuriend'
|
'HenloFuriend'
|
||||||
);
|
);
|
||||||
|
spyOn(storagePersistenceService, 'getAuthenticationResult')
|
||||||
mockImplementationWhenArgsEqual(
|
.withArgs({ configId: 'configId1' })
|
||||||
vi.spyOn(storagePersistenceService, 'getAuthenticationResult'),
|
.and.returnValue({ scope: 'HenloFuriend' });
|
||||||
[{ configId: 'configId1' }],
|
|
||||||
() => ({ scope: 'HenloFuriend' })
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = authStateService.getAuthenticationResult({
|
const result = authStateService.getAuthenticationResult({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -396,18 +366,18 @@ describe('Auth State Service', () => {
|
|||||||
|
|
||||||
describe('getIdToken', () => {
|
describe('getIdToken', () => {
|
||||||
it('isAuthorized is false returns null', () => {
|
it('isAuthorized is false returns null', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||||
const result = authStateService.getIdToken({ configId: 'configId1' });
|
const result = authStateService.getIdToken({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBe('');
|
expect(result).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isAuthorized is true returns decodeURIComponent(token)', () => {
|
it('isAuthorized is true returns decodeURIComponent(token)', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||||
'HenloLegger'
|
'HenloLegger'
|
||||||
);
|
);
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||||
'HenloFuriend'
|
'HenloFuriend'
|
||||||
);
|
);
|
||||||
const result = authStateService.getIdToken({ configId: 'configId1' });
|
const result = authStateService.getIdToken({ configId: 'configId1' });
|
||||||
@ -418,8 +388,8 @@ describe('Auth State Service', () => {
|
|||||||
|
|
||||||
describe('getRefreshToken', () => {
|
describe('getRefreshToken', () => {
|
||||||
it('isAuthorized is false returns null', () => {
|
it('isAuthorized is false returns null', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||||
const result = authStateService.getRefreshToken({
|
const result = authStateService.getRefreshToken({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
@ -428,13 +398,13 @@ describe('Auth State Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('isAuthorized is true returns decodeURIComponent(token)', () => {
|
it('isAuthorized is true returns decodeURIComponent(token)', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||||
'HenloLegger'
|
'HenloLegger'
|
||||||
);
|
);
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||||
'HenloFuriend'
|
'HenloFuriend'
|
||||||
);
|
);
|
||||||
vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue(
|
||||||
'HenloRefreshLegger'
|
'HenloRefreshLegger'
|
||||||
);
|
);
|
||||||
const result = authStateService.getRefreshToken({
|
const result = authStateService.getRefreshToken({
|
||||||
@ -447,105 +417,105 @@ describe('Auth State Service', () => {
|
|||||||
|
|
||||||
describe('areAuthStorageTokensValid', () => {
|
describe('areAuthStorageTokensValid', () => {
|
||||||
it('isAuthorized is false returns false', () => {
|
it('isAuthorized is false returns false', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||||
const result = authStateService.areAuthStorageTokensValid({
|
const result = authStateService.areAuthStorageTokensValid({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isAuthorized is true and id_token is expired returns true', () => {
|
it('isAuthorized is true and id_token is expired returns true', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||||
'HenloLegger'
|
'HenloLegger'
|
||||||
);
|
);
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||||
'HenloFuriend'
|
'HenloFuriend'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService as any,
|
authStateService as any,
|
||||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService as any,
|
authStateService as any,
|
||||||
'hasAccessTokenExpiredIfExpiryExists'
|
'hasAccessTokenExpiredIfExpiryExists'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
const result = authStateService.areAuthStorageTokensValid({
|
const result = authStateService.areAuthStorageTokensValid({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isAuthorized is true and access_token is expired returns true', () => {
|
it('isAuthorized is true and access_token is expired returns true', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||||
'HenloLegger'
|
'HenloLegger'
|
||||||
);
|
);
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||||
'HenloFuriend'
|
'HenloFuriend'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService as any,
|
authStateService as any,
|
||||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService as any,
|
authStateService as any,
|
||||||
'hasAccessTokenExpiredIfExpiryExists'
|
'hasAccessTokenExpiredIfExpiryExists'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
const result = authStateService.areAuthStorageTokensValid({
|
const result = authStateService.areAuthStorageTokensValid({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isAuthorized is true and id_token is not expired returns true', () => {
|
it('isAuthorized is true and id_token is not expired returns true', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||||
'HenloLegger'
|
'HenloLegger'
|
||||||
);
|
);
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||||
'HenloFuriend'
|
'HenloFuriend'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService as any,
|
authStateService as any,
|
||||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService as any,
|
authStateService as any,
|
||||||
'hasAccessTokenExpiredIfExpiryExists'
|
'hasAccessTokenExpiredIfExpiryExists'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
const result = authStateService.areAuthStorageTokensValid({
|
const result = authStateService.areAuthStorageTokensValid({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authState is AuthorizedState.Authorized and id_token is not expired fires event', () => {
|
it('authState is AuthorizedState.Authorized and id_token is not expired fires event', () => {
|
||||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||||
'HenloLegger'
|
'HenloLegger'
|
||||||
);
|
);
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||||
'HenloFuriend'
|
'HenloFuriend'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService as any,
|
authStateService as any,
|
||||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService as any,
|
authStateService as any,
|
||||||
'hasAccessTokenExpiredIfExpiryExists'
|
'hasAccessTokenExpiredIfExpiryExists'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
const result = authStateService.areAuthStorageTokensValid({
|
const result = authStateService.areAuthStorageTokensValid({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -557,65 +527,56 @@ describe('Auth State Service', () => {
|
|||||||
triggerRefreshWhenIdTokenExpired: true,
|
triggerRefreshWhenIdTokenExpired: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'getIdToken')
|
||||||
vi.spyOn(storagePersistenceService, 'getIdToken'),
|
.withArgs(config)
|
||||||
[config],
|
.and.returnValue('idToken');
|
||||||
() => 'idToken'
|
const spy = spyOn(
|
||||||
);
|
tokenValidationService,
|
||||||
const spy = vi
|
'hasIdTokenExpired'
|
||||||
.spyOn(tokenValidationService, 'hasIdTokenExpired')
|
).and.callFake((_a, _b) => true);
|
||||||
.mockImplementation((_a, _b) => true);
|
|
||||||
|
|
||||||
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
|
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith('idToken', config, 30);
|
expect(spy).toHaveBeenCalledOnceWith('idToken', config, 30);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fires event if idToken is expired', () => {
|
it('fires event if idToken is expired', () => {
|
||||||
vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockImplementation(
|
spyOn(tokenValidationService, 'hasIdTokenExpired').and.callFake(
|
||||||
(_a, _b) => true
|
(_a, _b) => true
|
||||||
);
|
);
|
||||||
|
|
||||||
const spy = vi.spyOn(eventsService, 'fireEvent');
|
const spy = spyOn(eventsService, 'fireEvent');
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
renewTimeBeforeTokenExpiresInSeconds: 30,
|
renewTimeBeforeTokenExpiresInSeconds: 30,
|
||||||
triggerRefreshWhenIdTokenExpired: true,
|
triggerRefreshWhenIdTokenExpired: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authnResult', config)
|
||||||
['authnResult', config],
|
.and.returnValue('idToken');
|
||||||
() => 'idToken'
|
|
||||||
);
|
|
||||||
|
|
||||||
const result =
|
const result =
|
||||||
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
|
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
|
||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
expect(spy).toHaveBeenCalledOnceWith(EventTypes.IdTokenExpired, true);
|
||||||
EventTypes.IdTokenExpired,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does NOT fire event if idToken is NOT expired', () => {
|
it('does NOT fire event if idToken is NOT expired', () => {
|
||||||
vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockImplementation(
|
spyOn(tokenValidationService, 'hasIdTokenExpired').and.callFake(
|
||||||
(_a, _b) => false
|
(_a, _b) => false
|
||||||
);
|
);
|
||||||
|
|
||||||
const spy = vi.spyOn(eventsService, 'fireEvent');
|
const spy = spyOn(eventsService, 'fireEvent');
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
renewTimeBeforeTokenExpiresInSeconds: 30,
|
renewTimeBeforeTokenExpiresInSeconds: 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authnResult', config)
|
||||||
['authnResult', config],
|
.and.returnValue('idToken');
|
||||||
() => 'idToken'
|
|
||||||
);
|
|
||||||
|
|
||||||
const result =
|
const result =
|
||||||
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
|
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
|
||||||
|
|
||||||
@ -634,45 +595,41 @@ describe('Auth State Service', () => {
|
|||||||
renewTimeBeforeTokenExpiresInSeconds: 5,
|
renewTimeBeforeTokenExpiresInSeconds: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('access_token_expires_at', config)
|
||||||
['access_token_expires_at', config],
|
.and.returnValue(date);
|
||||||
() => date
|
const spy = spyOn(
|
||||||
);
|
tokenValidationService,
|
||||||
const spy = vi
|
'validateAccessTokenNotExpired'
|
||||||
.spyOn(tokenValidationService, 'validateAccessTokenNotExpired')
|
).and.returnValue(validateAccessTokenNotExpiredResult);
|
||||||
.mockReturnValue(validateAccessTokenNotExpiredResult);
|
|
||||||
const result =
|
const result =
|
||||||
authStateService.hasAccessTokenExpiredIfExpiryExists(config);
|
authStateService.hasAccessTokenExpiredIfExpiryExists(config);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(date, config, 5);
|
expect(spy).toHaveBeenCalledOnceWith(date, config, 5);
|
||||||
expect(result).toEqual(expectedResult);
|
expect(result).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws event when token is expired', () => {
|
it('throws event when token is expired', () => {
|
||||||
const validateAccessTokenNotExpiredResult = false;
|
const validateAccessTokenNotExpiredResult = false;
|
||||||
const expectedResult = !validateAccessTokenNotExpiredResult;
|
const expectedResult = !validateAccessTokenNotExpiredResult;
|
||||||
// vi.spyOn(configurationProvider, 'getOpenIDConfiguration').mockReturnValue({ renewTimeBeforeTokenExpiresInSeconds: 5 });
|
// spyOn(configurationProvider, 'getOpenIDConfiguration').and.returnValue({ renewTimeBeforeTokenExpiresInSeconds: 5 });
|
||||||
const date = new Date(new Date().toUTCString());
|
const date = new Date(new Date().toUTCString());
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
renewTimeBeforeTokenExpiresInSeconds: 5,
|
renewTimeBeforeTokenExpiresInSeconds: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
vi.spyOn(eventsService, 'fireEvent');
|
spyOn(eventsService, 'fireEvent');
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('access_token_expires_at', config)
|
||||||
['access_token_expires_at', config],
|
.and.returnValue(date);
|
||||||
() => date
|
spyOn(
|
||||||
);
|
|
||||||
|
|
||||||
vi.spyOn(
|
|
||||||
tokenValidationService,
|
tokenValidationService,
|
||||||
'validateAccessTokenNotExpired'
|
'validateAccessTokenNotExpired'
|
||||||
).mockReturnValue(validateAccessTokenNotExpiredResult);
|
).and.returnValue(validateAccessTokenNotExpiredResult);
|
||||||
authStateService.hasAccessTokenExpiredIfExpiryExists(config);
|
authStateService.hasAccessTokenExpiredIfExpiryExists(config);
|
||||||
expect(eventsService.fireEvent).toHaveBeenCalledExactlyOnceWith(
|
expect(eventsService.fireEvent).toHaveBeenCalledOnceWith(
|
||||||
EventTypes.TokenExpired,
|
EventTypes.TokenExpired,
|
||||||
expectedResult
|
expectedResult
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { Injectable, inject } from 'injection-js';
|
||||||
import { BehaviorSubject, type Observable, throwError } from 'rxjs';
|
import { BehaviorSubject, Observable, throwError } from 'rxjs';
|
||||||
import { distinctUntilChanged } from 'rxjs/operators';
|
import { distinctUntilChanged } from 'rxjs/operators';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import type { AuthResult } from '../flows/callback-context';
|
import { AuthResult } from '../flows/callback-context';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { EventTypes } from '../public-events/event-types';
|
import { EventTypes } from '../public-events/event-types';
|
||||||
import { PublicEventsService } from '../public-events/public-events.service';
|
import { PublicEventsService } from '../public-events/public-events.service';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { TokenValidationService } from '../validation/token-validation.service';
|
import { TokenValidationService } from '../validation/token-validation.service';
|
||||||
import type { AuthenticatedResult } from './auth-result';
|
import { AuthenticatedResult } from './auth-result';
|
||||||
import type { AuthStateResult } from './auth-state';
|
import { AuthStateResult } from './auth-state';
|
||||||
|
|
||||||
const DEFAULT_AUTHRESULT = {
|
const DEFAULT_AUTHRESULT = {
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
@ -257,8 +257,9 @@ export class AuthStateService {
|
|||||||
private decodeURIComponentSafely(token: string): string {
|
private decodeURIComponentSafely(token: string): string {
|
||||||
if (token) {
|
if (token) {
|
||||||
return decodeURIComponent(token);
|
return decodeURIComponent(token);
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private persistAccessTokenExpirationTime(
|
private persistAccessTokenExpirationTime(
|
||||||
@ -292,7 +293,7 @@ export class AuthStateService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.checkallConfigsIfTheyAreAuthenticated(allConfigs);
|
return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private composeUnAuthenticatedResult(
|
private composeUnAuthenticatedResult(
|
||||||
@ -309,10 +310,10 @@ export class AuthStateService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.checkallConfigsIfTheyAreAuthenticated(allConfigs);
|
return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkallConfigsIfTheyAreAuthenticated(
|
private checkAllConfigsIfTheyAreAuthenticated(
|
||||||
allConfigs: OpenIdConfiguration[]
|
allConfigs: OpenIdConfiguration[]
|
||||||
): AuthenticatedResult {
|
): AuthenticatedResult {
|
||||||
const allConfigsAuthenticated = allConfigs.map((config) => ({
|
const allConfigsAuthenticated = allConfigs.map((config) => ({
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ValidationResult } from '../validation/validation-result';
|
import { ValidationResult } from '../validation/validation-result';
|
||||||
|
|
||||||
export interface AuthStateResult {
|
export interface AuthStateResult {
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,15 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, forkJoin, of, throwError } from 'rxjs';
|
import { forkJoin, Observable, of, throwError } from 'rxjs';
|
||||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { AutoLoginService } from '../auto-login/auto-login.service';
|
import { AutoLoginService } from '../auto-login/auto-login.service';
|
||||||
import { CallbackService } from '../callback/callback.service';
|
import { CallbackService } from '../callback/callback.service';
|
||||||
import { PeriodicallyTokenCheckService } from '../callback/periodically-token-check.service';
|
import { PeriodicallyTokenCheckService } from '../callback/periodically-token-check.service';
|
||||||
import { RefreshSessionService } from '../callback/refresh-session.service';
|
import { RefreshSessionService } from '../callback/refresh-session.service';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import { CheckSessionService } from '../iframe/check-session.service';
|
import { CheckSessionService } from '../iframe/check-session.service';
|
||||||
import { SilentRenewService } from '../iframe/silent-renew.service';
|
import { SilentRenewService } from '../iframe/silent-renew.service';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import type { LoginResponse } from '../login/login-response';
|
import { LoginResponse } from '../login/login-response';
|
||||||
import { PopUpService } from '../login/popup/popup.service';
|
import { PopUpService } from '../login/popup/popup.service';
|
||||||
import { EventTypes } from '../public-events/event-types';
|
import { EventTypes } from '../public-events/event-types';
|
||||||
import { PublicEventsService } from '../public-events/public-events.service';
|
import { PublicEventsService } from '../public-events/public-events.service';
|
||||||
@ -57,7 +57,7 @@ export class CheckAuthService {
|
|||||||
const stateParamFromUrl =
|
const stateParamFromUrl =
|
||||||
this.currentUrlService.getStateParamFromCurrentUrl(url);
|
this.currentUrlService.getStateParamFromCurrentUrl(url);
|
||||||
|
|
||||||
return stateParamFromUrl
|
return Boolean(stateParamFromUrl)
|
||||||
? this.getConfigurationWithUrlState([configuration], stateParamFromUrl)
|
? this.getConfigurationWithUrlState([configuration], stateParamFromUrl)
|
||||||
: configuration;
|
: configuration;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
import { mockProvider } from '../test/auto-mock';
|
||||||
import { PASSED_CONFIG } from './auth-config';
|
import { PASSED_CONFIG } from './auth-config';
|
||||||
import { AuthModule } from './auth.module';
|
import { AuthModule } from './auth.module';
|
||||||
import { ConfigurationService } from './config/config.service';
|
import { ConfigurationService } from './config/config.service';
|
||||||
@ -8,42 +9,37 @@ import {
|
|||||||
StsConfigLoader,
|
StsConfigLoader,
|
||||||
StsConfigStaticLoader,
|
StsConfigStaticLoader,
|
||||||
} from './config/loader/config-loader';
|
} from './config/loader/config-loader';
|
||||||
import { mockProvider } from './testing/mock';
|
|
||||||
|
|
||||||
describe('AuthModule', () => {
|
describe('AuthModule', () => {
|
||||||
describe('APP_CONFIG', () => {
|
describe('APP_CONFIG', () => {
|
||||||
let authModule: AuthModule;
|
beforeEach(waitForAsync(() => {
|
||||||
beforeEach(async () => {
|
TestBed.configureTestingModule({
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [AuthModule.forRoot({ config: { authority: 'something' } })],
|
imports: [AuthModule.forRoot({ config: { authority: 'something' } })],
|
||||||
providers: [mockProvider(ConfigurationService)],
|
providers: [mockProvider(ConfigurationService)],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
authModule = TestBed.getImportByType(AuthModule);
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(AuthModule).toBeDefined();
|
expect(AuthModule).toBeDefined();
|
||||||
expect(authModule).toBeDefined();
|
expect(AuthModule.forRoot({})).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should provide config', () => {
|
it('should provide config', () => {
|
||||||
const config = authModule.get(PASSED_CONFIG);
|
const config = TestBed.inject(PASSED_CONFIG);
|
||||||
|
|
||||||
expect(config).toEqual({ config: { authority: 'something' } });
|
expect(config).toEqual({ config: { authority: 'something' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create StsConfigStaticLoader if config is passed', () => {
|
it('should create StsConfigStaticLoader if config is passed', () => {
|
||||||
const configLoader = authModule.get(StsConfigLoader);
|
const configLoader = TestBed.inject(StsConfigLoader);
|
||||||
|
|
||||||
expect(configLoader instanceof StsConfigStaticLoader).toBe(true);
|
expect(configLoader instanceof StsConfigStaticLoader).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('StsConfigHttpLoader', () => {
|
describe('StsConfigHttpLoader', () => {
|
||||||
let authModule: AuthModule;
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [
|
imports: [
|
||||||
AuthModule.forRoot({
|
AuthModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
@ -54,11 +50,10 @@ describe('AuthModule', () => {
|
|||||||
],
|
],
|
||||||
providers: [mockProvider(ConfigurationService)],
|
providers: [mockProvider(ConfigurationService)],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
authModule = TestBed.getImportByType(AuthModule);
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('should create StsConfigStaticLoader if config is passed', () => {
|
it('should create StsConfigStaticLoader if config is passed', () => {
|
||||||
const configLoader = authModule.get(StsConfigLoader);
|
const configLoader = TestBed.inject(StsConfigLoader);
|
||||||
|
|
||||||
expect(configLoader instanceof StsConfigHttpLoader).toBe(true);
|
expect(configLoader instanceof StsConfigHttpLoader).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,41 +1,25 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
type InjectionToken,
|
provideHttpClient,
|
||||||
Injector,
|
withInterceptorsFromDi,
|
||||||
ReflectiveInjector,
|
} from '@ngify/http';
|
||||||
type Type,
|
import { ModuleWithProviders, NgModule } from 'injection-js';
|
||||||
} from 'injection-js';
|
import { PassedInitialConfig } from './auth-config';
|
||||||
import type { PassedInitialConfig } from './auth-config';
|
|
||||||
import type { Module } from './injection';
|
|
||||||
import { _provideAuth } from './provide-auth';
|
import { _provideAuth } from './provide-auth';
|
||||||
|
|
||||||
export interface AuthModuleOptions {
|
@NgModule({
|
||||||
passedConfig: PassedInitialConfig;
|
declarations: [],
|
||||||
parentInjector?: ReflectiveInjector;
|
exports: [],
|
||||||
}
|
imports: [CommonModule],
|
||||||
|
providers: [provideHttpClient(withInterceptorsFromDi())],
|
||||||
export class AuthModule extends Injector {
|
})
|
||||||
passedConfig: PassedInitialConfig;
|
export class AuthModule {
|
||||||
injector: ReflectiveInjector;
|
static forRoot(
|
||||||
parentInjector?: Injector;
|
passedConfig: PassedInitialConfig
|
||||||
|
): ModuleWithProviders<AuthModule> {
|
||||||
constructor(passedConfig?: PassedInitialConfig, parentInjector?: Injector) {
|
return {
|
||||||
super();
|
ngModule: AuthModule,
|
||||||
this.passedConfig = passedConfig ?? {};
|
providers: [..._provideAuth(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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import { TestBed, mockRouterProvider } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import {
|
import {
|
||||||
AbstractRouter,
|
ActivatedRouteSnapshot,
|
||||||
type ActivatedRouteSnapshot,
|
Router,
|
||||||
type RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from 'oidc-client-rx';
|
} from '@angular/router';
|
||||||
import { firstValueFrom, of } from 'rxjs';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { vi } from 'vitest';
|
import { of } from 'rxjs';
|
||||||
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||||
import { CheckAuthService } from '../auth-state/check-auth.service';
|
import { CheckAuthService } from '../auth-state/check-auth.service';
|
||||||
import { ConfigurationService } from '../config/config.service';
|
import { ConfigurationService } from '../config/config.service';
|
||||||
import { LoginService } from '../login/login.service';
|
import { LoginService } from '../login/login.service';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import {
|
import {
|
||||||
AutoLoginPartialRoutesGuard,
|
AutoLoginPartialRoutesGuard,
|
||||||
autoLoginPartialRoutesGuard,
|
autoLoginPartialRoutesGuard,
|
||||||
@ -19,13 +19,11 @@ import {
|
|||||||
} from './auto-login-partial-routes.guard';
|
} from './auto-login-partial-routes.guard';
|
||||||
import { AutoLoginService } from './auto-login.service';
|
import { AutoLoginService } from './auto-login.service';
|
||||||
|
|
||||||
describe('AutoLoginPartialRoutesGuard', () => {
|
describe(`AutoLoginPartialRoutesGuard`, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [RouterTestingModule],
|
||||||
providers: [
|
providers: [
|
||||||
AutoLoginPartialRoutesGuard,
|
|
||||||
mockRouterProvider(),
|
|
||||||
AutoLoginService,
|
AutoLoginService,
|
||||||
mockProvider(AuthStateService),
|
mockProvider(AuthStateService),
|
||||||
mockProvider(LoginService),
|
mockProvider(LoginService),
|
||||||
@ -43,7 +41,7 @@ describe('AutoLoginPartialRoutesGuard', () => {
|
|||||||
let storagePersistenceService: StoragePersistenceService;
|
let storagePersistenceService: StoragePersistenceService;
|
||||||
let configurationService: ConfigurationService;
|
let configurationService: ConfigurationService;
|
||||||
let autoLoginService: AutoLoginService;
|
let autoLoginService: AutoLoginService;
|
||||||
let router: AbstractRouter;
|
let router: Router;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
authStateService = TestBed.inject(AuthStateService);
|
authStateService = TestBed.inject(AuthStateService);
|
||||||
@ -51,16 +49,15 @@ describe('AutoLoginPartialRoutesGuard', () => {
|
|||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
configurationService = TestBed.inject(ConfigurationService);
|
configurationService = TestBed.inject(ConfigurationService);
|
||||||
|
|
||||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||||
of({ configId: 'configId1' })
|
of({ configId: 'configId1' })
|
||||||
);
|
);
|
||||||
|
|
||||||
guard = TestBed.inject(AutoLoginPartialRoutesGuard);
|
guard = TestBed.inject(AutoLoginPartialRoutesGuard);
|
||||||
autoLoginService = TestBed.inject(AutoLoginService);
|
autoLoginService = TestBed.inject(AutoLoginService);
|
||||||
router = TestBed.inject(AbstractRouter);
|
router = TestBed.inject(Router);
|
||||||
});
|
});
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
storagePersistenceService.clear({});
|
storagePersistenceService.clear({});
|
||||||
});
|
});
|
||||||
@ -70,226 +67,239 @@ describe('AutoLoginPartialRoutesGuard', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('canActivate', () => {
|
describe('canActivate', () => {
|
||||||
it('should save current route and call `login` if not authenticated already', async () => {
|
it('should save current route and call `login` if not authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
await firstValueFrom(
|
guard
|
||||||
guard.canActivate(
|
.canActivate(
|
||||||
{} as ActivatedRouteSnapshot,
|
{} as ActivatedRouteSnapshot,
|
||||||
{ url: 'some-url1' } as RouterStateSnapshot
|
{ url: 'some-url1' } as RouterStateSnapshot
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
'some-url1'
|
'some-url1'
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
expect(loginSpy).toHaveBeenCalledOnceWith({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
expect(
|
||||||
});
|
checkSavedRedirectRouteAndNavigateSpy
|
||||||
|
).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should save current route and call `login` if not authenticated already and add custom params', async () => {
|
it('should save current route and call `login` if not authenticated already and add custom params', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
await firstValueFrom(
|
guard
|
||||||
guard.canActivate(
|
.canActivate(
|
||||||
{ data: { custom: 'param' } } as unknown as ActivatedRouteSnapshot,
|
{ data: { custom: 'param' } } as unknown as ActivatedRouteSnapshot,
|
||||||
{ url: 'some-url1' } as RouterStateSnapshot
|
{ url: 'some-url1' } as RouterStateSnapshot
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
'some-url1'
|
'some-url1'
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(loginSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
{ customParams: { custom: 'param' } }
|
{ customParams: { custom: 'param' } }
|
||||||
);
|
);
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
expect(
|
||||||
});
|
checkSavedRedirectRouteAndNavigateSpy
|
||||||
|
).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
|
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
await firstValueFrom(
|
guard
|
||||||
guard.canActivate(
|
.canActivate(
|
||||||
{} as ActivatedRouteSnapshot,
|
{} as ActivatedRouteSnapshot,
|
||||||
{ url: 'some-url1' } as RouterStateSnapshot
|
{ url: 'some-url1' } as RouterStateSnapshot
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
||||||
expect(loginSpy).not.toHaveBeenCalled();
|
expect(loginSpy).not.toHaveBeenCalled();
|
||||||
expect(
|
expect(
|
||||||
checkSavedRedirectRouteAndNavigateSpy
|
checkSavedRedirectRouteAndNavigateSpy
|
||||||
).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('canActivateChild', () => {
|
describe('canActivateChild', () => {
|
||||||
it('should save current route and call `login` if not authenticated already', async () => {
|
it('should save current route and call `login` if not authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
await firstValueFrom(
|
guard
|
||||||
guard.canActivateChild(
|
.canActivateChild(
|
||||||
{} as ActivatedRouteSnapshot,
|
{} as ActivatedRouteSnapshot,
|
||||||
{ url: 'some-url1' } as RouterStateSnapshot
|
{ url: 'some-url1' } as RouterStateSnapshot
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
'some-url1'
|
'some-url1'
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
expect(loginSpy).toHaveBeenCalledOnceWith({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
expect(
|
||||||
});
|
checkSavedRedirectRouteAndNavigateSpy
|
||||||
|
).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should save current route and call `login` if not authenticated already with custom params', async () => {
|
it('should save current route and call `login` if not authenticated already with custom params', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
await firstValueFrom(
|
guard
|
||||||
guard.canActivateChild(
|
.canActivateChild(
|
||||||
{ data: { custom: 'param' } } as unknown as ActivatedRouteSnapshot,
|
{ data: { custom: 'param' } } as unknown as ActivatedRouteSnapshot,
|
||||||
{ url: 'some-url1' } as RouterStateSnapshot
|
{ url: 'some-url1' } as RouterStateSnapshot
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
'some-url1'
|
'some-url1'
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(loginSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
{ customParams: { custom: 'param' } }
|
{ customParams: { custom: 'param' } }
|
||||||
);
|
);
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
expect(
|
||||||
});
|
checkSavedRedirectRouteAndNavigateSpy
|
||||||
|
).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
|
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
await firstValueFrom(
|
guard
|
||||||
guard.canActivateChild(
|
.canActivateChild(
|
||||||
{} as ActivatedRouteSnapshot,
|
{} as ActivatedRouteSnapshot,
|
||||||
{ url: 'some-url1' } as RouterStateSnapshot
|
{ url: 'some-url1' } as RouterStateSnapshot
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
||||||
expect(loginSpy).not.toHaveBeenCalled();
|
expect(loginSpy).not.toHaveBeenCalled();
|
||||||
expect(
|
expect(
|
||||||
checkSavedRedirectRouteAndNavigateSpy
|
checkSavedRedirectRouteAndNavigateSpy
|
||||||
).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('canLoad', () => {
|
describe('canLoad', () => {
|
||||||
it('should save current route (empty) and call `login` if not authenticated already', async () => {
|
it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
await firstValueFrom(guard.canLoad());
|
guard.canLoad().subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
configId: 'configId1',
|
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', async () => {
|
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
vi.spyOn(router, 'getCurrentNavigation').mockReturnValue({
|
spyOn(router, 'getCurrentNavigation').and.returnValue({
|
||||||
extractedUrl: router.parseUrl(
|
extractedUrl: router.parseUrl(
|
||||||
'some-url12/with/some-param?queryParam=true'
|
'some-url12/with/some-param?queryParam=true'
|
||||||
),
|
),
|
||||||
@ -300,38 +310,38 @@ describe('AutoLoginPartialRoutesGuard', () => {
|
|||||||
trigger: 'imperative',
|
trigger: 'imperative',
|
||||||
});
|
});
|
||||||
|
|
||||||
await firstValueFrom(guard.canLoad());
|
guard.canLoad().subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
'some-url12/with/some-param?queryParam=true'
|
'some-url12/with/some-param?queryParam=true'
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
configId: 'configId1',
|
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
|
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
await firstValueFrom(guard.canLoad());
|
guard.canLoad().subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
||||||
expect(loginSpy).not.toHaveBeenCalled();
|
expect(loginSpy).not.toHaveBeenCalled();
|
||||||
expect(
|
expect(
|
||||||
checkSavedRedirectRouteAndNavigateSpy
|
checkSavedRedirectRouteAndNavigateSpy
|
||||||
).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -342,7 +352,7 @@ describe('AutoLoginPartialRoutesGuard', () => {
|
|||||||
let storagePersistenceService: StoragePersistenceService;
|
let storagePersistenceService: StoragePersistenceService;
|
||||||
let configurationService: ConfigurationService;
|
let configurationService: ConfigurationService;
|
||||||
let autoLoginService: AutoLoginService;
|
let autoLoginService: AutoLoginService;
|
||||||
let router: AbstractRouter;
|
let router: Router;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
authStateService = TestBed.inject(AuthStateService);
|
authStateService = TestBed.inject(AuthStateService);
|
||||||
@ -350,51 +360,48 @@ describe('AutoLoginPartialRoutesGuard', () => {
|
|||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
configurationService = TestBed.inject(ConfigurationService);
|
configurationService = TestBed.inject(ConfigurationService);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||||
configurationService,
|
of({ configId: 'configId1' })
|
||||||
'getOpenIDConfiguration'
|
);
|
||||||
).mockReturnValue(of({ configId: 'configId1' }));
|
|
||||||
|
|
||||||
autoLoginService = TestBed.inject(AutoLoginService);
|
autoLoginService = TestBed.inject(AutoLoginService);
|
||||||
router = TestBed.inject(AbstractRouter);
|
router = TestBed.inject(Router);
|
||||||
});
|
});
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
storagePersistenceService.clear({});
|
storagePersistenceService.clear({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save current route (empty) and call `login` if not authenticated already', async () => {
|
it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
const guard$ = TestBed.runInInjectionContext(
|
const guard$ = TestBed.runInInjectionContext(
|
||||||
autoLoginPartialRoutesGuard
|
autoLoginPartialRoutesGuard
|
||||||
);
|
);
|
||||||
|
|
||||||
await firstValueFrom(guard$);
|
guard$.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
configId: 'configId1',
|
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', async () => {
|
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(router, 'getCurrentNavigation').mockReturnValue({
|
spyOn(router, 'getCurrentNavigation').and.returnValue({
|
||||||
extractedUrl: router.parseUrl(
|
extractedUrl: router.parseUrl(
|
||||||
'some-url12/with/some-param?queryParam=true'
|
'some-url12/with/some-param?queryParam=true'
|
||||||
),
|
),
|
||||||
@ -405,47 +412,46 @@ describe('AutoLoginPartialRoutesGuard', () => {
|
|||||||
trigger: 'imperative',
|
trigger: 'imperative',
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
const guard$ = TestBed.runInInjectionContext(
|
const guard$ = TestBed.runInInjectionContext(
|
||||||
autoLoginPartialRoutesGuard
|
autoLoginPartialRoutesGuard
|
||||||
);
|
);
|
||||||
|
|
||||||
await firstValueFrom(guard$);
|
guard$.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
'some-url12/with/some-param?queryParam=true'
|
'some-url12/with/some-param?queryParam=true'
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
configId: 'configId1',
|
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('should save current route and call `login` if not authenticated already and add custom params', async () => {
|
it('should save current route and call `login` if not authenticated already and add custom params', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
const guard$ = TestBed.runInInjectionContext(() =>
|
const guard$ = TestBed.runInInjectionContext(() =>
|
||||||
autoLoginPartialRoutesGuard({
|
autoLoginPartialRoutesGuard({
|
||||||
@ -453,43 +459,45 @@ describe('AutoLoginPartialRoutesGuard', () => {
|
|||||||
} as unknown as ActivatedRouteSnapshot)
|
} as unknown as ActivatedRouteSnapshot)
|
||||||
);
|
);
|
||||||
|
|
||||||
await firstValueFrom(guard$);
|
guard$.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(loginSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
{ customParams: { custom: 'param' } }
|
{ customParams: { custom: 'param' } }
|
||||||
);
|
);
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
|
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
const guard$ = TestBed.runInInjectionContext(
|
const guard$ = TestBed.runInInjectionContext(
|
||||||
autoLoginPartialRoutesGuard
|
autoLoginPartialRoutesGuard
|
||||||
);
|
);
|
||||||
|
|
||||||
await firstValueFrom(guard$);
|
guard$.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
||||||
expect(loginSpy).not.toHaveBeenCalled();
|
expect(loginSpy).not.toHaveBeenCalled();
|
||||||
expect(
|
expect(
|
||||||
checkSavedRedirectRouteAndNavigateSpy
|
checkSavedRedirectRouteAndNavigateSpy
|
||||||
).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('autoLoginPartialRoutesGuardWithConfig', () => {
|
describe('autoLoginPartialRoutesGuardWithConfig', () => {
|
||||||
@ -505,47 +513,44 @@ describe('AutoLoginPartialRoutesGuard', () => {
|
|||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
configurationService = TestBed.inject(ConfigurationService);
|
configurationService = TestBed.inject(ConfigurationService);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(configurationService, 'getOpenIDConfiguration').and.callFake(
|
||||||
configurationService,
|
(configId) => of({ configId })
|
||||||
'getOpenIDConfiguration'
|
);
|
||||||
).mockImplementation((configId) => of({ configId }));
|
|
||||||
|
|
||||||
autoLoginService = TestBed.inject(AutoLoginService);
|
autoLoginService = TestBed.inject(AutoLoginService);
|
||||||
});
|
});
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
storagePersistenceService.clear({});
|
storagePersistenceService.clear({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save current route (empty) and call `login` if not authenticated already', async () => {
|
it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => {
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'checkSavedRedirectRouteAndNavigate'
|
'checkSavedRedirectRouteAndNavigate'
|
||||||
);
|
);
|
||||||
const saveRedirectRouteSpy = vi.spyOn(
|
const saveRedirectRouteSpy = spyOn(
|
||||||
autoLoginService,
|
autoLoginService,
|
||||||
'saveRedirectRoute'
|
'saveRedirectRoute'
|
||||||
);
|
);
|
||||||
const loginSpy = vi.spyOn(loginService, 'login');
|
const loginSpy = spyOn(loginService, 'login');
|
||||||
|
|
||||||
const guard$ = TestBed.runInInjectionContext(
|
const guard$ = TestBed.runInInjectionContext(
|
||||||
autoLoginPartialRoutesGuardWithConfig('configId1')
|
autoLoginPartialRoutesGuardWithConfig('configId1')
|
||||||
);
|
);
|
||||||
|
|
||||||
await firstValueFrom(guard$);
|
guard$.subscribe(() => {
|
||||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||||
configId: 'configId1',
|
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
}));
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import type { Observable } from 'rxjs';
|
import {
|
||||||
|
ActivatedRouteSnapshot,
|
||||||
|
Router,
|
||||||
|
RouterStateSnapshot,
|
||||||
|
} from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import type { AuthOptions } from '../auth-options';
|
import { AuthOptions } from '../auth-options';
|
||||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||||
import { ConfigurationService } from '../config/config.service';
|
import { ConfigurationService } from '../config/config.service';
|
||||||
import { injectAbstractType } from '../injection';
|
|
||||||
import { LoginService } from '../login/login.service';
|
import { LoginService } from '../login/login.service';
|
||||||
import {
|
|
||||||
AbstractRouter,
|
|
||||||
type ActivatedRouteSnapshot,
|
|
||||||
type RouterStateSnapshot,
|
|
||||||
} from '../router';
|
|
||||||
import { AutoLoginService } from './auto-login.service';
|
import { AutoLoginService } from './auto-login.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -23,7 +22,7 @@ export class AutoLoginPartialRoutesGuard {
|
|||||||
|
|
||||||
private readonly configurationService = inject(ConfigurationService);
|
private readonly configurationService = inject(ConfigurationService);
|
||||||
|
|
||||||
private readonly router = injectAbstractType(AbstractRouter);
|
private readonly router = inject(Router);
|
||||||
|
|
||||||
canLoad(): Observable<boolean> {
|
canLoad(): Observable<boolean> {
|
||||||
const url =
|
const url =
|
||||||
@ -80,14 +79,14 @@ export class AutoLoginPartialRoutesGuard {
|
|||||||
|
|
||||||
export function autoLoginPartialRoutesGuard(
|
export function autoLoginPartialRoutesGuard(
|
||||||
route?: ActivatedRouteSnapshot,
|
route?: ActivatedRouteSnapshot,
|
||||||
_state?: RouterStateSnapshot,
|
state?: RouterStateSnapshot,
|
||||||
configId?: string
|
configId?: string
|
||||||
): Observable<boolean> {
|
): Observable<boolean> {
|
||||||
const configurationService = inject(ConfigurationService);
|
const configurationService = inject(ConfigurationService);
|
||||||
const authStateService = inject(AuthStateService);
|
const authStateService = inject(AuthStateService);
|
||||||
const loginService = inject(LoginService);
|
const loginService = inject(LoginService);
|
||||||
const autoLoginService = inject(AutoLoginService);
|
const autoLoginService = inject(AutoLoginService);
|
||||||
const router = injectAbstractType(AbstractRouter);
|
const router = inject(Router);
|
||||||
const authOptions: AuthOptions | undefined = route?.data
|
const authOptions: AuthOptions | undefined = route?.data
|
||||||
? { customParams: route.data }
|
? { customParams: route.data }
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|||||||
@ -1,25 +1,24 @@
|
|||||||
import { TestBed, mockRouterProvider } from '@/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { AbstractRouter } from 'oidc-client-rx/router';
|
import { Router } from '@angular/router';
|
||||||
import { vi } from 'vitest';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { AutoLoginService } from './auto-login.service';
|
import { AutoLoginService } from './auto-login.service';
|
||||||
|
|
||||||
describe('AutoLoginService ', () => {
|
describe('AutoLoginService ', () => {
|
||||||
let autoLoginService: AutoLoginService;
|
let autoLoginService: AutoLoginService;
|
||||||
let storagePersistenceService: StoragePersistenceService;
|
let storagePersistenceService: StoragePersistenceService;
|
||||||
let router: AbstractRouter;
|
let router: Router;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [RouterTestingModule],
|
||||||
providers: [
|
providers: [AutoLoginService, mockProvider(StoragePersistenceService)],
|
||||||
mockRouterProvider(),
|
|
||||||
AutoLoginService,
|
|
||||||
mockProvider(StoragePersistenceService),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
router = TestBed.inject(AbstractRouter);
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
router = TestBed.inject(Router);
|
||||||
autoLoginService = TestBed.inject(AutoLoginService);
|
autoLoginService = TestBed.inject(AutoLoginService);
|
||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
});
|
});
|
||||||
@ -30,11 +29,11 @@ describe('AutoLoginService ', () => {
|
|||||||
|
|
||||||
describe('checkSavedRedirectRouteAndNavigate', () => {
|
describe('checkSavedRedirectRouteAndNavigate', () => {
|
||||||
it('if not route is saved, router and delete are not called', () => {
|
it('if not route is saved, router and delete are not called', () => {
|
||||||
const deleteSpy = vi.spyOn(storagePersistenceService, 'remove');
|
const deleteSpy = spyOn(storagePersistenceService, 'remove');
|
||||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||||
const readSpy = vi
|
const readSpy = spyOn(storagePersistenceService, 'read').and.returnValue(
|
||||||
.spyOn(storagePersistenceService, 'read')
|
null
|
||||||
.mockReturnValue(null);
|
);
|
||||||
|
|
||||||
autoLoginService.checkSavedRedirectRouteAndNavigate({
|
autoLoginService.checkSavedRedirectRouteAndNavigate({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -42,27 +41,27 @@ describe('AutoLoginService ', () => {
|
|||||||
|
|
||||||
expect(deleteSpy).not.toHaveBeenCalled();
|
expect(deleteSpy).not.toHaveBeenCalled();
|
||||||
expect(routerSpy).not.toHaveBeenCalled();
|
expect(routerSpy).not.toHaveBeenCalled();
|
||||||
expect(readSpy).toHaveBeenCalledExactlyOnceWith('redirect', {
|
expect(readSpy).toHaveBeenCalledOnceWith('redirect', {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if route is saved, router and delete are called', () => {
|
it('if route is saved, router and delete are called', () => {
|
||||||
const deleteSpy = vi.spyOn(storagePersistenceService, 'remove');
|
const deleteSpy = spyOn(storagePersistenceService, 'remove');
|
||||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||||
const readSpy = vi
|
const readSpy = spyOn(storagePersistenceService, 'read').and.returnValue(
|
||||||
.spyOn(storagePersistenceService, 'read')
|
'saved-route'
|
||||||
.mockReturnValue('saved-route');
|
);
|
||||||
|
|
||||||
autoLoginService.checkSavedRedirectRouteAndNavigate({
|
autoLoginService.checkSavedRedirectRouteAndNavigate({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(deleteSpy).toHaveBeenCalledExactlyOnceWith('redirect', {
|
expect(deleteSpy).toHaveBeenCalledOnceWith('redirect', {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('saved-route');
|
expect(routerSpy).toHaveBeenCalledOnceWith('saved-route');
|
||||||
expect(readSpy).toHaveBeenCalledExactlyOnceWith('redirect', {
|
expect(readSpy).toHaveBeenCalledOnceWith('redirect', {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -70,20 +69,16 @@ describe('AutoLoginService ', () => {
|
|||||||
|
|
||||||
describe('saveRedirectRoute', () => {
|
describe('saveRedirectRoute', () => {
|
||||||
it('calls storageService with correct params', () => {
|
it('calls storageService with correct params', () => {
|
||||||
const writeSpy = vi.spyOn(storagePersistenceService, 'write');
|
const writeSpy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
autoLoginService.saveRedirectRoute(
|
autoLoginService.saveRedirectRoute(
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
'some-route'
|
'some-route'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(writeSpy).toHaveBeenCalledOnceWith('redirect', 'some-route', {
|
||||||
'redirect',
|
configId: 'configId1',
|
||||||
'some-route',
|
});
|
||||||
{
|
|
||||||
configId: 'configId1',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { Router } from '@angular/router';
|
||||||
import { injectAbstractType } from '../injection';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import { AbstractRouter } from '../router';
|
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
|
|
||||||
const STORAGE_KEY = 'redirect';
|
const STORAGE_KEY = 'redirect';
|
||||||
@ -10,7 +9,7 @@ const STORAGE_KEY = 'redirect';
|
|||||||
export class AutoLoginService {
|
export class AutoLoginService {
|
||||||
private readonly storageService = inject(StoragePersistenceService);
|
private readonly storageService = inject(StoragePersistenceService);
|
||||||
|
|
||||||
private readonly router = injectAbstractType(AbstractRouter);
|
private readonly router = inject(Router);
|
||||||
|
|
||||||
checkSavedRedirectRouteAndNavigate(config: OpenIdConfiguration | null): void {
|
checkSavedRedirectRouteAndNavigate(config: OpenIdConfiguration | null): void {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { Observable, firstValueFrom, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||||
import { UrlService } from '../utils/url/url.service';
|
import { UrlService } from '../utils/url/url.service';
|
||||||
import { CallbackService } from './callback.service';
|
import { CallbackService } from './callback.service';
|
||||||
@ -27,6 +26,9 @@ describe('CallbackService ', () => {
|
|||||||
mockProvider(CodeFlowCallbackService),
|
mockProvider(CodeFlowCallbackService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
callbackService = TestBed.inject(CallbackService);
|
callbackService = TestBed.inject(CallbackService);
|
||||||
flowHelper = TestBed.inject(FlowHelper);
|
flowHelper = TestBed.inject(FlowHelper);
|
||||||
implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService);
|
implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService);
|
||||||
@ -36,13 +38,10 @@ describe('CallbackService ', () => {
|
|||||||
|
|
||||||
describe('isCallback', () => {
|
describe('isCallback', () => {
|
||||||
it('calls urlService.isCallbackFromSts with passed url', () => {
|
it('calls urlService.isCallbackFromSts with passed url', () => {
|
||||||
const urlServiceSpy = vi.spyOn(urlService, 'isCallbackFromSts');
|
const urlServiceSpy = spyOn(urlService, 'isCallbackFromSts');
|
||||||
|
|
||||||
callbackService.isCallback('anyUrl');
|
callbackService.isCallback('anyUrl');
|
||||||
expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(urlServiceSpy).toHaveBeenCalledOnceWith('anyUrl', undefined);
|
||||||
'anyUrl',
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,95 +52,93 @@ describe('CallbackService ', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('handleCallbackAndFireEvents', () => {
|
describe('handleCallbackAndFireEvents', () => {
|
||||||
it('calls authorizedCallbackWithCode if current flow is code flow', async () => {
|
it('calls authorizedCallbackWithCode if current flow is code flow', waitForAsync(() => {
|
||||||
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
|
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
|
||||||
const authorizedCallbackWithCodeSpy = vi
|
const authorizedCallbackWithCodeSpy = spyOn(
|
||||||
.spyOn(codeFlowCallbackService, 'authenticatedCallbackWithCode')
|
codeFlowCallbackService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'authenticatedCallbackWithCode'
|
||||||
|
).and.returnValue(of({} as CallbackContext));
|
||||||
|
|
||||||
await firstValueFrom(
|
callbackService
|
||||||
callbackService.handleCallbackAndFireEvents(
|
.handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [
|
||||||
'anyUrl',
|
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
[{ configId: 'configId1' }]
|
])
|
||||||
)
|
.subscribe(() => {
|
||||||
);
|
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledOnceWith(
|
||||||
|
'anyUrl',
|
||||||
|
{ configId: 'configId1' },
|
||||||
|
[{ configId: 'configId1' }]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledExactlyOnceWith(
|
it('calls authorizedImplicitFlowCallback without hash if current flow is implicit flow and callbackurl does not include a hash', waitForAsync(() => {
|
||||||
'anyUrl',
|
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false);
|
||||||
{ configId: 'configId1' },
|
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true);
|
||||||
[{ configId: 'configId1' }]
|
const authorizedCallbackWithCodeSpy = spyOn(
|
||||||
);
|
implicitFlowCallbackService,
|
||||||
});
|
'authenticatedImplicitFlowCallback'
|
||||||
|
).and.returnValue(of({} as CallbackContext));
|
||||||
|
|
||||||
it('calls authorizedImplicitFlowCallback without hash if current flow is implicit flow and callbackurl does not include a hash', async () => {
|
callbackService
|
||||||
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
|
.handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [
|
||||||
vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
|
|
||||||
true
|
|
||||||
);
|
|
||||||
const authorizedCallbackWithCodeSpy = vi
|
|
||||||
.spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback')
|
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
|
||||||
|
|
||||||
await firstValueFrom(
|
|
||||||
callbackService.handleCallbackAndFireEvents(
|
|
||||||
'anyUrl',
|
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
[{ configId: 'configId1' }]
|
])
|
||||||
)
|
.subscribe(() => {
|
||||||
);
|
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith(
|
||||||
expect(authorizedCallbackWithCodeSpy.mock.calls).toEqual([
|
{ configId: 'configId1' },
|
||||||
[{ configId: 'configId1' }, [{ configId: 'configId1' }]],
|
[{ configId: 'configId1' }]
|
||||||
]);
|
);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls authorizedImplicitFlowCallback with hash if current flow is implicit flow and callbackurl does include a hash', async () => {
|
it('calls authorizedImplicitFlowCallback with hash if current flow is implicit flow and callbackurl does include a hash', waitForAsync(() => {
|
||||||
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
|
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false);
|
||||||
vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
|
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true);
|
||||||
true
|
const authorizedCallbackWithCodeSpy = spyOn(
|
||||||
);
|
implicitFlowCallbackService,
|
||||||
const authorizedCallbackWithCodeSpy = vi
|
'authenticatedImplicitFlowCallback'
|
||||||
.spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback')
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
|
||||||
|
|
||||||
await firstValueFrom(
|
callbackService
|
||||||
callbackService.handleCallbackAndFireEvents(
|
.handleCallbackAndFireEvents(
|
||||||
'anyUrlWithAHash#some-string',
|
'anyUrlWithAHash#some-string',
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
[{ configId: 'configId1' }]
|
[{ configId: 'configId1' }]
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
|
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith(
|
||||||
|
{ configId: 'configId1' },
|
||||||
|
[{ configId: 'configId1' }],
|
||||||
|
'some-string'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
expect(authorizedCallbackWithCodeSpy.mock.calls).toEqual([
|
it('emits callbackinternal no matter which flow it is', waitForAsync(() => {
|
||||||
[{ configId: 'configId1' }, [{ configId: 'configId1' }], 'some-string'],
|
const callbackSpy = spyOn(
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('emits callbackinternal no matter which flow it is', async () => {
|
|
||||||
const callbackSpy = vi.spyOn(
|
|
||||||
(callbackService as any).stsCallbackInternal$,
|
(callbackService as any).stsCallbackInternal$,
|
||||||
'next'
|
'next'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
|
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
|
||||||
const authenticatedCallbackWithCodeSpy = vi
|
const authenticatedCallbackWithCodeSpy = spyOn(
|
||||||
.spyOn(codeFlowCallbackService, 'authenticatedCallbackWithCode')
|
codeFlowCallbackService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'authenticatedCallbackWithCode'
|
||||||
|
).and.returnValue(of({} as CallbackContext));
|
||||||
|
|
||||||
await firstValueFrom(
|
callbackService
|
||||||
callbackService.handleCallbackAndFireEvents(
|
.handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [
|
||||||
'anyUrl',
|
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
[{ configId: 'configId1' }]
|
])
|
||||||
)
|
.subscribe(() => {
|
||||||
);
|
expect(authenticatedCallbackWithCodeSpy).toHaveBeenCalledOnceWith(
|
||||||
|
'anyUrl',
|
||||||
expect(authenticatedCallbackWithCodeSpy).toHaveBeenCalledExactlyOnceWith(
|
{ configId: 'configId1' },
|
||||||
'anyUrl',
|
[{ configId: 'configId1' }]
|
||||||
{ configId: 'configId1' },
|
);
|
||||||
[{ configId: 'configId1' }]
|
expect(callbackSpy).toHaveBeenCalled();
|
||||||
);
|
});
|
||||||
expect(callbackSpy).toHaveBeenCalled();
|
}));
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { tap } from 'rxjs/operators';
|
import { tap } from 'rxjs/operators';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||||
import { UrlService } from '../utils/url/url.service';
|
import { UrlService } from '../utils/url/url.service';
|
||||||
import { CodeFlowCallbackService } from './code-flow-callback.service';
|
import { CodeFlowCallbackService } from './code-flow-callback.service';
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { TestBed, mockRouterProvider } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { AbstractRouter } from 'oidc-client-rx';
|
import { Router } from '@angular/router';
|
||||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { vi } from 'vitest';
|
import { of, throwError } from 'rxjs';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsDataService } from '../flows/flows-data.service';
|
import { FlowsDataService } from '../flows/flows-data.service';
|
||||||
import { FlowsService } from '../flows/flows.service';
|
import { FlowsService } from '../flows/flows.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { CodeFlowCallbackService } from './code-flow-callback.service';
|
import { CodeFlowCallbackService } from './code-flow-callback.service';
|
||||||
import { IntervalService } from './interval.service';
|
import { IntervalService } from './interval.service';
|
||||||
|
|
||||||
@ -14,24 +14,26 @@ describe('CodeFlowCallbackService ', () => {
|
|||||||
let intervalService: IntervalService;
|
let intervalService: IntervalService;
|
||||||
let flowsService: FlowsService;
|
let flowsService: FlowsService;
|
||||||
let flowsDataService: FlowsDataService;
|
let flowsDataService: FlowsDataService;
|
||||||
let router: AbstractRouter;
|
let router: Router;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [RouterTestingModule],
|
||||||
providers: [
|
providers: [
|
||||||
mockRouterProvider(),
|
|
||||||
CodeFlowCallbackService,
|
CodeFlowCallbackService,
|
||||||
mockProvider(FlowsService),
|
mockProvider(FlowsService),
|
||||||
mockProvider(FlowsDataService),
|
mockProvider(FlowsDataService),
|
||||||
mockProvider(IntervalService),
|
mockProvider(IntervalService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
codeFlowCallbackService = TestBed.inject(CodeFlowCallbackService);
|
codeFlowCallbackService = TestBed.inject(CodeFlowCallbackService);
|
||||||
intervalService = TestBed.inject(IntervalService);
|
intervalService = TestBed.inject(IntervalService);
|
||||||
flowsDataService = TestBed.inject(FlowsDataService);
|
flowsDataService = TestBed.inject(FlowsDataService);
|
||||||
flowsService = TestBed.inject(FlowsService);
|
flowsService = TestBed.inject(FlowsService);
|
||||||
router = TestBed.inject(AbstractRouter);
|
router = TestBed.inject(Router);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
@ -40,10 +42,11 @@ describe('CodeFlowCallbackService ', () => {
|
|||||||
|
|
||||||
describe('authenticatedCallbackWithCode', () => {
|
describe('authenticatedCallbackWithCode', () => {
|
||||||
it('calls flowsService.processCodeFlowCallback with correct url', () => {
|
it('calls flowsService.processCodeFlowCallback with correct url', () => {
|
||||||
const spy = vi
|
const spy = spyOn(
|
||||||
.spyOn(flowsService, 'processCodeFlowCallback')
|
flowsService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'processCodeFlowCallback'
|
||||||
//spyOn(configurationProvider, 'getOpenIDConfiguration').mockReturnValue({ triggerAuthorizationResultEvent: true });
|
).and.returnValue(of({} as CallbackContext));
|
||||||
|
//spyOn(configurationProvider, 'getOpenIDConfiguration').and.returnValue({ triggerAuthorizationResultEvent: true });
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -55,12 +58,10 @@ describe('CodeFlowCallbackService ', () => {
|
|||||||
config,
|
config,
|
||||||
[config]
|
[config]
|
||||||
);
|
);
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith('some-url1', config, [
|
expect(spy).toHaveBeenCalledOnceWith('some-url1', config, [config]);
|
||||||
config,
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does only call resetCodeFlowInProgress if triggerAuthorizationResultEvent is true and isRenewProcess is true', async () => {
|
it('does only call resetCodeFlowInProgress if triggerAuthorizationResultEvent is true and isRenewProcess is true', waitForAsync(() => {
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
code: '',
|
code: '',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
@ -72,34 +73,27 @@ describe('CodeFlowCallbackService ', () => {
|
|||||||
validationResult: null,
|
validationResult: null,
|
||||||
existingIdToken: '',
|
existingIdToken: '',
|
||||||
};
|
};
|
||||||
const spy = vi
|
const spy = spyOn(
|
||||||
.spyOn(flowsService, 'processCodeFlowCallback')
|
flowsService,
|
||||||
.mockReturnValue(of(callbackContext));
|
'processCodeFlowCallback'
|
||||||
const flowsDataSpy = vi.spyOn(
|
).and.returnValue(of(callbackContext));
|
||||||
flowsDataService,
|
const flowsDataSpy = spyOn(flowsDataService, 'resetCodeFlowInProgress');
|
||||||
'resetCodeFlowInProgress'
|
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||||
);
|
|
||||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
triggerAuthorizationResultEvent: true,
|
triggerAuthorizationResultEvent: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
await firstValueFrom(
|
codeFlowCallbackService
|
||||||
codeFlowCallbackService.authenticatedCallbackWithCode(
|
.authenticatedCallbackWithCode('some-url2', config, [config])
|
||||||
'some-url2',
|
.subscribe(() => {
|
||||||
config,
|
expect(spy).toHaveBeenCalledOnceWith('some-url2', config, [config]);
|
||||||
[config]
|
expect(routerSpy).not.toHaveBeenCalled();
|
||||||
)
|
expect(flowsDataSpy).toHaveBeenCalled();
|
||||||
);
|
});
|
||||||
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', async () => {
|
it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', waitForAsync(() => {
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
code: '',
|
code: '',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
@ -111,47 +105,40 @@ describe('CodeFlowCallbackService ', () => {
|
|||||||
validationResult: null,
|
validationResult: null,
|
||||||
existingIdToken: '',
|
existingIdToken: '',
|
||||||
};
|
};
|
||||||
const spy = vi
|
const spy = spyOn(
|
||||||
.spyOn(flowsService, 'processCodeFlowCallback')
|
flowsService,
|
||||||
.mockReturnValue(of(callbackContext));
|
'processCodeFlowCallback'
|
||||||
const flowsDataSpy = vi.spyOn(
|
).and.returnValue(of(callbackContext));
|
||||||
flowsDataService,
|
const flowsDataSpy = spyOn(flowsDataService, 'resetCodeFlowInProgress');
|
||||||
'resetCodeFlowInProgress'
|
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||||
);
|
|
||||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
triggerAuthorizationResultEvent: false,
|
triggerAuthorizationResultEvent: false,
|
||||||
postLoginRoute: 'postLoginRoute',
|
postLoginRoute: 'postLoginRoute',
|
||||||
};
|
};
|
||||||
|
|
||||||
await firstValueFrom(
|
codeFlowCallbackService
|
||||||
codeFlowCallbackService.authenticatedCallbackWithCode(
|
.authenticatedCallbackWithCode('some-url3', config, [config])
|
||||||
'some-url3',
|
.subscribe(() => {
|
||||||
config,
|
expect(spy).toHaveBeenCalledOnceWith('some-url3', config, [config]);
|
||||||
[config]
|
expect(routerSpy).toHaveBeenCalledOnceWith('postLoginRoute');
|
||||||
)
|
expect(flowsDataSpy).toHaveBeenCalled();
|
||||||
);
|
});
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith('some-url3', config, [
|
}));
|
||||||
config,
|
|
||||||
]);
|
|
||||||
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
|
|
||||||
expect(flowsDataSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', async () => {
|
it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', waitForAsync(() => {
|
||||||
vi.spyOn(flowsService, 'processCodeFlowCallback').mockReturnValue(
|
spyOn(flowsService, 'processCodeFlowCallback').and.returnValue(
|
||||||
throwError(() => new Error('error'))
|
throwError(() => new Error('error'))
|
||||||
);
|
);
|
||||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
const resetSilentRenewRunningSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetSilentRenewRunning'
|
'resetSilentRenewRunning'
|
||||||
);
|
);
|
||||||
const resetCodeFlowInProgressSpy = vi.spyOn(
|
const resetCodeFlowInProgressSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetCodeFlowInProgress'
|
'resetCodeFlowInProgress'
|
||||||
);
|
);
|
||||||
const stopPeriodicallTokenCheckSpy = vi.spyOn(
|
const stopPeriodicallTokenCheckSpy = spyOn(
|
||||||
intervalService,
|
intervalService,
|
||||||
'stopPeriodicTokenCheck'
|
'stopPeriodicTokenCheck'
|
||||||
);
|
);
|
||||||
@ -162,37 +149,33 @@ describe('CodeFlowCallbackService ', () => {
|
|||||||
postLoginRoute: 'postLoginRoute',
|
postLoginRoute: 'postLoginRoute',
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
codeFlowCallbackService
|
||||||
await firstValueFrom(
|
.authenticatedCallbackWithCode('some-url4', config, [config])
|
||||||
codeFlowCallbackService.authenticatedCallbackWithCode(
|
.subscribe({
|
||||||
'some-url4',
|
error: (err) => {
|
||||||
config,
|
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||||
[config]
|
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
|
||||||
)
|
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
||||||
);
|
expect(err).toBeTruthy();
|
||||||
} catch (err: any) {
|
},
|
||||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
});
|
||||||
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
|
}));
|
||||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
|
||||||
expect(err).toBeTruthy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`navigates to unauthorizedRoute in case of error and in case of error and
|
it(`navigates to unauthorizedRoute in case of error and in case of error and
|
||||||
triggerAuthorizationResultEvent is false`, async () => {
|
triggerAuthorizationResultEvent is false`, waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(flowsService, 'processCodeFlowCallback').mockReturnValue(
|
spyOn(flowsService, 'processCodeFlowCallback').and.returnValue(
|
||||||
throwError(() => new Error('error'))
|
throwError(() => new Error('error'))
|
||||||
);
|
);
|
||||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
const resetSilentRenewRunningSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetSilentRenewRunning'
|
'resetSilentRenewRunning'
|
||||||
);
|
);
|
||||||
const stopPeriodicallTokenCheckSpy = vi.spyOn(
|
const stopPeriodicallTokenCheckSpy = spyOn(
|
||||||
intervalService,
|
intervalService,
|
||||||
'stopPeriodicTokenCheck'
|
'stopPeriodicTokenCheck'
|
||||||
);
|
);
|
||||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -200,20 +183,16 @@ describe('CodeFlowCallbackService ', () => {
|
|||||||
unauthorizedRoute: 'unauthorizedRoute',
|
unauthorizedRoute: 'unauthorizedRoute',
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
codeFlowCallbackService
|
||||||
await firstValueFrom(
|
.authenticatedCallbackWithCode('some-url5', config, [config])
|
||||||
codeFlowCallbackService.authenticatedCallbackWithCode(
|
.subscribe({
|
||||||
'some-url5',
|
error: (err) => {
|
||||||
config,
|
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||||
[config]
|
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
||||||
)
|
expect(err).toBeTruthy();
|
||||||
);
|
expect(routerSpy).toHaveBeenCalledOnceWith('unauthorizedRoute');
|
||||||
} catch (err: any) {
|
},
|
||||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
});
|
||||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
}));
|
||||||
expect(err).toBeTruthy();
|
|
||||||
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('unauthorizedRoute');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, throwError } from 'rxjs';
|
import { Router } from '@angular/router';
|
||||||
|
import { Observable, throwError } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsDataService } from '../flows/flows-data.service';
|
import { FlowsDataService } from '../flows/flows-data.service';
|
||||||
import { FlowsService } from '../flows/flows.service';
|
import { FlowsService } from '../flows/flows.service';
|
||||||
import { injectAbstractType } from '../injection';
|
|
||||||
import { AbstractRouter } from '../router';
|
|
||||||
import { IntervalService } from './interval.service';
|
import { IntervalService } from './interval.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CodeFlowCallbackService {
|
export class CodeFlowCallbackService {
|
||||||
private readonly flowsService = inject(FlowsService);
|
private readonly flowsService = inject(FlowsService);
|
||||||
|
|
||||||
private readonly router = injectAbstractType(AbstractRouter);
|
private readonly router = inject(Router);
|
||||||
|
|
||||||
private readonly flowsDataService = inject(FlowsDataService);
|
private readonly flowsDataService = inject(FlowsDataService);
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { TestBed, mockRouterProvider } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { AbstractRouter } from 'oidc-client-rx';
|
import { Router } from '@angular/router';
|
||||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { vi } from 'vitest';
|
import { of, throwError } from 'rxjs';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsDataService } from '../flows/flows-data.service';
|
import { FlowsDataService } from '../flows/flows-data.service';
|
||||||
import { FlowsService } from '../flows/flows.service';
|
import { FlowsService } from '../flows/flows.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { ImplicitFlowCallbackService } from './implicit-flow-callback.service';
|
import { ImplicitFlowCallbackService } from './implicit-flow-callback.service';
|
||||||
import { IntervalService } from './interval.service';
|
import { IntervalService } from './interval.service';
|
||||||
|
|
||||||
@ -14,24 +14,25 @@ describe('ImplicitFlowCallbackService ', () => {
|
|||||||
let intervalService: IntervalService;
|
let intervalService: IntervalService;
|
||||||
let flowsService: FlowsService;
|
let flowsService: FlowsService;
|
||||||
let flowsDataService: FlowsDataService;
|
let flowsDataService: FlowsDataService;
|
||||||
let router: AbstractRouter;
|
let router: Router;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [RouterTestingModule],
|
||||||
providers: [
|
providers: [
|
||||||
ImplicitFlowCallbackService,
|
|
||||||
mockRouterProvider(),
|
|
||||||
mockProvider(FlowsService),
|
mockProvider(FlowsService),
|
||||||
mockProvider(FlowsDataService),
|
mockProvider(FlowsDataService),
|
||||||
mockProvider(IntervalService),
|
mockProvider(IntervalService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService);
|
implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService);
|
||||||
intervalService = TestBed.inject(IntervalService);
|
intervalService = TestBed.inject(IntervalService);
|
||||||
flowsDataService = TestBed.inject(FlowsDataService);
|
flowsDataService = TestBed.inject(FlowsDataService);
|
||||||
flowsService = TestBed.inject(FlowsService);
|
flowsService = TestBed.inject(FlowsService);
|
||||||
router = TestBed.inject(AbstractRouter);
|
router = TestBed.inject(Router);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
@ -40,9 +41,10 @@ describe('ImplicitFlowCallbackService ', () => {
|
|||||||
|
|
||||||
describe('authorizedImplicitFlowCallback', () => {
|
describe('authorizedImplicitFlowCallback', () => {
|
||||||
it('calls flowsService.processImplicitFlowCallback with hash if given', () => {
|
it('calls flowsService.processImplicitFlowCallback with hash if given', () => {
|
||||||
const spy = vi
|
const spy = spyOn(
|
||||||
.spyOn(flowsService, 'processImplicitFlowCallback')
|
flowsService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'processImplicitFlowCallback'
|
||||||
|
).and.returnValue(of({} as CallbackContext));
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
triggerAuthorizationResultEvent: true,
|
triggerAuthorizationResultEvent: true,
|
||||||
@ -54,14 +56,10 @@ describe('ImplicitFlowCallbackService ', () => {
|
|||||||
'some-hash'
|
'some-hash'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash');
|
||||||
config,
|
|
||||||
[config],
|
|
||||||
'some-hash'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does nothing if triggerAuthorizationResultEvent is true and isRenewProcess is true', async () => {
|
it('does nothing if triggerAuthorizationResultEvent is true and isRenewProcess is true', waitForAsync(() => {
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
code: '',
|
code: '',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
@ -73,31 +71,25 @@ describe('ImplicitFlowCallbackService ', () => {
|
|||||||
validationResult: null,
|
validationResult: null,
|
||||||
existingIdToken: '',
|
existingIdToken: '',
|
||||||
};
|
};
|
||||||
const spy = vi
|
const spy = spyOn(
|
||||||
.spyOn(flowsService, 'processImplicitFlowCallback')
|
flowsService,
|
||||||
.mockReturnValue(of(callbackContext));
|
'processImplicitFlowCallback'
|
||||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
).and.returnValue(of(callbackContext));
|
||||||
|
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
triggerAuthorizationResultEvent: true,
|
triggerAuthorizationResultEvent: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
await firstValueFrom(
|
implicitFlowCallbackService
|
||||||
implicitFlowCallbackService.authenticatedImplicitFlowCallback(
|
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
|
||||||
config,
|
.subscribe(() => {
|
||||||
[config],
|
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash');
|
||||||
'some-hash'
|
expect(routerSpy).not.toHaveBeenCalled();
|
||||||
)
|
});
|
||||||
);
|
}));
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
|
||||||
config,
|
|
||||||
[config],
|
|
||||||
'some-hash'
|
|
||||||
);
|
|
||||||
expect(routerSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => {
|
it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', waitForAsync(() => {
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
code: '',
|
code: '',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
@ -109,40 +101,34 @@ describe('ImplicitFlowCallbackService ', () => {
|
|||||||
validationResult: null,
|
validationResult: null,
|
||||||
existingIdToken: '',
|
existingIdToken: '',
|
||||||
};
|
};
|
||||||
const spy = vi
|
const spy = spyOn(
|
||||||
.spyOn(flowsService, 'processImplicitFlowCallback')
|
flowsService,
|
||||||
.mockReturnValue(of(callbackContext));
|
'processImplicitFlowCallback'
|
||||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
).and.returnValue(of(callbackContext));
|
||||||
|
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
triggerAuthorizationResultEvent: false,
|
triggerAuthorizationResultEvent: false,
|
||||||
postLoginRoute: 'postLoginRoute',
|
postLoginRoute: 'postLoginRoute',
|
||||||
};
|
};
|
||||||
|
|
||||||
await firstValueFrom(
|
implicitFlowCallbackService
|
||||||
implicitFlowCallbackService.authenticatedImplicitFlowCallback(
|
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
|
||||||
config,
|
.subscribe(() => {
|
||||||
[config],
|
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash');
|
||||||
'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', async () => {
|
it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', waitForAsync(() => {
|
||||||
vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue(
|
spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue(
|
||||||
throwError(() => new Error('error'))
|
throwError(() => new Error('error'))
|
||||||
);
|
);
|
||||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
const resetSilentRenewRunningSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetSilentRenewRunning'
|
'resetSilentRenewRunning'
|
||||||
);
|
);
|
||||||
const stopPeriodicallyTokenCheckSpy = vi.spyOn(
|
const stopPeriodicallyTokenCheckSpy = spyOn(
|
||||||
intervalService,
|
intervalService,
|
||||||
'stopPeriodicTokenCheck'
|
'stopPeriodicTokenCheck'
|
||||||
);
|
);
|
||||||
@ -152,56 +138,48 @@ describe('ImplicitFlowCallbackService ', () => {
|
|||||||
postLoginRoute: 'postLoginRoute',
|
postLoginRoute: 'postLoginRoute',
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
implicitFlowCallbackService
|
||||||
await firstValueFrom(
|
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
|
||||||
implicitFlowCallbackService.authenticatedImplicitFlowCallback(
|
.subscribe({
|
||||||
config,
|
error: (err) => {
|
||||||
[config],
|
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||||
'some-hash'
|
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
|
||||||
)
|
expect(err).toBeTruthy();
|
||||||
);
|
},
|
||||||
} catch (err: any) {
|
});
|
||||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
}));
|
||||||
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
|
|
||||||
expect(err).toBeTruthy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`navigates to unauthorizedRoute in case of error and in case of error and
|
it(`navigates to unauthorizedRoute in case of error and in case of error and
|
||||||
triggerAuthorizationResultEvent is false`, async () => {
|
triggerAuthorizationResultEvent is false`, waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue(
|
spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue(
|
||||||
throwError(() => new Error('error'))
|
throwError(() => new Error('error'))
|
||||||
);
|
);
|
||||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
const resetSilentRenewRunningSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetSilentRenewRunning'
|
'resetSilentRenewRunning'
|
||||||
);
|
);
|
||||||
const stopPeriodicallTokenCheckSpy = vi.spyOn(
|
const stopPeriodicallTokenCheckSpy = spyOn(
|
||||||
intervalService,
|
intervalService,
|
||||||
'stopPeriodicTokenCheck'
|
'stopPeriodicTokenCheck'
|
||||||
);
|
);
|
||||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
triggerAuthorizationResultEvent: false,
|
triggerAuthorizationResultEvent: false,
|
||||||
unauthorizedRoute: 'unauthorizedRoute',
|
unauthorizedRoute: 'unauthorizedRoute',
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
implicitFlowCallbackService
|
||||||
await firstValueFrom(
|
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
|
||||||
implicitFlowCallbackService.authenticatedImplicitFlowCallback(
|
.subscribe({
|
||||||
config,
|
error: (err) => {
|
||||||
[config],
|
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||||
'some-hash'
|
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
||||||
)
|
expect(err).toBeTruthy();
|
||||||
);
|
expect(routerSpy).toHaveBeenCalledOnceWith('unauthorizedRoute');
|
||||||
} catch (err: any) {
|
},
|
||||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
});
|
||||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
}));
|
||||||
expect(err).toBeTruthy();
|
|
||||||
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('unauthorizedRoute');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, throwError } from 'rxjs';
|
import { Router } from '@angular/router';
|
||||||
|
import { Observable, throwError } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsDataService } from '../flows/flows-data.service';
|
import { FlowsDataService } from '../flows/flows-data.service';
|
||||||
import { FlowsService } from '../flows/flows.service';
|
import { FlowsService } from '../flows/flows.service';
|
||||||
import { injectAbstractType } from '../injection';
|
|
||||||
import { AbstractRouter } from '../router';
|
|
||||||
import { IntervalService } from './interval.service';
|
import { IntervalService } from './interval.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImplicitFlowCallbackService {
|
export class ImplicitFlowCallbackService {
|
||||||
private readonly flowsService = inject(FlowsService);
|
private readonly flowsService = inject(FlowsService);
|
||||||
|
|
||||||
private readonly router = injectAbstractType(AbstractRouter);
|
private readonly router = inject(Router);
|
||||||
|
|
||||||
private readonly flowsDataService = inject(FlowsDataService);
|
private readonly flowsDataService = inject(FlowsDataService);
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,13 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
|
||||||
import { IntervalService } from './interval.service';
|
import { IntervalService } from './interval.service';
|
||||||
|
|
||||||
describe('IntervalService', () => {
|
describe('IntervalService', () => {
|
||||||
let intervalService: IntervalService;
|
let intervalService: IntervalService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers();
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
IntervalService,
|
|
||||||
{
|
{
|
||||||
provide: Document,
|
provide: Document,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -21,12 +18,10 @@ describe('IntervalService', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
intervalService = TestBed.inject(IntervalService);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
beforeEach(() => {
|
||||||
afterEach(() => {
|
intervalService = TestBed.inject(IntervalService);
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
@ -36,7 +31,7 @@ describe('IntervalService', () => {
|
|||||||
describe('stopPeriodicTokenCheck', () => {
|
describe('stopPeriodicTokenCheck', () => {
|
||||||
it('calls unsubscribe and sets to null', () => {
|
it('calls unsubscribe and sets to null', () => {
|
||||||
intervalService.runTokenValidationRunning = new Subscription();
|
intervalService.runTokenValidationRunning = new Subscription();
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(
|
||||||
intervalService.runTokenValidationRunning,
|
intervalService.runTokenValidationRunning,
|
||||||
'unsubscribe'
|
'unsubscribe'
|
||||||
);
|
);
|
||||||
@ -49,7 +44,7 @@ describe('IntervalService', () => {
|
|||||||
|
|
||||||
it('does nothing if `runTokenValidationRunning` is null', () => {
|
it('does nothing if `runTokenValidationRunning` is null', () => {
|
||||||
intervalService.runTokenValidationRunning = new Subscription();
|
intervalService.runTokenValidationRunning = new Subscription();
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(
|
||||||
intervalService.runTokenValidationRunning,
|
intervalService.runTokenValidationRunning,
|
||||||
'unsubscribe'
|
'unsubscribe'
|
||||||
);
|
);
|
||||||
@ -62,20 +57,20 @@ describe('IntervalService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('startPeriodicTokenCheck', () => {
|
describe('startPeriodicTokenCheck', () => {
|
||||||
it('starts check after correct milliseconds', async () => {
|
it('starts check after correct milliseconds', fakeAsync(() => {
|
||||||
const periodicCheck = intervalService.startPeriodicTokenCheck(0.5);
|
const periodicCheck = intervalService.startPeriodicTokenCheck(0.5);
|
||||||
const spy = vi.fn();
|
const spy = jasmine.createSpy();
|
||||||
const sub = periodicCheck.subscribe(() => {
|
const sub = periodicCheck.subscribe(() => {
|
||||||
spy();
|
spy();
|
||||||
});
|
});
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(500);
|
tick(500);
|
||||||
expect(spy).toHaveBeenCalledTimes(1);
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(500);
|
tick(500);
|
||||||
expect(spy).toHaveBeenCalledTimes(2);
|
expect(spy).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
sub.unsubscribe();
|
sub.unsubscribe();
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { Injectable, NgZone, inject } from 'injection-js';
|
||||||
import { type Observable, type Subscription, interval } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { DOCUMENT } from '../dom';
|
import { DOCUMENT } from '../../dom';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class IntervalService {
|
export class IntervalService {
|
||||||
|
private readonly zone = inject(NgZone);
|
||||||
|
|
||||||
private readonly document = inject(DOCUMENT);
|
private readonly document = inject(DOCUMENT);
|
||||||
|
|
||||||
runTokenValidationRunning: Subscription | null = null;
|
runTokenValidationRunning: Subscription | null = null;
|
||||||
@ -22,6 +24,19 @@ export class IntervalService {
|
|||||||
startPeriodicTokenCheck(repeatAfterSeconds: number): Observable<unknown> {
|
startPeriodicTokenCheck(repeatAfterSeconds: number): Observable<unknown> {
|
||||||
const millisecondsDelayBetweenTokenCheck = repeatAfterSeconds * 1000;
|
const millisecondsDelayBetweenTokenCheck = repeatAfterSeconds * 1000;
|
||||||
|
|
||||||
return interval(millisecondsDelayBetweenTokenCheck);
|
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);
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||||
import { ReplaySubject, firstValueFrom, of, share, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||||
import { ConfigurationService } from '../config/config.service';
|
import { ConfigurationService } from '../config/config.service';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsDataService } from '../flows/flows-data.service';
|
import { FlowsDataService } from '../flows/flows-data.service';
|
||||||
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
||||||
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
||||||
@ -12,7 +12,6 @@ import { LoggerService } from '../logging/logger.service';
|
|||||||
import { EventTypes } from '../public-events/event-types';
|
import { EventTypes } from '../public-events/event-types';
|
||||||
import { PublicEventsService } from '../public-events/public-events.service';
|
import { PublicEventsService } from '../public-events/public-events.service';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { UserService } from '../user-data/user.service';
|
import { UserService } from '../user-data/user.service';
|
||||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||||
import { IntervalService } from './interval.service';
|
import { IntervalService } from './interval.service';
|
||||||
@ -33,11 +32,9 @@ describe('PeriodicallyTokenCheckService', () => {
|
|||||||
let publicEventsService: PublicEventsService;
|
let publicEventsService: PublicEventsService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers();
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [],
|
||||||
providers: [
|
providers: [
|
||||||
PeriodicallyTokenCheckService,
|
|
||||||
mockProvider(ResetAuthDataService),
|
mockProvider(ResetAuthDataService),
|
||||||
FlowHelper,
|
FlowHelper,
|
||||||
mockProvider(FlowsDataService),
|
mockProvider(FlowsDataService),
|
||||||
@ -52,6 +49,9 @@ describe('PeriodicallyTokenCheckService', () => {
|
|||||||
mockProvider(ConfigurationService),
|
mockProvider(ConfigurationService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
periodicallyTokenCheckService = TestBed.inject(
|
periodicallyTokenCheckService = TestBed.inject(
|
||||||
PeriodicallyTokenCheckService
|
PeriodicallyTokenCheckService
|
||||||
);
|
);
|
||||||
@ -68,18 +68,14 @@ describe('PeriodicallyTokenCheckService', () => {
|
|||||||
publicEventsService = TestBed.inject(PublicEventsService);
|
publicEventsService = TestBed.inject(PublicEventsService);
|
||||||
configurationService = TestBed.inject(ConfigurationService);
|
configurationService = TestBed.inject(ConfigurationService);
|
||||||
|
|
||||||
vi.spyOn(intervalService, 'startPeriodicTokenCheck').mockReturnValue(
|
spyOn(intervalService, 'startPeriodicTokenCheck').and.returnValue(of(null));
|
||||||
of(null)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
if (intervalService?.runTokenValidationRunning?.unsubscribe) {
|
if (!!intervalService.runTokenValidationRunning?.unsubscribe) {
|
||||||
intervalService.runTokenValidationRunning.unsubscribe();
|
intervalService.runTokenValidationRunning.unsubscribe();
|
||||||
intervalService.runTokenValidationRunning = null;
|
intervalService.runTokenValidationRunning = null;
|
||||||
}
|
}
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
@ -87,200 +83,164 @@ describe('PeriodicallyTokenCheckService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('startTokenValidationPeriodically', () => {
|
describe('startTokenValidationPeriodically', () => {
|
||||||
beforeEach(() => {
|
it('returns if no config has silentrenew enabled', waitForAsync(() => {
|
||||||
vi.useFakeTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
|
||||||
afterEach(() => {
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns if no config has silentrenew enabled', async () => {
|
|
||||||
const configs = [
|
const configs = [
|
||||||
{ silentRenew: false, configId: 'configId1' },
|
{ silentRenew: false, configId: 'configId1' },
|
||||||
{ silentRenew: false, configId: 'configId2' },
|
{ silentRenew: false, configId: 'configId2' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
const result =
|
||||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||||
configs,
|
configs,
|
||||||
configs[0]!
|
configs[0]
|
||||||
)
|
);
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('returns if runTokenValidationRunning', async () => {
|
it('returns if runTokenValidationRunning', waitForAsync(() => {
|
||||||
const configs = [{ silentRenew: true, configId: 'configId1' }];
|
const configs = [{ silentRenew: true, configId: 'configId1' }];
|
||||||
|
|
||||||
vi.spyOn(intervalService, 'isTokenValidationRunning').mockReturnValue(
|
spyOn(intervalService, 'isTokenValidationRunning').and.returnValue(true);
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
const result =
|
||||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||||
configs,
|
configs,
|
||||||
configs[0]!
|
configs[0]
|
||||||
)
|
);
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('interval calls resetSilentRenewRunning when current flow is CodeFlowWithRefreshTokens', async () => {
|
it('interval calls resetSilentRenewRunning when current flow is CodeFlowWithRefreshTokens', fakeAsync(() => {
|
||||||
const configs = [
|
const configs = [
|
||||||
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
periodicallyTokenCheckService as any,
|
periodicallyTokenCheckService as any,
|
||||||
'shouldStartPeriodicallyCheckForConfig'
|
'shouldStartPeriodicallyCheckForConfig'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
const isCurrentFlowCodeFlowWithRefreshTokensSpy = vi
|
const isCurrentFlowCodeFlowWithRefreshTokensSpy = spyOn(
|
||||||
.spyOn(flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens')
|
flowHelper,
|
||||||
.mockReturnValue(true);
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
).and.returnValue(true);
|
||||||
|
const resetSilentRenewRunningSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetSilentRenewRunning'
|
'resetSilentRenewRunning'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionRefreshTokenService,
|
refreshSessionRefreshTokenService,
|
||||||
'refreshSessionWithRefreshTokens'
|
'refreshSessionWithRefreshTokens'
|
||||||
).mockReturnValue(of({} as CallbackContext));
|
).and.returnValue(of({} as CallbackContext));
|
||||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||||
of(configs[0]!)
|
of(configs[0])
|
||||||
);
|
);
|
||||||
|
|
||||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||||
configs,
|
configs,
|
||||||
configs[0]!
|
configs[0]
|
||||||
);
|
);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(1000);
|
tick(1000);
|
||||||
|
|
||||||
intervalService.runTokenValidationRunning?.unsubscribe();
|
intervalService.runTokenValidationRunning?.unsubscribe();
|
||||||
intervalService.runTokenValidationRunning = null;
|
intervalService.runTokenValidationRunning = null;
|
||||||
expect(isCurrentFlowCodeFlowWithRefreshTokensSpy).toHaveBeenCalled();
|
expect(isCurrentFlowCodeFlowWithRefreshTokensSpy).toHaveBeenCalled();
|
||||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('interval calls resetSilentRenewRunning in case of error when current flow is CodeFlowWithRefreshTokens', async () => {
|
it('interval calls resetSilentRenewRunning in case of error when current flow is CodeFlowWithRefreshTokens', fakeAsync(() => {
|
||||||
const configs = [
|
const configs = [
|
||||||
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
periodicallyTokenCheckService as any,
|
periodicallyTokenCheckService as any,
|
||||||
'shouldStartPeriodicallyCheckForConfig'
|
'shouldStartPeriodicallyCheckForConfig'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
const resetSilentRenewRunning = vi.spyOn(
|
const resetSilentRenewRunning = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetSilentRenewRunning'
|
'resetSilentRenewRunning'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionRefreshTokenService,
|
refreshSessionRefreshTokenService,
|
||||||
'refreshSessionWithRefreshTokens'
|
'refreshSessionWithRefreshTokens'
|
||||||
).mockReturnValue(throwError(() => new Error('error')));
|
).and.returnValue(throwError(() => new Error('error')));
|
||||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||||
of(configs[0]!)
|
of(configs[0])
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||||
const test$ = periodicallyTokenCheckService
|
configs,
|
||||||
.startTokenValidationPeriodically(configs, configs[0]!)
|
configs[0]
|
||||||
.pipe(
|
);
|
||||||
share({
|
|
||||||
connector: () => new ReplaySubject(1),
|
|
||||||
resetOnError: false,
|
|
||||||
resetOnComplete: false,
|
|
||||||
resetOnRefCountZero: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
tick(1000);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(1000);
|
expect(
|
||||||
|
periodicallyTokenCheckService.startTokenValidationPeriodically
|
||||||
|
).toThrowError();
|
||||||
|
expect(resetSilentRenewRunning).toHaveBeenCalledOnceWith(configs[0]);
|
||||||
|
}));
|
||||||
|
|
||||||
await firstValueFrom(test$);
|
it('interval throws silent renew failed event with data in case of an error', fakeAsync(() => {
|
||||||
expect.fail('should throw errror');
|
|
||||||
} catch {
|
|
||||||
expect(resetSilentRenewRunning).toHaveBeenCalledExactlyOnceWith(
|
|
||||||
configs[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('interval throws silent renew failed event with data in case of an error', async () => {
|
|
||||||
const configs = [
|
const configs = [
|
||||||
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
periodicallyTokenCheckService as any,
|
periodicallyTokenCheckService as any,
|
||||||
'shouldStartPeriodicallyCheckForConfig'
|
'shouldStartPeriodicallyCheckForConfig'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(flowsDataService, 'resetSilentRenewRunning');
|
spyOn(flowsDataService, 'resetSilentRenewRunning');
|
||||||
const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent');
|
const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent');
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionRefreshTokenService,
|
refreshSessionRefreshTokenService,
|
||||||
'refreshSessionWithRefreshTokens'
|
'refreshSessionWithRefreshTokens'
|
||||||
).mockReturnValue(throwError(() => new Error('error')));
|
).and.returnValue(throwError(() => new Error('error')));
|
||||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||||
of(configs[0]!)
|
of(configs[0])
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||||
const test$ = periodicallyTokenCheckService
|
configs,
|
||||||
.startTokenValidationPeriodically(configs, configs[0]!)
|
configs[0]
|
||||||
.pipe(
|
);
|
||||||
share({
|
|
||||||
connector: () => new ReplaySubject(1),
|
|
||||||
resetOnComplete: false,
|
|
||||||
resetOnError: false,
|
|
||||||
resetOnRefCountZero: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
test$.subscribe();
|
tick(1000);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(1000);
|
expect(
|
||||||
|
periodicallyTokenCheckService.startTokenValidationPeriodically
|
||||||
|
).toThrowError();
|
||||||
|
expect(publicEventsServiceSpy.calls.allArgs()).toEqual([
|
||||||
|
[EventTypes.SilentRenewStarted],
|
||||||
|
[EventTypes.SilentRenewFailed, new Error('error')],
|
||||||
|
]);
|
||||||
|
}));
|
||||||
|
|
||||||
await firstValueFrom(test$);
|
it('calls resetAuthorizationData and returns if no silent renew is configured', fakeAsync(() => {
|
||||||
} catch {
|
|
||||||
expect(publicEventsServiceSpy.mock.calls).toEqual([
|
|
||||||
[EventTypes.SilentRenewStarted],
|
|
||||||
[EventTypes.SilentRenewFailed, new Error('error')],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls resetAuthorizationData and returns if no silent renew is configured', async () => {
|
|
||||||
const configs = [
|
const configs = [
|
||||||
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
periodicallyTokenCheckService as any,
|
periodicallyTokenCheckService as any,
|
||||||
'shouldStartPeriodicallyCheckForConfig'
|
'shouldStartPeriodicallyCheckForConfig'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
|
|
||||||
const configSpy = vi.spyOn(
|
const configSpy = spyOn(configurationService, 'getOpenIDConfiguration');
|
||||||
configurationService,
|
|
||||||
'getOpenIDConfiguration'
|
|
||||||
);
|
|
||||||
const configWithoutSilentRenew = {
|
const configWithoutSilentRenew = {
|
||||||
silentRenew: false,
|
silentRenew: false,
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -288,70 +248,68 @@ describe('PeriodicallyTokenCheckService', () => {
|
|||||||
};
|
};
|
||||||
const configWithoutSilentRenew$ = of(configWithoutSilentRenew);
|
const configWithoutSilentRenew$ = of(configWithoutSilentRenew);
|
||||||
|
|
||||||
configSpy.mockReturnValue(configWithoutSilentRenew$);
|
configSpy.and.returnValue(configWithoutSilentRenew$);
|
||||||
|
|
||||||
const resetAuthorizationDataSpy = vi.spyOn(
|
const resetAuthorizationDataSpy = spyOn(
|
||||||
resetAuthDataService,
|
resetAuthDataService,
|
||||||
'resetAuthorizationData'
|
'resetAuthorizationData'
|
||||||
);
|
);
|
||||||
|
|
||||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||||
configs,
|
configs,
|
||||||
configs[0]!
|
configs[0]
|
||||||
);
|
);
|
||||||
await vi.advanceTimersByTimeAsync(1000);
|
tick(1000);
|
||||||
intervalService.runTokenValidationRunning?.unsubscribe();
|
intervalService.runTokenValidationRunning?.unsubscribe();
|
||||||
intervalService.runTokenValidationRunning = null;
|
intervalService.runTokenValidationRunning = null;
|
||||||
|
|
||||||
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(resetAuthorizationDataSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(resetAuthorizationDataSpy).toHaveBeenCalledOnceWith(
|
||||||
configWithoutSilentRenew,
|
configWithoutSilentRenew,
|
||||||
configs
|
configs
|
||||||
);
|
);
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('calls refreshSessionWithRefreshTokens if current flow is Code flow with refresh tokens', async () => {
|
it('calls refreshSessionWithRefreshTokens if current flow is Code flow with refresh tokens', fakeAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
periodicallyTokenCheckService as any,
|
periodicallyTokenCheckService as any,
|
||||||
'shouldStartPeriodicallyCheckForConfig'
|
'shouldStartPeriodicallyCheckForConfig'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(storagePersistenceService, 'read').mockReturnValue({});
|
spyOn(storagePersistenceService, 'read').and.returnValue({});
|
||||||
const configs = [
|
const configs = [
|
||||||
{ configId: 'configId1', silentRenew: true, tokenRefreshInSeconds: 1 },
|
{ configId: 'configId1', silentRenew: true, tokenRefreshInSeconds: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||||
of(configs[0] as OpenIdConfiguration)
|
of(configs[0] as OpenIdConfiguration)
|
||||||
);
|
);
|
||||||
const refreshSessionWithRefreshTokensSpy = vi
|
const refreshSessionWithRefreshTokensSpy = spyOn(
|
||||||
.spyOn(
|
refreshSessionRefreshTokenService,
|
||||||
refreshSessionRefreshTokenService,
|
'refreshSessionWithRefreshTokens'
|
||||||
'refreshSessionWithRefreshTokens'
|
).and.returnValue(of({} as CallbackContext));
|
||||||
)
|
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
|
||||||
|
|
||||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||||
configs,
|
configs,
|
||||||
configs[0]!
|
configs[0]
|
||||||
);
|
);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(1000);
|
tick(1000);
|
||||||
|
|
||||||
intervalService.runTokenValidationRunning?.unsubscribe();
|
intervalService.runTokenValidationRunning?.unsubscribe();
|
||||||
intervalService.runTokenValidationRunning = null;
|
intervalService.runTokenValidationRunning = null;
|
||||||
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
|
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('shouldStartPeriodicallyCheckForConfig', () => {
|
describe('shouldStartPeriodicallyCheckForConfig', () => {
|
||||||
it('returns false when there is no IdToken', () => {
|
it('returns false when there is no IdToken', () => {
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('');
|
spyOn(authStateService, 'getIdToken').and.returnValue('');
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||||
'some-userdata'
|
'some-userdata'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -359,13 +317,13 @@ describe('PeriodicallyTokenCheckService', () => {
|
|||||||
periodicallyTokenCheckService as any
|
periodicallyTokenCheckService as any
|
||||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false when silent renew is running', () => {
|
it('returns false when silent renew is running', () => {
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||||
'some-userdata'
|
'some-userdata'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -373,14 +331,14 @@ describe('PeriodicallyTokenCheckService', () => {
|
|||||||
periodicallyTokenCheckService as any
|
periodicallyTokenCheckService as any
|
||||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false when code flow is in progress', () => {
|
it('returns false when code flow is in progress', () => {
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(flowsDataService, 'isCodeFlowInProgress').mockReturnValue(true);
|
spyOn(flowsDataService, 'isCodeFlowInProgress').and.returnValue(true);
|
||||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||||
'some-userdata'
|
'some-userdata'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -388,87 +346,87 @@ describe('PeriodicallyTokenCheckService', () => {
|
|||||||
periodicallyTokenCheckService as any
|
periodicallyTokenCheckService as any
|
||||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false when there is no userdata from the store', () => {
|
it('returns false when there is no userdata from the store', () => {
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(null);
|
spyOn(userService, 'getUserDataFromStore').and.returnValue(null);
|
||||||
|
|
||||||
const result = (
|
const result = (
|
||||||
periodicallyTokenCheckService as any
|
periodicallyTokenCheckService as any
|
||||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true when there is userDataFromStore, silentrenew is not running and there is an idtoken', () => {
|
it('returns true when there is userDataFromStore, silentrenew is not running and there is an idtoken', () => {
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||||
'some-userdata'
|
'some-userdata'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'hasAccessTokenExpiredIfExpiryExists'
|
'hasAccessTokenExpiredIfExpiryExists'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
|
|
||||||
const result = (
|
const result = (
|
||||||
periodicallyTokenCheckService as any
|
periodicallyTokenCheckService as any
|
||||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if tokens are not expired', () => {
|
it('returns false if tokens are not expired', () => {
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||||
'some-userdata'
|
'some-userdata'
|
||||||
);
|
);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'hasAccessTokenExpiredIfExpiryExists'
|
'hasAccessTokenExpiredIfExpiryExists'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
|
|
||||||
const result = (
|
const result = (
|
||||||
periodicallyTokenCheckService as any
|
periodicallyTokenCheckService as any
|
||||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if tokens are expired', () => {
|
it('returns true if tokens are expired', () => {
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||||
'some-userdata'
|
'some-userdata'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'hasAccessTokenExpiredIfExpiryExists'
|
'hasAccessTokenExpiredIfExpiryExists'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
|
|
||||||
const result = (
|
const result = (
|
||||||
periodicallyTokenCheckService as any
|
periodicallyTokenCheckService as any
|
||||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, ReplaySubject, forkJoin, of, throwError } from 'rxjs';
|
import { forkJoin, Observable, of, throwError } from 'rxjs';
|
||||||
import { catchError, map, share, switchMap } from 'rxjs/operators';
|
import { catchError, switchMap } from 'rxjs/operators';
|
||||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||||
import { ConfigurationService } from '../config/config.service';
|
import { ConfigurationService } from '../config/config.service';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsDataService } from '../flows/flows-data.service';
|
import { FlowsDataService } from '../flows/flows-data.service';
|
||||||
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
||||||
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
||||||
@ -52,16 +52,16 @@ export class PeriodicallyTokenCheckService {
|
|||||||
startTokenValidationPeriodically(
|
startTokenValidationPeriodically(
|
||||||
allConfigs: OpenIdConfiguration[],
|
allConfigs: OpenIdConfiguration[],
|
||||||
currentConfig: OpenIdConfiguration
|
currentConfig: OpenIdConfiguration
|
||||||
): Observable<undefined> {
|
): void {
|
||||||
const configsWithSilentRenewEnabled =
|
const configsWithSilentRenewEnabled =
|
||||||
this.getConfigsWithSilentRenewEnabled(allConfigs);
|
this.getConfigsWithSilentRenewEnabled(allConfigs);
|
||||||
|
|
||||||
if (configsWithSilentRenewEnabled.length <= 0) {
|
if (configsWithSilentRenewEnabled.length <= 0) {
|
||||||
return of(undefined);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.intervalService.isTokenValidationRunning()) {
|
if (this.intervalService.isTokenValidationRunning()) {
|
||||||
return of(undefined);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshTimeInSeconds = this.getSmallestRefreshTimeFromConfigs(
|
const refreshTimeInSeconds = this.getSmallestRefreshTimeFromConfigs(
|
||||||
@ -75,56 +75,46 @@ export class PeriodicallyTokenCheckService {
|
|||||||
[id: string]: Observable<boolean | CallbackContext | null>;
|
[id: string]: Observable<boolean | CallbackContext | null>;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
for (const config of configsWithSilentRenewEnabled) {
|
configsWithSilentRenewEnabled.forEach((config) => {
|
||||||
const identifier = config.configId as string;
|
const identifier = config.configId as string;
|
||||||
const refreshEvent = this.getRefreshEvent(config, allConfigs);
|
const refreshEvent = this.getRefreshEvent(config, allConfigs);
|
||||||
|
|
||||||
objectWithConfigIdsAndRefreshEvent[identifier] = refreshEvent;
|
objectWithConfigIdsAndRefreshEvent[identifier] = refreshEvent;
|
||||||
}
|
});
|
||||||
|
|
||||||
return forkJoin(objectWithConfigIdsAndRefreshEvent);
|
return forkJoin(objectWithConfigIdsAndRefreshEvent);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const o$ = periodicallyCheck$.pipe(
|
this.intervalService.runTokenValidationRunning = periodicallyCheck$
|
||||||
catchError((error) => {
|
.pipe(catchError((error) => throwError(() => new Error(error))))
|
||||||
this.loggerService.logError(
|
.subscribe({
|
||||||
currentConfig,
|
next: (objectWithConfigIds) => {
|
||||||
'silent renew failed!',
|
for (const [configId, _] of Object.entries(objectWithConfigIds)) {
|
||||||
error
|
this.configurationService
|
||||||
);
|
.getOpenIDConfiguration(configId)
|
||||||
return throwError(() => error);
|
.subscribe((config) => {
|
||||||
}),
|
this.loggerService.logDebug(
|
||||||
map((objectWithConfigIds) => {
|
config,
|
||||||
for (const [configId, _] of Object.entries(objectWithConfigIds)) {
|
'silent renew, periodic check finished!'
|
||||||
this.configurationService
|
);
|
||||||
.getOpenIDConfiguration(configId)
|
|
||||||
.subscribe((config) => {
|
|
||||||
this.loggerService.logDebug(
|
|
||||||
config,
|
|
||||||
'silent renew, periodic check finished!'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config)
|
this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config)
|
||||||
) {
|
) {
|
||||||
this.flowsDataService.resetSilentRenewRunning(config);
|
this.flowsDataService.resetSilentRenewRunning(config);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return undefined;
|
}
|
||||||
}
|
},
|
||||||
}),
|
error: (error) => {
|
||||||
share({
|
this.loggerService.logError(
|
||||||
connector: () => new ReplaySubject(1),
|
currentConfig,
|
||||||
resetOnError: false,
|
'silent renew failed!',
|
||||||
resetOnComplete: false,
|
error
|
||||||
resetOnRefCountZero: false,
|
);
|
||||||
})
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
this.intervalService.runTokenValidationRunning = o$.subscribe({});
|
|
||||||
|
|
||||||
return o$;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRefreshEvent(
|
private getRefreshEvent(
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsService } from '../flows/flows.service';
|
import { FlowsService } from '../flows/flows.service';
|
||||||
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { IntervalService } from './interval.service';
|
import { IntervalService } from './interval.service';
|
||||||
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
|
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
|
||||||
|
|
||||||
@ -16,7 +15,6 @@ describe('RefreshSessionRefreshTokenService', () => {
|
|||||||
let flowsService: FlowsService;
|
let flowsService: FlowsService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers();
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [],
|
||||||
providers: [
|
providers: [
|
||||||
@ -27,6 +25,9 @@ describe('RefreshSessionRefreshTokenService', () => {
|
|||||||
mockProvider(IntervalService),
|
mockProvider(IntervalService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
flowsService = TestBed.inject(FlowsService);
|
flowsService = TestBed.inject(FlowsService);
|
||||||
refreshSessionRefreshTokenService = TestBed.inject(
|
refreshSessionRefreshTokenService = TestBed.inject(
|
||||||
RefreshSessionRefreshTokenService
|
RefreshSessionRefreshTokenService
|
||||||
@ -35,73 +36,66 @@ describe('RefreshSessionRefreshTokenService', () => {
|
|||||||
resetAuthDataService = TestBed.inject(ResetAuthDataService);
|
resetAuthDataService = TestBed.inject(ResetAuthDataService);
|
||||||
});
|
});
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
|
||||||
afterEach(() => {
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(refreshSessionRefreshTokenService).toBeTruthy();
|
expect(refreshSessionRefreshTokenService).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('refreshSessionWithRefreshTokens', () => {
|
describe('refreshSessionWithRefreshTokens', () => {
|
||||||
it('calls flowsService.processRefreshToken()', async () => {
|
it('calls flowsService.processRefreshToken()', waitForAsync(() => {
|
||||||
const spy = vi
|
const spy = spyOn(flowsService, 'processRefreshToken').and.returnValue(
|
||||||
.spyOn(flowsService, 'processRefreshToken')
|
of({} as CallbackContext)
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
|
||||||
|
|
||||||
await firstValueFrom(
|
|
||||||
refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(
|
|
||||||
{ configId: 'configId1' },
|
|
||||||
[{ configId: 'configId1' }]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
expect(spy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('resetAuthorizationData in case of error', async () => {
|
refreshSessionRefreshTokenService
|
||||||
vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue(
|
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [
|
||||||
|
{ configId: 'configId1' },
|
||||||
|
])
|
||||||
|
.subscribe(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('resetAuthorizationData in case of error', waitForAsync(() => {
|
||||||
|
spyOn(flowsService, 'processRefreshToken').and.returnValue(
|
||||||
throwError(() => new Error('error'))
|
throwError(() => new Error('error'))
|
||||||
);
|
);
|
||||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
const resetSilentRenewRunningSpy = spyOn(
|
||||||
resetAuthDataService,
|
resetAuthDataService,
|
||||||
'resetAuthorizationData'
|
'resetAuthorizationData'
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
refreshSessionRefreshTokenService
|
||||||
await firstValueFrom(
|
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [
|
||||||
refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(
|
{ configId: 'configId1' },
|
||||||
{ configId: 'configId1' },
|
])
|
||||||
[{ configId: 'configId1' }]
|
.subscribe({
|
||||||
)
|
error: (err) => {
|
||||||
);
|
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||||
} catch (err: any) {
|
expect(err).toBeTruthy();
|
||||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
},
|
||||||
expect(err).toBeTruthy();
|
});
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('finalize with stopPeriodicTokenCheck in case of error', async () => {
|
it('finalize with stopPeriodicTokenCheck in case of error', fakeAsync(() => {
|
||||||
vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue(
|
spyOn(flowsService, 'processRefreshToken').and.returnValue(
|
||||||
throwError(() => new Error('error'))
|
throwError(() => new Error('error'))
|
||||||
);
|
);
|
||||||
const stopPeriodicallyTokenCheckSpy = vi.spyOn(
|
const stopPeriodicallyTokenCheckSpy = spyOn(
|
||||||
intervalService,
|
intervalService,
|
||||||
'stopPeriodicTokenCheck'
|
'stopPeriodicTokenCheck'
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
refreshSessionRefreshTokenService
|
||||||
await firstValueFrom(
|
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [
|
||||||
refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(
|
{ configId: 'configId1' },
|
||||||
{ configId: 'configId1' },
|
])
|
||||||
[{ configId: 'configId1' }]
|
.subscribe({
|
||||||
)
|
error: (err) => {
|
||||||
);
|
expect(err).toBeTruthy();
|
||||||
} catch (err: any) {
|
},
|
||||||
expect(err).toBeTruthy();
|
});
|
||||||
}
|
tick();
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
|
||||||
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
|
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, throwError } from 'rxjs';
|
import { Observable, throwError } from 'rxjs';
|
||||||
import { catchError, finalize } from 'rxjs/operators';
|
import { catchError, finalize } from 'rxjs/operators';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsService } from '../flows/flows.service';
|
import { FlowsService } from '../flows/flows.service';
|
||||||
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
|
|||||||
@ -1,24 +1,17 @@
|
|||||||
import { TestBed, spyOnProperty } from '@/testing';
|
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||||
import {
|
import { of, throwError } from 'rxjs';
|
||||||
EmptyError,
|
import { delay } from 'rxjs/operators';
|
||||||
ReplaySubject,
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
firstValueFrom,
|
|
||||||
of,
|
|
||||||
throwError,
|
|
||||||
} from 'rxjs';
|
|
||||||
import { delay, share } from 'rxjs/operators';
|
|
||||||
import { vi } from 'vitest';
|
|
||||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||||
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
|
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsDataService } from '../flows/flows-data.service';
|
import { FlowsDataService } from '../flows/flows-data.service';
|
||||||
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
||||||
import { SilentRenewService } from '../iframe/silent-renew.service';
|
import { SilentRenewService } from '../iframe/silent-renew.service';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import type { LoginResponse } from '../login/login-response';
|
import { LoginResponse } from '../login/login-response';
|
||||||
import { PublicEventsService } from '../public-events/public-events.service';
|
import { PublicEventsService } from '../public-events/public-events.service';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { UserService } from '../user-data/user.service';
|
import { UserService } from '../user-data/user.service';
|
||||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||||
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
|
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
|
||||||
@ -28,7 +21,6 @@ import {
|
|||||||
} from './refresh-session.service';
|
} from './refresh-session.service';
|
||||||
|
|
||||||
describe('RefreshSessionService ', () => {
|
describe('RefreshSessionService ', () => {
|
||||||
vi.useFakeTimers();
|
|
||||||
let refreshSessionService: RefreshSessionService;
|
let refreshSessionService: RefreshSessionService;
|
||||||
let flowHelper: FlowHelper;
|
let flowHelper: FlowHelper;
|
||||||
let authStateService: AuthStateService;
|
let authStateService: AuthStateService;
|
||||||
@ -57,6 +49,9 @@ describe('RefreshSessionService ', () => {
|
|||||||
mockProvider(PublicEventsService),
|
mockProvider(PublicEventsService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
refreshSessionService = TestBed.inject(RefreshSessionService);
|
refreshSessionService = TestBed.inject(RefreshSessionService);
|
||||||
flowsDataService = TestBed.inject(FlowsDataService);
|
flowsDataService = TestBed.inject(FlowsDataService);
|
||||||
flowHelper = TestBed.inject(FlowHelper);
|
flowHelper = TestBed.inject(FlowHelper);
|
||||||
@ -70,29 +65,24 @@ describe('RefreshSessionService ', () => {
|
|||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
});
|
});
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
|
||||||
afterEach(() => {
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(refreshSessionService).toBeTruthy();
|
expect(refreshSessionService).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('userForceRefreshSession', () => {
|
describe('userForceRefreshSession', () => {
|
||||||
it('should persist params refresh when extra custom params given and useRefreshToken is true', async () => {
|
it('should persist params refresh when extra custom params given and useRefreshToken is true', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const writeSpy = vi.spyOn(storagePersistenceService, 'write');
|
const writeSpy = spyOn(storagePersistenceService, 'write');
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -103,30 +93,27 @@ describe('RefreshSessionService ', () => {
|
|||||||
|
|
||||||
const extraCustomParams = { extra: 'custom' };
|
const extraCustomParams = { extra: 'custom' };
|
||||||
|
|
||||||
await firstValueFrom(
|
refreshSessionService
|
||||||
refreshSessionService.userForceRefreshSession(
|
.userForceRefreshSession(allConfigs[0], allConfigs, extraCustomParams)
|
||||||
allConfigs[0]!,
|
.subscribe(() => {
|
||||||
allConfigs,
|
expect(writeSpy).toHaveBeenCalledOnceWith(
|
||||||
extraCustomParams
|
'storageCustomParamsRefresh',
|
||||||
)
|
extraCustomParams,
|
||||||
);
|
allConfigs[0]
|
||||||
expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
|
);
|
||||||
'storageCustomParamsRefresh',
|
});
|
||||||
extraCustomParams,
|
}));
|
||||||
allConfigs[0]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should persist storageCustomParamsAuthRequest when extra custom params given and useRefreshToken is false', async () => {
|
it('should persist storageCustomParamsAuthRequest when extra custom params given and useRefreshToken is false', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
@ -136,34 +123,31 @@ describe('RefreshSessionService ', () => {
|
|||||||
silentRenewTimeoutInSeconds: 10,
|
silentRenewTimeoutInSeconds: 10,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const writeSpy = vi.spyOn(storagePersistenceService, 'write');
|
const writeSpy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
const extraCustomParams = { extra: 'custom' };
|
const extraCustomParams = { extra: 'custom' };
|
||||||
|
|
||||||
await firstValueFrom(
|
refreshSessionService
|
||||||
refreshSessionService.userForceRefreshSession(
|
.userForceRefreshSession(allConfigs[0], allConfigs, extraCustomParams)
|
||||||
allConfigs[0]!,
|
.subscribe(() => {
|
||||||
allConfigs,
|
expect(writeSpy).toHaveBeenCalledOnceWith(
|
||||||
extraCustomParams
|
'storageCustomParamsAuthRequest',
|
||||||
)
|
extraCustomParams,
|
||||||
);
|
allConfigs[0]
|
||||||
expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
|
);
|
||||||
'storageCustomParamsAuthRequest',
|
});
|
||||||
extraCustomParams,
|
}));
|
||||||
allConfigs[0]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should NOT persist customparams if no customparams are given', async () => {
|
it('should NOT persist customparams if no customparams are given', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
@ -173,22 +157,20 @@ describe('RefreshSessionService ', () => {
|
|||||||
silentRenewTimeoutInSeconds: 10,
|
silentRenewTimeoutInSeconds: 10,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const writeSpy = vi.spyOn(storagePersistenceService, 'write');
|
const writeSpy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
await firstValueFrom(
|
refreshSessionService
|
||||||
refreshSessionService.userForceRefreshSession(
|
.userForceRefreshSession(allConfigs[0], allConfigs)
|
||||||
allConfigs[0]!,
|
.subscribe(() => {
|
||||||
allConfigs
|
expect(writeSpy).not.toHaveBeenCalled();
|
||||||
)
|
});
|
||||||
);
|
}));
|
||||||
expect(writeSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call resetSilentRenewRunning in case of an error', async () => {
|
it('should call resetSilentRenewRunning in case of an error', waitForAsync(() => {
|
||||||
vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue(
|
spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue(
|
||||||
throwError(() => new Error('error'))
|
throwError(() => new Error('error'))
|
||||||
);
|
);
|
||||||
vi.spyOn(flowsDataService, 'resetSilentRenewRunning');
|
spyOn(flowsDataService, 'resetSilentRenewRunning');
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -197,29 +179,28 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
refreshSessionService
|
||||||
const result = await firstValueFrom(
|
.userForceRefreshSession(allConfigs[0], allConfigs)
|
||||||
refreshSessionService.userForceRefreshSession(
|
.subscribe({
|
||||||
allConfigs[0]!,
|
next: () => {
|
||||||
allConfigs
|
fail('It should not return any result.');
|
||||||
)
|
},
|
||||||
);
|
error: (error) => {
|
||||||
if (result) {
|
expect(error).toBeInstanceOf(Error);
|
||||||
expect.fail('It should not return any result.');
|
},
|
||||||
} else {
|
complete: () => {
|
||||||
expect(
|
expect(
|
||||||
flowsDataService.resetSilentRenewRunning
|
flowsDataService.resetSilentRenewRunning
|
||||||
).toHaveBeenCalledExactlyOnceWith(allConfigs[0]);
|
).toHaveBeenCalledOnceWith(allConfigs[0]);
|
||||||
}
|
},
|
||||||
} catch (error: any) {
|
});
|
||||||
expect(error).toBeInstanceOf(Error);
|
}));
|
||||||
}
|
|
||||||
});
|
it('should call resetSilentRenewRunning in case of no error', waitForAsync(() => {
|
||||||
it('should call resetSilentRenewRunning in case of no error', async () => {
|
spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue(
|
||||||
vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue(
|
|
||||||
of({} as LoginResponse)
|
of({} as LoginResponse)
|
||||||
);
|
);
|
||||||
vi.spyOn(flowsDataService, 'resetSilentRenewRunning');
|
spyOn(flowsDataService, 'resetSilentRenewRunning');
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -228,42 +209,36 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
refreshSessionService
|
||||||
await firstValueFrom(
|
.userForceRefreshSession(allConfigs[0], allConfigs)
|
||||||
refreshSessionService.userForceRefreshSession(
|
.subscribe({
|
||||||
allConfigs[0]!,
|
error: () => {
|
||||||
allConfigs
|
fail('It should not return any error.');
|
||||||
)
|
},
|
||||||
);
|
complete: () => {
|
||||||
} catch (err: any) {
|
expect(
|
||||||
if (err instanceof EmptyError) {
|
flowsDataService.resetSilentRenewRunning
|
||||||
expect(
|
).toHaveBeenCalledOnceWith(allConfigs[0]);
|
||||||
flowsDataService.resetSilentRenewRunning
|
},
|
||||||
).toHaveBeenCalledExactlyOnceWith(allConfigs[0]);
|
});
|
||||||
} else {
|
}));
|
||||||
expect.fail('It should not return any error.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('forceRefreshSession', () => {
|
describe('forceRefreshSession', () => {
|
||||||
it('only calls start refresh session and returns idToken and accessToken if auth is true', async () => {
|
it('only calls start refresh session and returns idToken and accessToken if auth is true', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('id-token');
|
spyOn(authStateService, 'getIdToken').and.returnValue('id-token');
|
||||||
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
|
spyOn(authStateService, 'getAccessToken').and.returnValue('access-token');
|
||||||
'access-token'
|
|
||||||
);
|
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -271,23 +246,24 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
refreshSessionService
|
||||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(result.idToken).toEqual('id-token');
|
expect(result.idToken).toEqual('id-token');
|
||||||
expect(result.accessToken).toEqual('access-token');
|
expect(result.accessToken).toEqual('access-token');
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('only calls start refresh session and returns null if auth is false', async () => {
|
it('only calls start refresh session and returns null if auth is false', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
@ -297,35 +273,36 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
refreshSessionService
|
||||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
userData: null,
|
userData: null,
|
||||||
idToken: '',
|
idToken: '',
|
||||||
accessToken: '',
|
accessToken: '',
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls start refresh session and waits for completed, returns idtoken and accesstoken if auth is true', async () => {
|
it('calls start refresh session and waits for completed, returns idtoken and accesstoken if auth is true', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
spyOnProperty(
|
spyOnProperty(
|
||||||
silentRenewService,
|
silentRenewService,
|
||||||
'refreshSessionWithIFrameCompleted$'
|
'refreshSessionWithIFrameCompleted$'
|
||||||
).mockReturnValue(
|
).and.returnValue(
|
||||||
of({
|
of({
|
||||||
authResult: {
|
authResult: {
|
||||||
id_token: 'some-id_token',
|
id_token: 'some-id_token',
|
||||||
@ -340,29 +317,30 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
refreshSessionService
|
||||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(result.idToken).toBeDefined();
|
expect(result.idToken).toBeDefined();
|
||||||
expect(result.accessToken).toBeDefined();
|
expect(result.accessToken).toBeDefined();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls start refresh session and waits for completed, returns LoginResponse if auth is false', async () => {
|
it('calls start refresh session and waits for completed, returns LoginResponse if auth is false', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
spyOnProperty(
|
spyOnProperty(
|
||||||
silentRenewService,
|
silentRenewService,
|
||||||
'refreshSessionWithIFrameCompleted$'
|
'refreshSessionWithIFrameCompleted$'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -370,36 +348,35 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
refreshSessionService
|
||||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
userData: null,
|
userData: null,
|
||||||
idToken: '',
|
idToken: '',
|
||||||
accessToken: '',
|
accessToken: '',
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('occurs timeout error and retry mechanism exhausted max retry count throws error', async () => {
|
it('occurs timeout error and retry mechanism exhausted max retry count throws error', fakeAsync(() => {
|
||||||
vi.useRealTimers();
|
spyOn(
|
||||||
vi.useFakeTimers();
|
|
||||||
vi.spyOn(
|
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
spyOnProperty(
|
spyOnProperty(
|
||||||
silentRenewService,
|
silentRenewService,
|
||||||
'refreshSessionWithIFrameCompleted$'
|
'refreshSessionWithIFrameCompleted$'
|
||||||
).mockReturnValue(of(null).pipe(delay(11000)));
|
).and.returnValue(of(null).pipe(delay(11000)));
|
||||||
|
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
@ -409,44 +386,30 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
const resetSilentRenewRunningSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetSilentRenewRunning'
|
'resetSilentRenewRunning'
|
||||||
);
|
);
|
||||||
const expectedInvokeCount = MAX_RETRY_ATTEMPTS;
|
const expectedInvokeCount = MAX_RETRY_ATTEMPTS;
|
||||||
|
|
||||||
try {
|
refreshSessionService
|
||||||
const o$ = refreshSessionService
|
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||||
.forceRefreshSession(allConfigs[0]!, allConfigs)
|
.subscribe({
|
||||||
.pipe(
|
next: () => {
|
||||||
share({
|
fail('It should not return any result.');
|
||||||
connector: () => new ReplaySubject(1),
|
},
|
||||||
resetOnError: false,
|
error: (error) => {
|
||||||
resetOnComplete: false,
|
expect(error).toBeInstanceOf(Error);
|
||||||
resetOnRefCountZero: true,
|
expect(resetSilentRenewRunningSpy).toHaveBeenCalledTimes(
|
||||||
})
|
expectedInvokeCount
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
o$.subscribe();
|
tick(allConfigs[0].silentRenewTimeoutInSeconds * 10000);
|
||||||
|
}));
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(
|
it('occurs unknown error throws it to subscriber', fakeAsync(() => {
|
||||||
allConfigs[0]!.silentRenewTimeoutInSeconds * 10000
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await firstValueFrom(o$);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
expect.fail('It should not return any result.');
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
expect(error).toBeInstanceOf(Error);
|
|
||||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalledTimes(
|
|
||||||
expectedInvokeCount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('occurs unknown error throws it to subscriber', async () => {
|
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -456,41 +419,43 @@ describe('RefreshSessionService ', () => {
|
|||||||
|
|
||||||
const expectedErrorMessage = 'Test error message';
|
const expectedErrorMessage = 'Test error message';
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
spyOnProperty(
|
spyOnProperty(
|
||||||
silentRenewService,
|
silentRenewService,
|
||||||
'refreshSessionWithIFrameCompleted$'
|
'refreshSessionWithIFrameCompleted$'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(throwError(() => new Error(expectedErrorMessage)));
|
).and.returnValue(throwError(() => new Error(expectedErrorMessage)));
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
const resetSilentRenewRunningSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetSilentRenewRunning'
|
'resetSilentRenewRunning'
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
refreshSessionService
|
||||||
await firstValueFrom(
|
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
.subscribe({
|
||||||
);
|
next: () => {
|
||||||
expect.fail('It should not return any result.');
|
fail('It should not return any result.');
|
||||||
} catch (error: any) {
|
},
|
||||||
expect(error).toBeInstanceOf(Error);
|
error: (error) => {
|
||||||
expect(error.message).toEqual(`Error: ${expectedErrorMessage}`);
|
expect(error).toBeInstanceOf(Error);
|
||||||
expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled();
|
expect(error.message).toEqual(`Error: ${expectedErrorMessage}`);
|
||||||
}
|
expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled();
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
describe('NOT isCurrentFlowCodeFlowWithRefreshTokens', () => {
|
describe('NOT isCurrentFlowCodeFlowWithRefreshTokens', () => {
|
||||||
it('does return null when not authenticated', async () => {
|
it('does return null when not authenticated', waitForAsync(() => {
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -498,36 +463,37 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
spyOnProperty(
|
spyOnProperty(
|
||||||
silentRenewService,
|
silentRenewService,
|
||||||
'refreshSessionWithIFrameCompleted$'
|
'refreshSessionWithIFrameCompleted$'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
refreshSessionService
|
||||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
userData: null,
|
userData: null,
|
||||||
idToken: '',
|
idToken: '',
|
||||||
accessToken: '',
|
accessToken: '',
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('return value only returns once', async () => {
|
it('return value only returns once', waitForAsync(() => {
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -535,18 +501,18 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionService as any,
|
refreshSessionService as any,
|
||||||
'startRefreshSession'
|
'startRefreshSession'
|
||||||
).mockReturnValue(of(null));
|
).and.returnValue(of(null));
|
||||||
spyOnProperty(
|
spyOnProperty(
|
||||||
silentRenewService,
|
silentRenewService,
|
||||||
'refreshSessionWithIFrameCompleted$'
|
'refreshSessionWithIFrameCompleted$'
|
||||||
).mockReturnValue(
|
).and.returnValue(
|
||||||
of({
|
of({
|
||||||
authResult: {
|
authResult: {
|
||||||
id_token: 'some-id_token',
|
id_token: 'some-id_token',
|
||||||
@ -554,46 +520,50 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
} as CallbackContext)
|
} as CallbackContext)
|
||||||
);
|
);
|
||||||
const spyInsideMap = vi
|
const spyInsideMap = spyOn(
|
||||||
.spyOn(authStateService, 'areAuthStorageTokensValid')
|
authStateService,
|
||||||
.mockReturnValue(true);
|
'areAuthStorageTokensValid'
|
||||||
|
).and.returnValue(true);
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
refreshSessionService
|
||||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
idToken: 'some-id_token',
|
idToken: 'some-id_token',
|
||||||
accessToken: 'some-access_token',
|
accessToken: 'some-access_token',
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
userData: undefined,
|
userData: undefined,
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
expect(spyInsideMap).toHaveBeenCalledTimes(1);
|
expect(spyInsideMap).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('startRefreshSession', () => {
|
describe('startRefreshSession', () => {
|
||||||
it('returns null if no auth well known endpoint defined', async () => {
|
it('returns null if no auth well known endpoint defined', waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
(refreshSessionService as any)
|
||||||
(refreshSessionService as any).startRefreshSession()
|
.startRefreshSession()
|
||||||
);
|
.subscribe((result: any) => {
|
||||||
expect(result).toBe(null);
|
expect(result).toBe(null);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('returns null if silent renew Is running', async () => {
|
it('returns null if silent renew Is running', waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
(refreshSessionService as any)
|
||||||
(refreshSessionService as any).startRefreshSession()
|
.startRefreshSession()
|
||||||
);
|
.subscribe((result: any) => {
|
||||||
expect(result).toBe(null);
|
expect(result).toBe(null);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls `setSilentRenewRunning` when should be executed', async () => {
|
it('calls `setSilentRenewRunning` when should be executed', waitForAsync(() => {
|
||||||
const setSilentRenewRunningSpy = vi.spyOn(
|
const setSilentRenewRunningSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'setSilentRenewRunning'
|
'setSilentRenewRunning'
|
||||||
);
|
);
|
||||||
@ -604,32 +574,30 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authWellKnownService,
|
authWellKnownService,
|
||||||
'queryAndStoreAuthWellKnownEndPoints'
|
'queryAndStoreAuthWellKnownEndPoints'
|
||||||
).mockReturnValue(of({}));
|
).and.returnValue(of({}));
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
refreshSessionRefreshTokenService,
|
refreshSessionRefreshTokenService,
|
||||||
'refreshSessionWithRefreshTokens'
|
'refreshSessionWithRefreshTokens'
|
||||||
).mockReturnValue(of({} as CallbackContext));
|
).and.returnValue(of({} as CallbackContext));
|
||||||
|
|
||||||
await firstValueFrom(
|
(refreshSessionService as any)
|
||||||
(refreshSessionService as any).startRefreshSession(
|
.startRefreshSession(allConfigs[0], allConfigs)
|
||||||
allConfigs[0]!,
|
.subscribe(() => {
|
||||||
allConfigs
|
expect(setSilentRenewRunningSpy).toHaveBeenCalled();
|
||||||
)
|
});
|
||||||
);
|
}));
|
||||||
expect(setSilentRenewRunningSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls refreshSessionWithRefreshTokens when current flow is codeflow with refresh tokens', async () => {
|
it('calls refreshSessionWithRefreshTokens when current flow is codeflow with refresh tokens', waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'setSilentRenewRunning');
|
spyOn(flowsDataService, 'setSilentRenewRunning');
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -637,34 +605,30 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authWellKnownService,
|
authWellKnownService,
|
||||||
'queryAndStoreAuthWellKnownEndPoints'
|
'queryAndStoreAuthWellKnownEndPoints'
|
||||||
).mockReturnValue(of({}));
|
).and.returnValue(of({}));
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
const refreshSessionWithRefreshTokensSpy = vi
|
const refreshSessionWithRefreshTokensSpy = spyOn(
|
||||||
.spyOn(
|
refreshSessionRefreshTokenService,
|
||||||
refreshSessionRefreshTokenService,
|
'refreshSessionWithRefreshTokens'
|
||||||
'refreshSessionWithRefreshTokens'
|
).and.returnValue(of({} as CallbackContext));
|
||||||
)
|
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
|
||||||
|
|
||||||
await firstValueFrom(
|
(refreshSessionService as any)
|
||||||
(refreshSessionService as any).startRefreshSession(
|
.startRefreshSession(allConfigs[0], allConfigs)
|
||||||
allConfigs[0]!,
|
.subscribe(() => {
|
||||||
allConfigs
|
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
|
||||||
)
|
});
|
||||||
);
|
}));
|
||||||
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls refreshSessionWithIframe when current flow is NOT codeflow with refresh tokens', async () => {
|
it('calls refreshSessionWithIframe when current flow is NOT codeflow with refresh tokens', waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'setSilentRenewRunning');
|
spyOn(flowsDataService, 'setSilentRenewRunning');
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -672,35 +636,32 @@ describe('RefreshSessionService ', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
authWellKnownService,
|
authWellKnownService,
|
||||||
'queryAndStoreAuthWellKnownEndPoints'
|
'queryAndStoreAuthWellKnownEndPoints'
|
||||||
).mockReturnValue(of({}));
|
).and.returnValue(of({}));
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowHelper,
|
flowHelper,
|
||||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
const refreshSessionWithRefreshTokensSpy = vi
|
const refreshSessionWithRefreshTokensSpy = spyOn(
|
||||||
.spyOn(
|
refreshSessionRefreshTokenService,
|
||||||
refreshSessionRefreshTokenService,
|
'refreshSessionWithRefreshTokens'
|
||||||
'refreshSessionWithRefreshTokens'
|
).and.returnValue(of({} as CallbackContext));
|
||||||
)
|
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
|
||||||
|
|
||||||
const refreshSessionWithIframeSpy = vi
|
const refreshSessionWithIframeSpy = spyOn(
|
||||||
.spyOn(refreshSessionIframeService, 'refreshSessionWithIframe')
|
refreshSessionIframeService,
|
||||||
.mockReturnValue(of(false));
|
'refreshSessionWithIframe'
|
||||||
|
).and.returnValue(of(false));
|
||||||
|
|
||||||
await firstValueFrom(
|
(refreshSessionService as any)
|
||||||
(refreshSessionService as any).startRefreshSession(
|
.startRefreshSession(allConfigs[0], allConfigs)
|
||||||
allConfigs[0]!,
|
.subscribe(() => {
|
||||||
allConfigs
|
expect(refreshSessionWithRefreshTokensSpy).not.toHaveBeenCalled();
|
||||||
)
|
expect(refreshSessionWithIframeSpy).toHaveBeenCalled();
|
||||||
);
|
});
|
||||||
expect(refreshSessionWithRefreshTokensSpy).not.toHaveBeenCalled();
|
}));
|
||||||
expect(refreshSessionWithIframeSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import {
|
import {
|
||||||
type Observable,
|
|
||||||
TimeoutError,
|
|
||||||
forkJoin,
|
forkJoin,
|
||||||
|
Observable,
|
||||||
of,
|
of,
|
||||||
throwError,
|
throwError,
|
||||||
|
TimeoutError,
|
||||||
timer,
|
timer,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@ -18,13 +18,13 @@ import {
|
|||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||||
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
|
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import type { CallbackContext } from '../flows/callback-context';
|
import { CallbackContext } from '../flows/callback-context';
|
||||||
import { FlowsDataService } from '../flows/flows-data.service';
|
import { FlowsDataService } from '../flows/flows-data.service';
|
||||||
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
||||||
import { SilentRenewService } from '../iframe/silent-renew.service';
|
import { SilentRenewService } from '../iframe/silent-renew.service';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import type { LoginResponse } from '../login/login-response';
|
import { LoginResponse } from '../login/login-response';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { UserService } from '../user-data/user.service';
|
import { UserService } from '../user-data/user.service';
|
||||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
|
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
|
||||||
import { DataService } from '../../api/data.service';
|
import { DataService } from '../../api/data.service';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { createRetriableStream } from '../../testing/create-retriable-stream.helper';
|
|
||||||
import { mockProvider } from '../../testing/mock';
|
|
||||||
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
||||||
import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||||
|
|
||||||
const DUMMY_WELL_KNOWN_DOCUMENT = {
|
const DUMMY_WELL_KNOWN_DOCUMENT = {
|
||||||
issuer: 'https://identity-server.test/realms/main',
|
issuer: 'https://identity-server.test/realms/main',
|
||||||
@ -39,6 +38,9 @@ describe('AuthWellKnownDataService', () => {
|
|||||||
mockProvider(LoggerService),
|
mockProvider(LoggerService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(AuthWellKnownDataService);
|
service = TestBed.inject(AuthWellKnownDataService);
|
||||||
loggerService = TestBed.inject(LoggerService);
|
loggerService = TestBed.inject(LoggerService);
|
||||||
dataService = TestBed.inject(DataService);
|
dataService = TestBed.inject(DataService);
|
||||||
@ -49,94 +51,92 @@ describe('AuthWellKnownDataService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getWellKnownDocument', () => {
|
describe('getWellKnownDocument', () => {
|
||||||
it('should add suffix if it does not exist on current URL', async () => {
|
it('should add suffix if it does not exist on current URL', waitForAsync(() => {
|
||||||
const dataServiceSpy = vi
|
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||||
.spyOn(dataService, 'get')
|
of(null)
|
||||||
.mockReturnValue(of(null));
|
);
|
||||||
const urlWithoutSuffix = 'myUrl';
|
const urlWithoutSuffix = 'myUrl';
|
||||||
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/openid-configuration`;
|
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/openid-configuration`;
|
||||||
|
|
||||||
await firstValueFrom(
|
(service as any)
|
||||||
(service as any).getWellKnownDocument(urlWithoutSuffix, {
|
.getWellKnownDocument(urlWithoutSuffix, { configId: 'configId1' })
|
||||||
configId: 'configId1',
|
.subscribe(() => {
|
||||||
})
|
expect(dataServiceSpy).toHaveBeenCalledOnceWith(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)
|
||||||
);
|
);
|
||||||
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(urlWithSuffix, {
|
const urlWithSuffix = `myUrl/.well-known/openid-configuration`;
|
||||||
configId: 'configId1',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not add suffix if it does exist on current url', async () => {
|
(service as any)
|
||||||
const dataServiceSpy = vi
|
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
|
||||||
.spyOn(dataService, 'get')
|
.subscribe(() => {
|
||||||
.mockReturnValue(of(null));
|
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||||
const urlWithSuffix = 'myUrl/.well-known/openid-configuration';
|
configId: 'configId1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
await firstValueFrom(
|
it('should not add suffix if it does exist in the middle of current url', waitForAsync(() => {
|
||||||
(service as any).getWellKnownDocument(urlWithSuffix, {
|
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||||
configId: 'configId1',
|
of(null)
|
||||||
})
|
|
||||||
);
|
);
|
||||||
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(urlWithSuffix, {
|
const urlWithSuffix = `myUrl/.well-known/openid-configuration/and/some/more/stuff`;
|
||||||
configId: 'configId1',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not add suffix if it does exist in the middle of current url', async () => {
|
(service as any)
|
||||||
const dataServiceSpy = vi
|
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
|
||||||
.spyOn(dataService, 'get')
|
.subscribe(() => {
|
||||||
.mockReturnValue(of(null));
|
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||||
const urlWithSuffix =
|
configId: 'configId1',
|
||||||
'myUrl/.well-known/openid-configuration/and/some/more/stuff';
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
await firstValueFrom(
|
it('should use the custom suffix provided in the config', waitForAsync(() => {
|
||||||
(service as any).getWellKnownDocument(urlWithSuffix, {
|
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||||
configId: 'configId1',
|
of(null)
|
||||||
})
|
|
||||||
);
|
);
|
||||||
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(urlWithSuffix, {
|
const urlWithoutSuffix = `myUrl`;
|
||||||
configId: 'configId1',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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`;
|
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`;
|
||||||
|
|
||||||
await firstValueFrom(
|
(service as any)
|
||||||
(service as any).getWellKnownDocument(urlWithoutSuffix, {
|
.getWellKnownDocument(urlWithoutSuffix, {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
|
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
|
||||||
})
|
})
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(urlWithSuffix, {
|
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
|
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should retry once', async () => {
|
it('should retry once', waitForAsync(() => {
|
||||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
spyOn(dataService, 'get').and.returnValue(
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => new Error('one')),
|
throwError(() => new Error('one')),
|
||||||
of(DUMMY_WELL_KNOWN_DOCUMENT)
|
of(DUMMY_WELL_KNOWN_DOCUMENT)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const res: unknown = await firstValueFrom(
|
(service as any)
|
||||||
(service as any).getWellKnownDocument('anyurl', {
|
.getWellKnownDocument('anyurl', { configId: 'configId1' })
|
||||||
configId: 'configId1',
|
.subscribe({
|
||||||
})
|
next: (res: unknown) => {
|
||||||
);
|
expect(res).toBeTruthy();
|
||||||
expect(res).toBeTruthy();
|
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
|
||||||
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
|
},
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should retry twice', async () => {
|
it('should retry twice', waitForAsync(() => {
|
||||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
spyOn(dataService, 'get').and.returnValue(
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => new Error('one')),
|
throwError(() => new Error('one')),
|
||||||
throwError(() => new Error('two')),
|
throwError(() => new Error('two')),
|
||||||
@ -144,17 +144,18 @@ describe('AuthWellKnownDataService', () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const res: any = await firstValueFrom(
|
(service as any)
|
||||||
(service as any).getWellKnownDocument('anyurl', {
|
.getWellKnownDocument('anyurl', { configId: 'configId1' })
|
||||||
configId: 'configId1',
|
.subscribe({
|
||||||
})
|
next: (res: any) => {
|
||||||
);
|
expect(res).toBeTruthy();
|
||||||
expect(res).toBeTruthy();
|
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
|
||||||
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
|
},
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should fail after three tries', async () => {
|
it('should fail after three tries', waitForAsync(() => {
|
||||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
spyOn(dataService, 'get').and.returnValue(
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => new Error('one')),
|
throwError(() => new Error('one')),
|
||||||
throwError(() => new Error('two')),
|
throwError(() => new Error('two')),
|
||||||
@ -163,57 +164,55 @@ describe('AuthWellKnownDataService', () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
(service as any).getWellKnownDocument('anyurl', 'configId').subscribe({
|
||||||
await firstValueFrom(
|
error: (err: unknown) => {
|
||||||
(service as any).getWellKnownDocument('anyurl', 'configId')
|
expect(err).toBeTruthy();
|
||||||
);
|
},
|
||||||
} catch (err: unknown) {
|
});
|
||||||
expect(err).toBeTruthy();
|
}));
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getWellKnownEndPointsForConfig', () => {
|
describe('getWellKnownEndPointsForConfig', () => {
|
||||||
it('calling internal getWellKnownDocument and maps', async () => {
|
it('calling internal getWellKnownDocument and maps', waitForAsync(() => {
|
||||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
spyOn(dataService, 'get').and.returnValue(of({ jwks_uri: 'jwks_uri' }));
|
||||||
of({ jwks_uri: 'jwks_uri' })
|
|
||||||
);
|
|
||||||
|
|
||||||
const spy = vi.spyOn(service as any, 'getWellKnownDocument');
|
const spy = spyOn(
|
||||||
|
service as any,
|
||||||
|
'getWellKnownDocument'
|
||||||
|
).and.callThrough();
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
service
|
||||||
service.getWellKnownEndPointsForConfig({
|
.getWellKnownEndPointsForConfig({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
authWellknownEndpointUrl: 'any-url',
|
authWellknownEndpointUrl: 'any-url',
|
||||||
})
|
})
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
expect((result as any).jwks_uri).toBeUndefined();
|
expect((result as any).jwks_uri).toBeUndefined();
|
||||||
expect(result.jwksUri).toBe('jwks_uri');
|
expect(result.jwksUri).toBe('jwks_uri');
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('throws error and logs if no authwellknownUrl is given', async () => {
|
it('throws error and logs if no authwellknownUrl is given', waitForAsync(() => {
|
||||||
const loggerSpy = vi.spyOn(loggerService, 'logError');
|
const loggerSpy = spyOn(loggerService, 'logError');
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
authWellknownEndpointUrl: undefined,
|
authWellknownEndpointUrl: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
service.getWellKnownEndPointsForConfig(config).subscribe({
|
||||||
await firstValueFrom(service.getWellKnownEndPointsForConfig(config));
|
error: (error) => {
|
||||||
} catch (error: any) {
|
expect(loggerSpy).toHaveBeenCalledOnceWith(
|
||||||
expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
|
config,
|
||||||
config,
|
'no authWellknownEndpoint given!'
|
||||||
'no authWellknownEndpoint given!'
|
);
|
||||||
);
|
expect(error.message).toEqual('no authWellknownEndpoint given!');
|
||||||
expect(error.message).toEqual('no authWellknownEndpoint given!');
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('should merge the mapped endpoints with the provided endpoints', async () => {
|
it('should merge the mapped endpoints with the provided endpoints', waitForAsync(() => {
|
||||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
spyOn(dataService, 'get').and.returnValue(of(DUMMY_WELL_KNOWN_DOCUMENT));
|
||||||
of(DUMMY_WELL_KNOWN_DOCUMENT)
|
|
||||||
);
|
|
||||||
|
|
||||||
const expected: AuthWellKnownEndpoints = {
|
const expected: AuthWellKnownEndpoints = {
|
||||||
endSessionEndpoint: 'config-endSessionEndpoint',
|
endSessionEndpoint: 'config-endSessionEndpoint',
|
||||||
@ -221,8 +220,8 @@ describe('AuthWellKnownDataService', () => {
|
|||||||
jwksUri: DUMMY_WELL_KNOWN_DOCUMENT.jwks_uri,
|
jwksUri: DUMMY_WELL_KNOWN_DOCUMENT.jwks_uri,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
service
|
||||||
service.getWellKnownEndPointsForConfig({
|
.getWellKnownEndPointsForConfig({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
authWellknownEndpointUrl: 'any-url',
|
authWellknownEndpointUrl: 'any-url',
|
||||||
authWellknownEndpoints: {
|
authWellknownEndpoints: {
|
||||||
@ -230,8 +229,9 @@ describe('AuthWellKnownDataService', () => {
|
|||||||
revocationEndpoint: 'config-revocationEndpoint',
|
revocationEndpoint: 'config-revocationEndpoint',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(result).toEqual(expect.objectContaining(expected));
|
expect(result).toEqual(jasmine.objectContaining(expected));
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { inject, Injectable } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, throwError } from 'rxjs';
|
import { Observable, throwError } from 'rxjs';
|
||||||
import { map, retry } from 'rxjs/operators';
|
import { map, retry } from 'rxjs/operators';
|
||||||
import { DataService } from '../../api/data.service';
|
import { DataService } from '../../api/data.service';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
import { OpenIdConfiguration } from '../openid-configuration';
|
||||||
import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||||
|
|
||||||
const WELL_KNOWN_SUFFIX = '/.well-known/openid-configuration';
|
const WELL_KNOWN_SUFFIX = `/.well-known/openid-configuration`;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthWellKnownDataService {
|
export class AuthWellKnownDataService {
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
import { EventTypes } from '../../public-events/event-types';
|
import { EventTypes } from '../../public-events/event-types';
|
||||||
import { PublicEventsService } from '../../public-events/public-events.service';
|
import { PublicEventsService } from '../../public-events/public-events.service';
|
||||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||||
import { mockProvider } from '../../testing/mock';
|
|
||||||
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
||||||
import { AuthWellKnownService } from './auth-well-known.service';
|
import { AuthWellKnownService } from './auth-well-known.service';
|
||||||
|
|
||||||
@ -23,6 +22,9 @@ describe('AuthWellKnownService', () => {
|
|||||||
mockProvider(StoragePersistenceService),
|
mockProvider(StoragePersistenceService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(AuthWellKnownService);
|
service = TestBed.inject(AuthWellKnownService);
|
||||||
dataService = TestBed.inject(AuthWellKnownDataService);
|
dataService = TestBed.inject(AuthWellKnownDataService);
|
||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
@ -34,75 +36,75 @@ describe('AuthWellKnownService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getAuthWellKnownEndPoints', () => {
|
describe('getAuthWellKnownEndPoints', () => {
|
||||||
it('getAuthWellKnownEndPoints throws an error if not config provided', async () => {
|
it('getAuthWellKnownEndPoints throws an error if not config provided', waitForAsync(() => {
|
||||||
try {
|
service.queryAndStoreAuthWellKnownEndPoints(null).subscribe({
|
||||||
await firstValueFrom(service.queryAndStoreAuthWellKnownEndPoints(null));
|
error: (error) => {
|
||||||
} catch (error) {
|
expect(error).toEqual(
|
||||||
expect(error).toEqual(
|
new Error(
|
||||||
new Error(
|
'Please provide a configuration before setting up the module'
|
||||||
'Please provide a configuration before setting up the module'
|
)
|
||||||
)
|
);
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('getAuthWellKnownEndPoints calls always dataservice', async () => {
|
it('getAuthWellKnownEndPoints calls always dataservice', waitForAsync(() => {
|
||||||
const dataServiceSpy = vi
|
const dataServiceSpy = spyOn(
|
||||||
.spyOn(dataService, 'getWellKnownEndPointsForConfig')
|
dataService,
|
||||||
.mockReturnValue(of({ issuer: 'anything' }));
|
'getWellKnownEndPointsForConfig'
|
||||||
|
).and.returnValue(of({ issuer: 'anything' }));
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue({ issuer: 'anything' });
|
||||||
() => ({ issuer: 'anything' })
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
service
|
||||||
service.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(storagePersistenceService.read).not.toHaveBeenCalled();
|
expect(storagePersistenceService.read).not.toHaveBeenCalled();
|
||||||
expect(dataServiceSpy).toHaveBeenCalled();
|
expect(dataServiceSpy).toHaveBeenCalled();
|
||||||
expect(result).toEqual({ issuer: 'anything' });
|
expect(result).toEqual({ issuer: 'anything' });
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('getAuthWellKnownEndPoints stored the result if http call is made', async () => {
|
it('getAuthWellKnownEndPoints stored the result if http call is made', waitForAsync(() => {
|
||||||
const dataServiceSpy = vi
|
const dataServiceSpy = spyOn(
|
||||||
.spyOn(dataService, 'getWellKnownEndPointsForConfig')
|
dataService,
|
||||||
.mockReturnValue(of({ issuer: 'anything' }));
|
'getWellKnownEndPointsForConfig'
|
||||||
|
).and.returnValue(of({ issuer: 'anything' }));
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue(null);
|
||||||
() => null
|
const storeSpy = spyOn(service, 'storeWellKnownEndpoints');
|
||||||
);
|
|
||||||
const storeSpy = vi.spyOn(service, 'storeWellKnownEndpoints');
|
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
service
|
||||||
service.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(dataServiceSpy).toHaveBeenCalled();
|
expect(dataServiceSpy).toHaveBeenCalled();
|
||||||
expect(storeSpy).toHaveBeenCalled();
|
expect(storeSpy).toHaveBeenCalled();
|
||||||
expect(result).toEqual({ issuer: 'anything' });
|
expect(result).toEqual({ issuer: 'anything' });
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('throws `ConfigLoadingFailed` event when error happens from http', async () => {
|
it('throws `ConfigLoadingFailed` event when error happens from http', waitForAsync(() => {
|
||||||
vi.spyOn(dataService, 'getWellKnownEndPointsForConfig').mockReturnValue(
|
spyOn(dataService, 'getWellKnownEndPointsForConfig').and.returnValue(
|
||||||
throwError(() => new Error('error'))
|
throwError(() => new Error('error'))
|
||||||
);
|
);
|
||||||
const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent');
|
const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent');
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||||
service.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err).toBeTruthy();
|
||||||
expect(err).toBeTruthy();
|
expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1);
|
expect(publicEventsServiceSpy).toHaveBeenCalledOnceWith(
|
||||||
expect(publicEventsServiceSpy).toHaveBeenCalledExactlyOnceWith(
|
EventTypes.ConfigLoadingFailed,
|
||||||
EventTypes.ConfigLoadingFailed,
|
null
|
||||||
null
|
);
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, throwError } from 'rxjs';
|
import { Observable, throwError } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
import { EventTypes } from '../../public-events/event-types';
|
import { EventTypes } from '../../public-events/event-types';
|
||||||
import { PublicEventsService } from '../../public-events/public-events.service';
|
import { PublicEventsService } from '../../public-events/public-events.service';
|
||||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
import { OpenIdConfiguration } from '../openid-configuration';
|
||||||
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
||||||
import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthWellKnownService {
|
export class AuthWellKnownService {
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom, of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockAbstractProvider, mockProvider } from '../../test/auto-mock';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { EventTypes } from '../public-events/event-types';
|
import { EventTypes } from '../public-events/event-types';
|
||||||
import { PublicEventsService } from '../public-events/public-events.service';
|
import { PublicEventsService } from '../public-events/public-events.service';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { mockAbstractProvider, mockProvider } from '../testing/mock';
|
|
||||||
import { PlatformProvider } from '../utils/platform-provider/platform.provider';
|
import { PlatformProvider } from '../utils/platform-provider/platform.provider';
|
||||||
import { AuthWellKnownService } from './auth-well-known/auth-well-known.service';
|
import { AuthWellKnownService } from './auth-well-known/auth-well-known.service';
|
||||||
import { ConfigurationService } from './config.service';
|
import { ConfigurationService } from './config.service';
|
||||||
import { StsConfigLoader, StsConfigStaticLoader } from './loader/config-loader';
|
import { StsConfigLoader, StsConfigStaticLoader } from './loader/config-loader';
|
||||||
import type { OpenIdConfiguration } from './openid-configuration';
|
import { OpenIdConfiguration } from './openid-configuration';
|
||||||
import { ConfigValidationService } from './validation/config-validation.service';
|
import { ConfigValidationService } from './validation/config-validation.service';
|
||||||
|
|
||||||
describe('Configuration Service', () => {
|
describe('Configuration Service', () => {
|
||||||
@ -35,6 +34,9 @@ describe('Configuration Service', () => {
|
|||||||
mockAbstractProvider(StsConfigLoader, StsConfigStaticLoader),
|
mockAbstractProvider(StsConfigLoader, StsConfigStaticLoader),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
configService = TestBed.inject(ConfigurationService);
|
configService = TestBed.inject(ConfigurationService);
|
||||||
publicEventsService = TestBed.inject(PublicEventsService);
|
publicEventsService = TestBed.inject(PublicEventsService);
|
||||||
authWellKnownService = TestBed.inject(AuthWellKnownService);
|
authWellKnownService = TestBed.inject(AuthWellKnownService);
|
||||||
@ -86,110 +88,98 @@ describe('Configuration Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getOpenIDConfiguration', () => {
|
describe('getOpenIDConfiguration', () => {
|
||||||
it(`if config is already saved 'loadConfigs' is not called`, async () => {
|
it(`if config is already saved 'loadConfigs' is not called`, waitForAsync(() => {
|
||||||
(configService as any).configsInternal = {
|
(configService as any).configsInternal = {
|
||||||
configId1: { configId: 'configId1' },
|
configId1: { configId: 'configId1' },
|
||||||
configId2: { configId: 'configId2' },
|
configId2: { configId: 'configId2' },
|
||||||
};
|
};
|
||||||
const spy = vi.spyOn(configService as any, 'loadConfigs');
|
const spy = spyOn(configService as any, 'loadConfigs');
|
||||||
|
|
||||||
const config = await firstValueFrom(
|
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||||
configService.getOpenIDConfiguration('configId1')
|
expect(config).toBeTruthy();
|
||||||
);
|
expect(spy).not.toHaveBeenCalled();
|
||||||
expect(config).toBeTruthy();
|
});
|
||||||
expect(spy).not.toHaveBeenCalled();
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it(`if config is NOT already saved 'loadConfigs' is called`, async () => {
|
it(`if config is NOT already saved 'loadConfigs' is called`, waitForAsync(() => {
|
||||||
const configs = [{ configId: 'configId1' }, { configId: 'configId2' }];
|
const configs = [{ configId: 'configId1' }, { configId: 'configId2' }];
|
||||||
const spy = vi
|
const spy = spyOn(configService as any, 'loadConfigs').and.returnValue(
|
||||||
.spyOn(configService as any, 'loadConfigs')
|
|
||||||
.mockReturnValue(of(configs));
|
|
||||||
|
|
||||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
|
||||||
|
|
||||||
const config = await firstValueFrom(
|
|
||||||
configService.getOpenIDConfiguration('configId1')
|
|
||||||
);
|
|
||||||
expect(config).toBeTruthy();
|
|
||||||
expect(spy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns null if config is not valid', async () => {
|
|
||||||
const configs = [{ configId: 'configId1' }];
|
|
||||||
|
|
||||||
vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
|
|
||||||
of(configs)
|
of(configs)
|
||||||
);
|
);
|
||||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(
|
|
||||||
false
|
|
||||||
);
|
|
||||||
const consoleSpy = vi.spyOn(console, 'warn');
|
|
||||||
|
|
||||||
const config = await firstValueFrom(
|
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||||
configService.getOpenIDConfiguration('configId1')
|
|
||||||
);
|
|
||||||
expect(config).toBeNull();
|
|
||||||
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', async () => {
|
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||||
|
expect(config).toBeTruthy();
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it(`returns null if config is not valid`, waitForAsync(() => {
|
||||||
|
const configs = [{ configId: 'configId1' }];
|
||||||
|
|
||||||
|
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs));
|
||||||
|
spyOn(configValidationService, 'validateConfig').and.returnValue(false);
|
||||||
|
const consoleSpy = spyOn(console, 'warn');
|
||||||
|
|
||||||
|
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||||
|
expect(config).toBeNull();
|
||||||
|
expect(consoleSpy).toHaveBeenCalledOnceWith(`[oidc-client-rx] No configuration found for config id 'configId1'.`)
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it(`returns null if configs are stored but not existing ID is passed`, waitForAsync(() => {
|
||||||
(configService as any).configsInternal = {
|
(configService as any).configsInternal = {
|
||||||
configId1: { configId: 'configId1' },
|
configId1: { configId: 'configId1' },
|
||||||
configId2: { configId: 'configId2' },
|
configId2: { configId: 'configId2' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = await firstValueFrom(
|
configService
|
||||||
configService.getOpenIDConfiguration('notExisting')
|
.getOpenIDConfiguration('notExisting')
|
||||||
);
|
.subscribe((config) => {
|
||||||
expect(config).toBeNull();
|
expect(config).toBeNull();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('sets authWellKnownEndPoints on config if authWellKnownEndPoints is stored', async () => {
|
it(`sets authWellKnownEndPoints on config if authWellKnownEndPoints is stored`, waitForAsync(() => {
|
||||||
const configs = [{ configId: 'configId1' }];
|
const configs = [{ configId: 'configId1' }];
|
||||||
|
|
||||||
vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
|
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs));
|
||||||
of(configs)
|
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||||
);
|
const consoleSpy = spyOn(console, 'warn');
|
||||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
|
||||||
const consoleSpy = vi.spyOn(console, 'warn');
|
|
||||||
|
|
||||||
vi.spyOn(storagePersistenceService, 'read').mockReturnValue({
|
spyOn(storagePersistenceService, 'read').and.returnValue({
|
||||||
issuer: 'auth-well-known',
|
issuer: 'auth-well-known',
|
||||||
});
|
});
|
||||||
|
|
||||||
const config = await firstValueFrom(
|
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||||
configService.getOpenIDConfiguration('configId1')
|
expect(config?.authWellknownEndpoints).toEqual({
|
||||||
);
|
issuer: 'auth-well-known',
|
||||||
expect(config?.authWellknownEndpoints).toEqual({
|
});
|
||||||
issuer: 'auth-well-known',
|
expect(consoleSpy).not.toHaveBeenCalled()
|
||||||
});
|
});
|
||||||
expect(consoleSpy).not.toHaveBeenCalled();
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('fires ConfigLoaded if authWellKnownEndPoints is stored', async () => {
|
it(`fires ConfigLoaded if authWellKnownEndPoints is stored`, waitForAsync(() => {
|
||||||
const configs = [{ configId: 'configId1' }];
|
const configs = [{ configId: 'configId1' }];
|
||||||
|
|
||||||
vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
|
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs));
|
||||||
of(configs)
|
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||||
);
|
spyOn(storagePersistenceService, 'read').and.returnValue({
|
||||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
|
||||||
vi.spyOn(storagePersistenceService, 'read').mockReturnValue({
|
|
||||||
issuer: 'auth-well-known',
|
issuer: 'auth-well-known',
|
||||||
});
|
});
|
||||||
|
|
||||||
const spy = vi.spyOn(publicEventsService, 'fireEvent');
|
const spy = spyOn(publicEventsService, 'fireEvent');
|
||||||
|
|
||||||
await firstValueFrom(configService.getOpenIDConfiguration('configId1'));
|
configService.getOpenIDConfiguration('configId1').subscribe(() => {
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
expect(spy).toHaveBeenCalledOnceWith(
|
||||||
EventTypes.ConfigLoaded,
|
EventTypes.ConfigLoaded,
|
||||||
expect.anything()
|
jasmine.anything()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('stores, uses and fires event when authwellknownendpoints are passed', async () => {
|
it(`stores, uses and fires event when authwellknownendpoints are passed`, waitForAsync(() => {
|
||||||
const configs = [
|
const configs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -197,96 +187,92 @@ describe('Configuration Service', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
|
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs));
|
||||||
of(configs)
|
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||||
);
|
spyOn(storagePersistenceService, 'read').and.returnValue(null);
|
||||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
|
||||||
vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null);
|
|
||||||
|
|
||||||
const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent');
|
const fireEventSpy = spyOn(publicEventsService, 'fireEvent');
|
||||||
const storeWellKnownEndpointsSpy = vi.spyOn(
|
const storeWellKnownEndpointsSpy = spyOn(
|
||||||
authWellKnownService,
|
authWellKnownService,
|
||||||
'storeWellKnownEndpoints'
|
'storeWellKnownEndpoints'
|
||||||
);
|
);
|
||||||
|
|
||||||
const config = await firstValueFrom(
|
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||||
configService.getOpenIDConfiguration('configId1')
|
expect(config).toBeTruthy();
|
||||||
);
|
expect(fireEventSpy).toHaveBeenCalledOnceWith(
|
||||||
expect(config).toBeTruthy();
|
EventTypes.ConfigLoaded,
|
||||||
expect(fireEventSpy).toHaveBeenCalledExactlyOnceWith(
|
jasmine.anything()
|
||||||
EventTypes.ConfigLoaded,
|
);
|
||||||
expect.anything()
|
expect(storeWellKnownEndpointsSpy).toHaveBeenCalledOnceWith(
|
||||||
);
|
config as OpenIdConfiguration,
|
||||||
expect(storeWellKnownEndpointsSpy).toHaveBeenCalledExactlyOnceWith(
|
{
|
||||||
config as OpenIdConfiguration,
|
issuer: 'auth-well-known',
|
||||||
{
|
}
|
||||||
issuer: 'auth-well-known',
|
);
|
||||||
}
|
});
|
||||||
);
|
}));
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getOpenIDConfigurations', () => {
|
describe('getOpenIDConfigurations', () => {
|
||||||
it('returns correct result', async () => {
|
it(`returns correct result`, waitForAsync(() => {
|
||||||
vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue(
|
spyOn(stsConfigLoader, 'loadConfigs').and.returnValue(
|
||||||
of([
|
of([
|
||||||
{ configId: 'configId1' } as OpenIdConfiguration,
|
{ configId: 'configId1' } as OpenIdConfiguration,
|
||||||
{ configId: 'configId2' } as OpenIdConfiguration,
|
{ configId: 'configId2' } as OpenIdConfiguration,
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
configService.getOpenIDConfigurations('configId1').subscribe((result) => {
|
||||||
configService.getOpenIDConfigurations('configId1')
|
expect(result.allConfigs.length).toEqual(2);
|
||||||
);
|
expect(result.currentConfig).toBeTruthy();
|
||||||
expect(result.allConfigs.length).toEqual(2);
|
});
|
||||||
expect(result.currentConfig).toBeTruthy();
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('created configId when configId is not set', async () => {
|
it(`created configId when configId is not set`, waitForAsync(() => {
|
||||||
vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue(
|
spyOn(stsConfigLoader, 'loadConfigs').and.returnValue(
|
||||||
of([
|
of([
|
||||||
{ clientId: 'clientId1' } as OpenIdConfiguration,
|
{ clientId: 'clientId1' } as OpenIdConfiguration,
|
||||||
{ clientId: 'clientId2' } as OpenIdConfiguration,
|
{ clientId: 'clientId2' } as OpenIdConfiguration,
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
configService.getOpenIDConfigurations().subscribe((result) => {
|
||||||
configService.getOpenIDConfigurations()
|
expect(result.allConfigs.length).toEqual(2);
|
||||||
);
|
const allConfigIds = result.allConfigs.map((x) => x.configId);
|
||||||
expect(result.allConfigs.length).toEqual(2);
|
|
||||||
const allConfigIds = result.allConfigs.map((x) => x.configId);
|
|
||||||
expect(allConfigIds).toEqual(['0-clientId1', '1-clientId2']);
|
|
||||||
expect(result.currentConfig).toBeTruthy();
|
|
||||||
expect(result.currentConfig?.configId).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns empty array if config is not valid', async () => {
|
expect(allConfigIds).toEqual(['0-clientId1', '1-clientId2']);
|
||||||
vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue(
|
|
||||||
|
expect(result.currentConfig).toBeTruthy();
|
||||||
|
expect(result.currentConfig?.configId).toBeTruthy();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it(`returns empty array if config is not valid`, waitForAsync(() => {
|
||||||
|
spyOn(stsConfigLoader, 'loadConfigs').and.returnValue(
|
||||||
of([
|
of([
|
||||||
{ configId: 'configId1' } as OpenIdConfiguration,
|
{ configId: 'configId1' } as OpenIdConfiguration,
|
||||||
{ configId: 'configId2' } as OpenIdConfiguration,
|
{ configId: 'configId2' } as OpenIdConfiguration,
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(configValidationService, 'validateConfigs').mockReturnValue(
|
spyOn(configValidationService, 'validateConfigs').and.returnValue(false);
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
const { allConfigs, currentConfig } = await firstValueFrom(
|
configService
|
||||||
configService.getOpenIDConfigurations()
|
.getOpenIDConfigurations()
|
||||||
);
|
.subscribe(({ allConfigs, currentConfig }) => {
|
||||||
expect(allConfigs).toEqual([]);
|
expect(allConfigs).toEqual([]);
|
||||||
expect(currentConfig).toBeNull();
|
expect(currentConfig).toBeNull();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setSpecialCases', () => {
|
describe('setSpecialCases', () => {
|
||||||
it('should set special cases when current platform is browser', () => {
|
it(`should set special cases when current platform is browser`, () => {
|
||||||
vi.spyOn(platformProvider, 'isBrowser').mockReturnValue(false);
|
spyOn(platformProvider, 'isBrowser').and.returnValue(false);
|
||||||
|
|
||||||
const config = { configId: 'configId1' } as OpenIdConfiguration;
|
const config = { configId: 'configId1' } as OpenIdConfiguration;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import {inject, Injectable, isDevMode} from 'injection-js';
|
||||||
import { type Observable, forkJoin, of } from 'rxjs';
|
import { forkJoin, Observable, of } from 'rxjs';
|
||||||
import { concatMap, map } from 'rxjs/operators';
|
import { concatMap, map } from 'rxjs/operators';
|
||||||
import { injectAbstractType } from '../injection/inject';
|
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { EventTypes } from '../public-events/event-types';
|
import { EventTypes } from '../public-events/event-types';
|
||||||
import { PublicEventsService } from '../public-events/public-events.service';
|
import { PublicEventsService } from '../public-events/public-events.service';
|
||||||
@ -10,7 +9,7 @@ import { PlatformProvider } from '../utils/platform-provider/platform.provider';
|
|||||||
import { AuthWellKnownService } from './auth-well-known/auth-well-known.service';
|
import { AuthWellKnownService } from './auth-well-known/auth-well-known.service';
|
||||||
import { DEFAULT_CONFIG } from './default-config';
|
import { DEFAULT_CONFIG } from './default-config';
|
||||||
import { StsConfigLoader } from './loader/config-loader';
|
import { StsConfigLoader } from './loader/config-loader';
|
||||||
import type { OpenIdConfiguration } from './openid-configuration';
|
import { OpenIdConfiguration } from './openid-configuration';
|
||||||
import { ConfigValidationService } from './validation/config-validation.service';
|
import { ConfigValidationService } from './validation/config-validation.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -27,7 +26,7 @@ export class ConfigurationService {
|
|||||||
|
|
||||||
private readonly authWellKnownService = inject(AuthWellKnownService);
|
private readonly authWellKnownService = inject(AuthWellKnownService);
|
||||||
|
|
||||||
private readonly loader = injectAbstractType(StsConfigLoader);
|
private readonly loader = inject(StsConfigLoader);
|
||||||
|
|
||||||
private readonly configValidationService = inject(ConfigValidationService);
|
private readonly configValidationService = inject(ConfigValidationService);
|
||||||
|
|
||||||
@ -85,14 +84,11 @@ export class ConfigurationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getConfig(configId?: string): OpenIdConfiguration | null {
|
private getConfig(configId?: string): OpenIdConfiguration | null {
|
||||||
if (configId) {
|
if (Boolean(configId)) {
|
||||||
const config = this.configsInternal[configId!];
|
const config = this.configsInternal[configId!];
|
||||||
|
|
||||||
if (!config) {
|
if(!config && isDevMode()) {
|
||||||
// biome-ignore lint/suspicious/noConsole: <explanation>
|
console.warn(`[oidc-client-rx] No configuration found for config id '${configId}'.`);
|
||||||
console.warn(
|
|
||||||
`[oidc-client-rx] No configuration found for config id '${configId}'.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return config || null;
|
return config || null;
|
||||||
@ -169,7 +165,7 @@ export class ConfigurationService {
|
|||||||
configuration
|
configuration
|
||||||
);
|
);
|
||||||
|
|
||||||
if (alreadyExistingAuthWellKnownEndpoints) {
|
if (!!alreadyExistingAuthWellKnownEndpoints) {
|
||||||
configuration.authWellknownEndpoints =
|
configuration.authWellknownEndpoints =
|
||||||
alreadyExistingAuthWellKnownEndpoints;
|
alreadyExistingAuthWellKnownEndpoints;
|
||||||
|
|
||||||
@ -178,7 +174,7 @@ export class ConfigurationService {
|
|||||||
|
|
||||||
const passedAuthWellKnownEndpoints = configuration.authWellknownEndpoints;
|
const passedAuthWellKnownEndpoints = configuration.authWellknownEndpoints;
|
||||||
|
|
||||||
if (passedAuthWellKnownEndpoints) {
|
if (!!passedAuthWellKnownEndpoints) {
|
||||||
this.authWellKnownService.storeWellKnownEndpoints(
|
this.authWellKnownService.storeWellKnownEndpoints(
|
||||||
configuration,
|
configuration,
|
||||||
passedAuthWellKnownEndpoints
|
passedAuthWellKnownEndpoints
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { LogLevel } from '../logging/log-level';
|
import { LogLevel } from '../logging/log-level';
|
||||||
import type { OpenIdConfiguration } from './openid-configuration';
|
import { OpenIdConfiguration } from './openid-configuration';
|
||||||
|
|
||||||
export const DEFAULT_CONFIG: OpenIdConfiguration = {
|
export const DEFAULT_CONFIG: OpenIdConfiguration = {
|
||||||
authority: 'https://please_set',
|
authority: 'https://please_set',
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { firstValueFrom, of } from 'rxjs';
|
import { waitForAsync } from '@angular/core/testing';
|
||||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
import { of } from 'rxjs';
|
||||||
|
import { OpenIdConfiguration } from '../openid-configuration';
|
||||||
import { StsConfigHttpLoader, StsConfigStaticLoader } from './config-loader';
|
import { StsConfigHttpLoader, StsConfigStaticLoader } from './config-loader';
|
||||||
|
|
||||||
describe('ConfigLoader', () => {
|
describe('ConfigLoader', () => {
|
||||||
describe('StsConfigStaticLoader', () => {
|
describe('StsConfigStaticLoader', () => {
|
||||||
describe('loadConfigs', () => {
|
describe('loadConfigs', () => {
|
||||||
it('returns an array if an array is passed', async () => {
|
it('returns an array if an array is passed', waitForAsync(() => {
|
||||||
const toPass = [
|
const toPass = [
|
||||||
{ configId: 'configId1' } as OpenIdConfiguration,
|
{ configId: 'configId1' } as OpenIdConfiguration,
|
||||||
{ configId: 'configId2' } as OpenIdConfiguration,
|
{ configId: 'configId2' } as OpenIdConfiguration,
|
||||||
@ -15,26 +16,28 @@ describe('ConfigLoader', () => {
|
|||||||
|
|
||||||
const result$ = loader.loadConfigs();
|
const result$ = loader.loadConfigs();
|
||||||
|
|
||||||
const result = await firstValueFrom(result$);
|
result$.subscribe((result) => {
|
||||||
expect(Array.isArray(result)).toBeTruthy();
|
expect(Array.isArray(result)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('returns an array if only one config is passed', async () => {
|
it('returns an array if only one config is passed', waitForAsync(() => {
|
||||||
const loader = new StsConfigStaticLoader({
|
const loader = new StsConfigStaticLoader({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
} as OpenIdConfiguration);
|
} as OpenIdConfiguration);
|
||||||
|
|
||||||
const result$ = loader.loadConfigs();
|
const result$ = loader.loadConfigs();
|
||||||
|
|
||||||
const result = await firstValueFrom(result$);
|
result$.subscribe((result) => {
|
||||||
expect(Array.isArray(result)).toBeTruthy();
|
expect(Array.isArray(result)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('StsConfigHttpLoader', () => {
|
describe('StsConfigHttpLoader', () => {
|
||||||
describe('loadConfigs', () => {
|
describe('loadConfigs', () => {
|
||||||
it('returns an array if an array of observables is passed', async () => {
|
it('returns an array if an array of observables is passed', waitForAsync(() => {
|
||||||
const toPass = [
|
const toPass = [
|
||||||
of({ configId: 'configId1' } as OpenIdConfiguration),
|
of({ configId: 'configId1' } as OpenIdConfiguration),
|
||||||
of({ configId: 'configId2' } as OpenIdConfiguration),
|
of({ configId: 'configId2' } as OpenIdConfiguration),
|
||||||
@ -43,13 +46,14 @@ describe('ConfigLoader', () => {
|
|||||||
|
|
||||||
const result$ = loader.loadConfigs();
|
const result$ = loader.loadConfigs();
|
||||||
|
|
||||||
const result = await firstValueFrom(result$);
|
result$.subscribe((result) => {
|
||||||
expect(Array.isArray(result)).toBeTruthy();
|
expect(Array.isArray(result)).toBeTrue();
|
||||||
expect(result[0]!.configId).toBe('configId1');
|
expect(result[0].configId).toBe('configId1');
|
||||||
expect(result[1]!.configId).toBe('configId2');
|
expect(result[1].configId).toBe('configId2');
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('returns an array if an observable with a config array is passed', async () => {
|
it('returns an array if an observable with a config array is passed', waitForAsync(() => {
|
||||||
const toPass = of([
|
const toPass = of([
|
||||||
{ configId: 'configId1' } as OpenIdConfiguration,
|
{ configId: 'configId1' } as OpenIdConfiguration,
|
||||||
{ configId: 'configId2' } as OpenIdConfiguration,
|
{ configId: 'configId2' } as OpenIdConfiguration,
|
||||||
@ -58,23 +62,25 @@ describe('ConfigLoader', () => {
|
|||||||
|
|
||||||
const result$ = loader.loadConfigs();
|
const result$ = loader.loadConfigs();
|
||||||
|
|
||||||
const result = await firstValueFrom(result$);
|
result$.subscribe((result) => {
|
||||||
expect(Array.isArray(result)).toBeTruthy();
|
expect(Array.isArray(result)).toBeTrue();
|
||||||
expect(result[0]!.configId).toBe('configId1');
|
expect(result[0].configId).toBe('configId1');
|
||||||
expect(result[1]!.configId).toBe('configId2');
|
expect(result[1].configId).toBe('configId2');
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('returns an array if only one config is passed', async () => {
|
it('returns an array if only one config is passed', waitForAsync(() => {
|
||||||
const loader = new StsConfigHttpLoader(
|
const loader = new StsConfigHttpLoader(
|
||||||
of({ configId: 'configId1' } as OpenIdConfiguration)
|
of({ configId: 'configId1' } as OpenIdConfiguration)
|
||||||
);
|
);
|
||||||
|
|
||||||
const result$ = loader.loadConfigs();
|
const result$ = loader.loadConfigs();
|
||||||
|
|
||||||
const result = await firstValueFrom(result$);
|
result$.subscribe((result) => {
|
||||||
expect(Array.isArray(result)).toBeTruthy();
|
expect(Array.isArray(result)).toBeTrue();
|
||||||
expect(result[0]!.configId).toBe('configId1');
|
expect(result[0].configId).toBe('configId1');
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { Provider } from 'injection-js';
|
import { Provider } from 'injection-js';
|
||||||
import { type Observable, forkJoin, of } from 'rxjs';
|
import { forkJoin, Observable, of } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
import { OpenIdConfiguration } from '../openid-configuration';
|
||||||
|
|
||||||
export class OpenIdConfigLoader {
|
export class OpenIdConfigLoader {
|
||||||
loader?: Provider;
|
loader?: Provider;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { LogLevel } from '../logging/log-level';
|
import { LogLevel } from '../logging/log-level';
|
||||||
import type { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints';
|
import { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints';
|
||||||
|
|
||||||
export interface OpenIdConfiguration {
|
export interface OpenIdConfiguration {
|
||||||
/**
|
/**
|
||||||
@ -207,5 +207,5 @@ export interface OpenIdConfiguration {
|
|||||||
/**
|
/**
|
||||||
* Disable cleaning up the popup when receiving invalid messages
|
* Disable cleaning up the popup when receiving invalid messages
|
||||||
*/
|
*/
|
||||||
disableCleaningPopupOnInvalidMessage?: boolean;
|
disableCleaningPopupOnInvalidMessage?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { mockImplementationWhenArgs, spyOnWithOrigin } from '@/testing/spy';
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
import { vi } from 'vitest';
|
|
||||||
import { LogLevel } from '../../logging/log-level';
|
import { LogLevel } from '../../logging/log-level';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { mockProvider } from '../../testing/mock';
|
import { OpenIdConfiguration } from '../openid-configuration';
|
||||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
|
||||||
import { ConfigValidationService } from './config-validation.service';
|
import { ConfigValidationService } from './config-validation.service';
|
||||||
import { allMultipleConfigRules } from './rules';
|
import { allMultipleConfigRules } from './rules';
|
||||||
|
|
||||||
@ -16,8 +14,6 @@ describe('Config Validation Service', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [ConfigValidationService, mockProvider(LoggerService)],
|
providers: [ConfigValidationService, mockProvider(LoggerService)],
|
||||||
});
|
});
|
||||||
configValidationService = TestBed.inject(ConfigValidationService);
|
|
||||||
loggerService = TestBed.inject(LoggerService);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const VALID_CONFIG = {
|
const VALID_CONFIG = {
|
||||||
@ -33,6 +29,11 @@ describe('Config Validation Service', () => {
|
|||||||
logLevel: LogLevel.Debug,
|
logLevel: LogLevel.Debug,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
configValidationService = TestBed.inject(ConfigValidationService);
|
||||||
|
loggerService = TestBed.inject(LoggerService);
|
||||||
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(configValidationService).toBeTruthy();
|
expect(configValidationService).toBeTruthy();
|
||||||
});
|
});
|
||||||
@ -41,27 +42,26 @@ describe('Config Validation Service', () => {
|
|||||||
const config = {};
|
const config = {};
|
||||||
const result = configValidationService.validateConfig(config);
|
const result = configValidationService.validateConfig(config);
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true for valid config', () => {
|
it('should return true for valid config', () => {
|
||||||
const result = configValidationService.validateConfig(VALID_CONFIG);
|
const result = configValidationService.validateConfig(VALID_CONFIG);
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls `logWarning` if one rule has warning level', () => {
|
it('calls `logWarning` if one rule has warning level', () => {
|
||||||
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
|
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
|
||||||
const messageTypeSpy = spyOnWithOrigin(
|
const messageTypeSpy = spyOn(
|
||||||
configValidationService,
|
configValidationService as any,
|
||||||
'getAllMessagesOfType' as any
|
'getAllMessagesOfType'
|
||||||
);
|
);
|
||||||
|
|
||||||
mockImplementationWhenArgs(
|
messageTypeSpy
|
||||||
messageTypeSpy,
|
.withArgs('warning', jasmine.any(Array))
|
||||||
(arg1: any, arg2: any) => arg1 === 'warning' && Array.isArray(arg2),
|
.and.returnValue(['A warning message']);
|
||||||
() => ['A warning message']
|
messageTypeSpy.withArgs('error', jasmine.any(Array)).and.callThrough();
|
||||||
);
|
|
||||||
|
|
||||||
configValidationService.validateConfig(VALID_CONFIG);
|
configValidationService.validateConfig(VALID_CONFIG);
|
||||||
expect(loggerWarningSpy).toHaveBeenCalled();
|
expect(loggerWarningSpy).toHaveBeenCalled();
|
||||||
@ -72,7 +72,7 @@ describe('Config Validation Service', () => {
|
|||||||
const config = { ...VALID_CONFIG, clientId: '' } as OpenIdConfiguration;
|
const config = { ...VALID_CONFIG, clientId: '' } as OpenIdConfiguration;
|
||||||
const result = configValidationService.validateConfig(config);
|
const result = configValidationService.validateConfig(config);
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ describe('Config Validation Service', () => {
|
|||||||
} as OpenIdConfiguration;
|
} as OpenIdConfiguration;
|
||||||
const result = configValidationService.validateConfig(config);
|
const result = configValidationService.validateConfig(config);
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ describe('Config Validation Service', () => {
|
|||||||
const config = { ...VALID_CONFIG, redirectUrl: '' };
|
const config = { ...VALID_CONFIG, redirectUrl: '' };
|
||||||
const result = configValidationService.validateConfig(config);
|
const result = configValidationService.validateConfig(config);
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ describe('Config Validation Service', () => {
|
|||||||
} as OpenIdConfiguration;
|
} as OpenIdConfiguration;
|
||||||
const result = configValidationService.validateConfig(config);
|
const result = configValidationService.validateConfig(config);
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -120,12 +120,12 @@ describe('Config Validation Service', () => {
|
|||||||
scopes: 'scope1 scope2 but_no_offline_access',
|
scopes: 'scope1 scope2 but_no_offline_access',
|
||||||
};
|
};
|
||||||
|
|
||||||
const loggerSpy = vi.spyOn(loggerService, 'logError');
|
const loggerSpy = spyOn(loggerService, 'logError');
|
||||||
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
|
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
|
||||||
|
|
||||||
const result = configValidationService.validateConfig(config);
|
const result = configValidationService.validateConfig(config);
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTrue();
|
||||||
expect(loggerSpy).not.toHaveBeenCalled();
|
expect(loggerSpy).not.toHaveBeenCalled();
|
||||||
expect(loggerWarningSpy).toHaveBeenCalled();
|
expect(loggerWarningSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -146,47 +146,47 @@ describe('Config Validation Service', () => {
|
|||||||
scopes: 'scope1 scope2 but_no_offline_access',
|
scopes: 'scope1 scope2 but_no_offline_access',
|
||||||
};
|
};
|
||||||
|
|
||||||
const loggerErrorSpy = vi.spyOn(loggerService, 'logError');
|
const loggerErrorSpy = spyOn(loggerService, 'logError');
|
||||||
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
|
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
|
||||||
|
|
||||||
const result = configValidationService.validateConfigs([
|
const result = configValidationService.validateConfigs([
|
||||||
config1,
|
config1,
|
||||||
config2,
|
config2,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTrue();
|
||||||
expect(loggerErrorSpy).not.toHaveBeenCalled();
|
expect(loggerErrorSpy).not.toHaveBeenCalled();
|
||||||
expect(vi.mocked(loggerWarningSpy).mock.calls[0]).toEqual([
|
expect(loggerWarningSpy.calls.argsFor(0)).toEqual([
|
||||||
config1,
|
config1,
|
||||||
'You added multiple configs with the same authority, clientId and scope',
|
'You added multiple configs with the same authority, clientId and scope',
|
||||||
]);
|
]);
|
||||||
expect(vi.mocked(loggerWarningSpy).mock.calls[1]).toEqual([
|
expect(loggerWarningSpy.calls.argsFor(1)).toEqual([
|
||||||
config2,
|
config2,
|
||||||
'You added multiple configs with the same authority, clientId and scope',
|
'You added multiple configs with the same authority, clientId and scope',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false and a better error message when config is not passed as object with config property', () => {
|
it('should return false and a better error message when config is not passed as object with config property', () => {
|
||||||
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
|
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
|
||||||
|
|
||||||
const result = configValidationService.validateConfigs([]);
|
const result = configValidationService.validateConfigs([]);
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
expect(loggerWarningSpy).not.toHaveBeenCalled();
|
expect(loggerWarningSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validateConfigs', () => {
|
describe('validateConfigs', () => {
|
||||||
it('calls internal method with empty array if something falsy is passed', () => {
|
it('calls internal method with empty array if something falsy is passed', () => {
|
||||||
const spy = vi.spyOn(
|
const spy = spyOn(
|
||||||
configValidationService as any,
|
configValidationService as any,
|
||||||
'validateConfigsInternal'
|
'validateConfigsInternal'
|
||||||
);
|
).and.callThrough();
|
||||||
|
|
||||||
const result = configValidationService.validateConfigs([]);
|
const result = configValidationService.validateConfigs([]);
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalse();
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith([], allMultipleConfigRules);
|
expect(spy).toHaveBeenCalledOnceWith([], allMultipleConfigRules);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
import { OpenIdConfiguration } from '../openid-configuration';
|
||||||
import type { Level, RuleValidationResult } from './rule';
|
import { Level, RuleValidationResult } from './rule';
|
||||||
import { allMultipleConfigRules, allRules } from './rules';
|
import { allMultipleConfigRules, allRules } from './rules';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -35,14 +35,14 @@ export class ConfigValidationService {
|
|||||||
|
|
||||||
let overallErrorCount = 0;
|
let overallErrorCount = 0;
|
||||||
|
|
||||||
for (const passedConfig of passedConfigs) {
|
passedConfigs.forEach((passedConfig) => {
|
||||||
const errorCount = this.processValidationResultsAndGetErrorCount(
|
const errorCount = this.processValidationResultsAndGetErrorCount(
|
||||||
allValidationResults,
|
allValidationResults,
|
||||||
passedConfig
|
passedConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
overallErrorCount += errorCount;
|
overallErrorCount += errorCount;
|
||||||
}
|
});
|
||||||
|
|
||||||
return overallErrorCount === 0;
|
return overallErrorCount === 0;
|
||||||
}
|
}
|
||||||
@ -75,17 +75,17 @@ export class ConfigValidationService {
|
|||||||
const allErrorMessages = this.getAllMessagesOfType('error', allMessages);
|
const allErrorMessages = this.getAllMessagesOfType('error', allMessages);
|
||||||
const allWarnings = this.getAllMessagesOfType('warning', allMessages);
|
const allWarnings = this.getAllMessagesOfType('warning', allMessages);
|
||||||
|
|
||||||
for (const message of allErrorMessages) {
|
allErrorMessages.forEach((message) =>
|
||||||
this.loggerService.logError(config, message);
|
this.loggerService.logError(config, message)
|
||||||
}
|
);
|
||||||
for (const message of allWarnings) {
|
allWarnings.forEach((message) =>
|
||||||
this.loggerService.logWarning(config, message);
|
this.loggerService.logWarning(config, message)
|
||||||
}
|
);
|
||||||
|
|
||||||
return allErrorMessages.length;
|
return allErrorMessages.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getAllMessagesOfType(
|
private getAllMessagesOfType(
|
||||||
type: Level,
|
type: Level,
|
||||||
results: RuleValidationResult[]
|
results: RuleValidationResult[]
|
||||||
): string[] {
|
): string[] {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
import { OpenIdConfiguration } from '../openid-configuration';
|
||||||
|
|
||||||
export interface Rule {
|
export interface Rule {
|
||||||
validate(passedConfig: OpenIdConfiguration): RuleValidationResult;
|
validate(passedConfig: OpenIdConfiguration): RuleValidationResult;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||||
|
|
||||||
export const ensureAuthority = (
|
export const ensureAuthority = (
|
||||||
passedConfig: OpenIdConfiguration
|
passedConfig: OpenIdConfiguration
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||||
|
|
||||||
export const ensureClientId = (
|
export const ensureClientId = (
|
||||||
passedConfig: OpenIdConfiguration
|
passedConfig: OpenIdConfiguration
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||||
|
|
||||||
const createIdentifierToCheck = (passedConfig: OpenIdConfiguration): string => {
|
const createIdentifierToCheck = (passedConfig: OpenIdConfiguration): string => {
|
||||||
if (!passedConfig) {
|
if (!passedConfig) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||||
|
|
||||||
export const ensureRedirectRule = (
|
export const ensureRedirectRule = (
|
||||||
passedConfig: OpenIdConfiguration
|
passedConfig: OpenIdConfiguration
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||||
|
|
||||||
export const ensureSilentRenewUrlWhenNoRefreshTokenUsed = (
|
export const ensureSilentRenewUrlWhenNoRefreshTokenUsed = (
|
||||||
passedConfig: OpenIdConfiguration
|
passedConfig: OpenIdConfiguration
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||||
|
|
||||||
export const useOfflineScopeWithSilentRenew = (
|
export const useOfflineScopeWithSilentRenew = (
|
||||||
passedConfig: OpenIdConfiguration
|
passedConfig: OpenIdConfiguration
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { CryptoService } from '../utils/crypto/crypto.service';
|
import { CryptoService } from '../utils/crypto/crypto.service';
|
||||||
import {
|
import {
|
||||||
JwkExtractor,
|
JwkExtractor,
|
||||||
@ -93,6 +93,9 @@ describe('JwkExtractor', () => {
|
|||||||
imports: [],
|
imports: [],
|
||||||
providers: [JwkExtractor, CryptoService],
|
providers: [JwkExtractor, CryptoService],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(JwkExtractor);
|
service = TestBed.inject(JwkExtractor);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -102,30 +105,21 @@ describe('JwkExtractor', () => {
|
|||||||
|
|
||||||
describe('extractJwk', () => {
|
describe('extractJwk', () => {
|
||||||
it('throws error if no keys are present in array', () => {
|
it('throws error if no keys are present in array', () => {
|
||||||
try {
|
expect(() => {
|
||||||
service.extractJwk([]);
|
service.extractJwk([]);
|
||||||
expect.fail('should error');
|
}).toThrow(JwkExtractorInvalidArgumentError);
|
||||||
} catch (error: any) {
|
|
||||||
expect(error).toBe(JwkExtractorInvalidArgumentError);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws error if spec.kid is present, but no key was matching', () => {
|
it('throws error if spec.kid is present, but no key was matching', () => {
|
||||||
try {
|
expect(() => {
|
||||||
service.extractJwk(keys, { kid: 'doot' });
|
service.extractJwk(keys, { kid: 'doot' });
|
||||||
expect.fail('should error');
|
}).toThrow(JwkExtractorNoMatchingKeysError);
|
||||||
} catch (error: any) {
|
|
||||||
expect(error).toBe(JwkExtractorNoMatchingKeysError);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws error if spec.use is present, but no key was matching', () => {
|
it('throws error if spec.use is present, but no key was matching', () => {
|
||||||
try {
|
expect(() => {
|
||||||
service.extractJwk(keys, { use: 'blorp' });
|
service.extractJwk(keys, { use: 'blorp' });
|
||||||
expect.fail('should error');
|
}).toThrow(JwkExtractorNoMatchingKeysError);
|
||||||
} catch (error: any) {
|
|
||||||
expect(error).toBe(JwkExtractorNoMatchingKeysError);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not throw error if no key is matching when throwOnEmpty is false', () => {
|
it('does not throw error if no key is matching when throwOnEmpty is false', () => {
|
||||||
@ -135,12 +129,9 @@ describe('JwkExtractor', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws error if multiple keys are present, and spec is not present', () => {
|
it('throws error if multiple keys are present, and spec is not present', () => {
|
||||||
try {
|
expect(() => {
|
||||||
service.extractJwk(keys);
|
service.extractJwk(keys);
|
||||||
expect.fail('should error');
|
}).toThrow(JwkExtractorSeveralMatchingKeysError);
|
||||||
} catch (error: any) {
|
|
||||||
expect(error).toBe(JwkExtractorSeveralMatchingKeysError);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns array of keys matching spec.kid', () => {
|
it('returns array of keys matching spec.kid', () => {
|
||||||
|
|||||||
@ -7,20 +7,20 @@ export class JwkExtractor {
|
|||||||
spec?: { kid?: string; use?: string; kty?: string },
|
spec?: { kid?: string; use?: string; kty?: string },
|
||||||
throwOnEmpty = true
|
throwOnEmpty = true
|
||||||
): JsonWebKey[] {
|
): JsonWebKey[] {
|
||||||
if (keys.length === 0) {
|
if (0 === keys.length) {
|
||||||
throw JwkExtractorInvalidArgumentError;
|
throw JwkExtractorInvalidArgumentError;
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundKeys = keys
|
const foundKeys = keys
|
||||||
.filter((k) => (spec?.kid ? (k as any).kid === spec.kid : true))
|
.filter((k) => (spec?.kid ? (k as any)['kid'] === spec.kid : true))
|
||||||
.filter((k) => (spec?.use ? k.use === spec.use : true))
|
.filter((k) => (spec?.use ? k['use'] === spec.use : true))
|
||||||
.filter((k) => (spec?.kty ? k.kty === spec.kty : true));
|
.filter((k) => (spec?.kty ? k['kty'] === spec.kty : true));
|
||||||
|
|
||||||
if (foundKeys.length === 0 && throwOnEmpty) {
|
if (foundKeys.length === 0 && throwOnEmpty) {
|
||||||
throw JwkExtractorNoMatchingKeysError;
|
throw JwkExtractorNoMatchingKeysError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundKeys.length > 1 && (spec === null || undefined === spec)) {
|
if (foundKeys.length > 1 && (null === spec || undefined === spec)) {
|
||||||
throw JwkExtractorSeveralMatchingKeysError;
|
throw JwkExtractorSeveralMatchingKeysError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ export class JwkExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildErrorName(name: string): string {
|
function buildErrorName(name: string): string {
|
||||||
return `${JwkExtractor.name}: ${name}`;
|
return JwkExtractor.name + ': ' + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const JwkExtractorInvalidArgumentError = {
|
export const JwkExtractorInvalidArgumentError = {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { JwtKeys } from '../validation/jwtkeys';
|
import { JwtKeys } from '../validation/jwtkeys';
|
||||||
import type { StateValidationResult } from '../validation/state-validation-result';
|
import { StateValidationResult } from '../validation/state-validation-result';
|
||||||
|
|
||||||
export interface CallbackContext {
|
export interface CallbackContext {
|
||||||
code: string;
|
code: string;
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
import { HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
import { HttpErrorResponse, HttpHeaders } from '@ngify/http';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
|
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
|
||||||
import { DataService } from '../../api/data.service';
|
import { DataService } from '../../api/data.service';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||||
import { createRetriableStream } from '../../testing/create-retriable-stream.helper';
|
|
||||||
import { mockProvider } from '../../testing/mock';
|
|
||||||
import { UrlService } from '../../utils/url/url.service';
|
import { UrlService } from '../../utils/url/url.service';
|
||||||
import { TokenValidationService } from '../../validation/token-validation.service';
|
import { TokenValidationService } from '../../validation/token-validation.service';
|
||||||
import type { CallbackContext } from '../callback-context';
|
import { CallbackContext } from '../callback-context';
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
import { CodeFlowCallbackHandlerService } from './code-flow-callback-handler.service';
|
import { CodeFlowCallbackHandlerService } from './code-flow-callback-handler.service';
|
||||||
|
|
||||||
@ -32,6 +31,9 @@ describe('CodeFlowCallbackHandlerService', () => {
|
|||||||
mockProvider(DataService),
|
mockProvider(DataService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(CodeFlowCallbackHandlerService);
|
service = TestBed.inject(CodeFlowCallbackHandlerService);
|
||||||
dataService = TestBed.inject(DataService);
|
dataService = TestBed.inject(DataService);
|
||||||
urlService = TestBed.inject(UrlService);
|
urlService = TestBed.inject(UrlService);
|
||||||
@ -44,48 +46,42 @@ describe('CodeFlowCallbackHandlerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('codeFlowCallback', () => {
|
describe('codeFlowCallback', () => {
|
||||||
it('throws error if no state is given', async () => {
|
it('throws error if no state is given', waitForAsync(() => {
|
||||||
const getUrlParameterSpy = vi
|
const getUrlParameterSpy = spyOn(
|
||||||
.spyOn(urlService, 'getUrlParameter')
|
urlService,
|
||||||
.mockReturnValue('params');
|
'getUrlParameter'
|
||||||
|
).and.returnValue('params');
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
getUrlParameterSpy.withArgs('test-url', 'state').and.returnValue('');
|
||||||
getUrlParameterSpy,
|
|
||||||
['test-url', 'state'],
|
|
||||||
() => ''
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.codeFlowCallback('test-url', { configId: 'configId1' })
|
||||||
service.codeFlowCallback('test-url', { configId: 'configId1' })
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err).toBeTruthy();
|
||||||
expect(err).toBeTruthy();
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('throws error if no code is given', async () => {
|
it('throws error if no code is given', waitForAsync(() => {
|
||||||
const getUrlParameterSpy = vi
|
const getUrlParameterSpy = spyOn(
|
||||||
.spyOn(urlService, 'getUrlParameter')
|
urlService,
|
||||||
.mockReturnValue('params');
|
'getUrlParameter'
|
||||||
|
).and.returnValue('params');
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
getUrlParameterSpy.withArgs('test-url', 'code').and.returnValue('');
|
||||||
getUrlParameterSpy,
|
|
||||||
['test-url', 'code'],
|
|
||||||
() => ''
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.codeFlowCallback('test-url', { configId: 'configId1' })
|
||||||
service.codeFlowCallback('test-url', { configId: 'configId1' })
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err).toBeTruthy();
|
||||||
expect(err).toBeTruthy();
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('returns callbackContext if all params are good', async () => {
|
it('returns callbackContext if all params are good', waitForAsync(() => {
|
||||||
vi.spyOn(urlService, 'getUrlParameter').mockReturnValue('params');
|
spyOn(urlService, 'getUrlParameter').and.returnValue('params');
|
||||||
|
|
||||||
const expectedCallbackContext = {
|
const expectedCallbackContext = {
|
||||||
code: 'params',
|
code: 'params',
|
||||||
@ -99,11 +95,12 @@ describe('CodeFlowCallbackHandlerService', () => {
|
|||||||
existingIdToken: null,
|
existingIdToken: null,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
|
|
||||||
const callbackContext = await firstValueFrom(
|
service
|
||||||
service.codeFlowCallback('test-url', { configId: 'configId1' })
|
.codeFlowCallback('test-url', { configId: 'configId1' })
|
||||||
);
|
.subscribe((callbackContext) => {
|
||||||
expect(callbackContext).toEqual(expectedCallbackContext);
|
expect(callbackContext).toEqual(expectedCallbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('codeFlowCodeRequest ', () => {
|
describe('codeFlowCodeRequest ', () => {
|
||||||
@ -115,96 +112,83 @@ describe('CodeFlowCallbackHandlerService', () => {
|
|||||||
url: 'https://identity-server.test/openid-connect/token',
|
url: 'https://identity-server.test/openid-connect/token',
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws error if state is not correct', async () => {
|
it('throws error if state is not correct', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
tokenValidationService,
|
tokenValidationService,
|
||||||
'validateStateFromHashCallback'
|
'validateStateFromHashCallback'
|
||||||
).mockReturnValue(false);
|
).and.returnValue(false);
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
|
||||||
service.codeFlowCodeRequest({} as CallbackContext, {
|
.subscribe({
|
||||||
configId: 'configId1',
|
error: (err) => {
|
||||||
})
|
expect(err).toBeTruthy();
|
||||||
);
|
},
|
||||||
} catch (err: any) {
|
});
|
||||||
expect(err).toBeTruthy();
|
}));
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws error if authWellknownEndpoints is null is given', async () => {
|
it('throws error if authWellknownEndpoints is null is given', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
tokenValidationService,
|
tokenValidationService,
|
||||||
'validateStateFromHashCallback'
|
'validateStateFromHashCallback'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue(null);
|
||||||
() => null
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
|
||||||
service.codeFlowCodeRequest({} as CallbackContext, {
|
.subscribe({
|
||||||
configId: 'configId1',
|
error: (err) => {
|
||||||
})
|
expect(err).toBeTruthy();
|
||||||
);
|
},
|
||||||
} catch (err: any) {
|
});
|
||||||
expect(err).toBeTruthy();
|
}));
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws error if tokenendpoint is null is given', async () => {
|
it('throws error if tokenendpoint is null is given', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
tokenValidationService,
|
tokenValidationService,
|
||||||
'validateStateFromHashCallback'
|
'validateStateFromHashCallback'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue({ tokenEndpoint: null });
|
||||||
() => ({ tokenEndpoint: null })
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
|
||||||
service.codeFlowCodeRequest({} as CallbackContext, {
|
.subscribe({
|
||||||
configId: 'configId1',
|
error: (err) => {
|
||||||
})
|
expect(err).toBeTruthy();
|
||||||
);
|
},
|
||||||
} catch (err: any) {
|
});
|
||||||
expect(err).toBeTruthy();
|
}));
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls dataService if all params are good', async () => {
|
it('calls dataService if all params are good', waitForAsync(() => {
|
||||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
|
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
tokenValidationService,
|
tokenValidationService,
|
||||||
'validateStateFromHashCallback'
|
'validateStateFromHashCallback'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
|
|
||||||
await firstValueFrom(
|
service
|
||||||
service.codeFlowCodeRequest({} as CallbackContext, {
|
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
|
||||||
configId: 'configId1',
|
.subscribe(() => {
|
||||||
})
|
expect(postSpy).toHaveBeenCalledOnceWith(
|
||||||
);
|
'tokenEndpoint',
|
||||||
expect(postSpy).toHaveBeenCalledExactlyOnceWith(
|
undefined,
|
||||||
'tokenEndpoint',
|
{ configId: 'configId1' },
|
||||||
undefined,
|
jasmine.any(HttpHeaders)
|
||||||
{ configId: 'configId1' },
|
);
|
||||||
expect.any(HttpHeaders)
|
});
|
||||||
);
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('calls url service with custom token params', async () => {
|
it('calls url service with custom token params', waitForAsync(() => {
|
||||||
const urlServiceSpy = vi.spyOn(
|
const urlServiceSpy = spyOn(
|
||||||
urlService,
|
urlService,
|
||||||
'createBodyForCodeFlowCodeRequest'
|
'createBodyForCodeFlowCodeRequest'
|
||||||
);
|
);
|
||||||
@ -213,83 +197,76 @@ describe('CodeFlowCallbackHandlerService', () => {
|
|||||||
customParamsCodeRequest: { foo: 'bar' },
|
customParamsCodeRequest: { foo: 'bar' },
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', config)
|
||||||
['authWellKnownEndPoints', config],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
tokenValidationService,
|
tokenValidationService,
|
||||||
'validateStateFromHashCallback'
|
'validateStateFromHashCallback'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
|
|
||||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
|
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
|
||||||
|
|
||||||
await firstValueFrom(
|
service
|
||||||
service.codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config)
|
.codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith('foo', config, {
|
expect(urlServiceSpy).toHaveBeenCalledOnceWith('foo', config, {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
});
|
});
|
||||||
expect(postSpy).toHaveBeenCalledTimes(1);
|
expect(postSpy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls dataService with correct headers if all params are good', async () => {
|
it('calls dataService with correct headers if all params are good', waitForAsync(() => {
|
||||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
|
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
customParamsCodeRequest: { foo: 'bar' },
|
customParamsCodeRequest: { foo: 'bar' },
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', config)
|
||||||
['authWellKnownEndPoints', config],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
tokenValidationService,
|
tokenValidationService,
|
||||||
'validateStateFromHashCallback'
|
'validateStateFromHashCallback'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
|
|
||||||
await firstValueFrom(
|
service
|
||||||
service.codeFlowCodeRequest({} as CallbackContext, config)
|
.codeFlowCodeRequest({} as CallbackContext, config)
|
||||||
);
|
.subscribe(() => {
|
||||||
const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
|
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;
|
||||||
expect(httpHeaders.has('Content-Type')).toBeTruthy();
|
|
||||||
expect(httpHeaders.get('Content-Type')).toBe(
|
|
||||||
'application/x-www-form-urlencoded'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns error in case of http error', async () => {
|
expect(httpHeaders.has('Content-Type')).toBeTrue();
|
||||||
vi.spyOn(dataService, 'post').mockReturnValue(
|
expect(httpHeaders.get('Content-Type')).toBe(
|
||||||
throwError(() => HTTP_ERROR)
|
'application/x-www-form-urlencoded'
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('returns error in case of http error', waitForAsync(() => {
|
||||||
|
spyOn(dataService, 'post').and.returnValue(throwError(() => HTTP_ERROR));
|
||||||
const config = {
|
const config = {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
customParamsCodeRequest: { foo: 'bar' },
|
customParamsCodeRequest: { foo: 'bar' },
|
||||||
authority: 'authority',
|
authority: 'authority',
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', config)
|
||||||
['authWellKnownEndPoints', config],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
|
||||||
await firstValueFrom(
|
error: (err) => {
|
||||||
service.codeFlowCodeRequest({} as CallbackContext, config)
|
expect(err).toBeTruthy();
|
||||||
);
|
},
|
||||||
} catch (err: any) {
|
});
|
||||||
expect(err).toBeTruthy();
|
}));
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('retries request in case of no connection http error and succeeds', async () => {
|
it('retries request in case of no connection http error and succeeds', waitForAsync(() => {
|
||||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
|
const postSpy = spyOn(dataService, 'post').and.returnValue(
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => CONNECTION_ERROR),
|
throwError(() => CONNECTION_ERROR),
|
||||||
of({})
|
of({})
|
||||||
@ -301,30 +278,29 @@ describe('CodeFlowCallbackHandlerService', () => {
|
|||||||
authority: 'authority',
|
authority: 'authority',
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', config)
|
||||||
['authWellKnownEndPoints', config],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
tokenValidationService,
|
tokenValidationService,
|
||||||
'validateStateFromHashCallback'
|
'validateStateFromHashCallback'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
|
|
||||||
try {
|
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
|
||||||
const res = await firstValueFrom(
|
next: (res) => {
|
||||||
service.codeFlowCodeRequest({} as CallbackContext, config)
|
expect(res).toBeTruthy();
|
||||||
);
|
expect(postSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(res).toBeTruthy();
|
},
|
||||||
expect(postSpy).toHaveBeenCalledTimes(1);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
// fails if there should be a result
|
||||||
expect(err).toBeFalsy();
|
expect(err).toBeFalsy();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('retries request in case of no connection http error and fails because of http error afterwards', async () => {
|
it('retries request in case of no connection http error and fails because of http error afterwards', waitForAsync(() => {
|
||||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
|
const postSpy = spyOn(dataService, 'post').and.returnValue(
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => CONNECTION_ERROR),
|
throwError(() => CONNECTION_ERROR),
|
||||||
throwError(() => HTTP_ERROR)
|
throwError(() => HTTP_ERROR)
|
||||||
@ -336,26 +312,25 @@ describe('CodeFlowCallbackHandlerService', () => {
|
|||||||
authority: 'authority',
|
authority: 'authority',
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', config)
|
||||||
['authWellKnownEndPoints', config],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
tokenValidationService,
|
tokenValidationService,
|
||||||
'validateStateFromHashCallback'
|
'validateStateFromHashCallback'
|
||||||
).mockReturnValue(true);
|
).and.returnValue(true);
|
||||||
|
|
||||||
try {
|
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
|
||||||
const res = await firstValueFrom(
|
next: (res) => {
|
||||||
service.codeFlowCodeRequest({} as CallbackContext, config)
|
// fails if there should be a result
|
||||||
);
|
expect(res).toBeFalsy();
|
||||||
expect(res).toBeFalsy();
|
},
|
||||||
} catch (err: any) {
|
error: (err) => {
|
||||||
expect(err).toBeTruthy();
|
expect(err).toBeTruthy();
|
||||||
expect(postSpy).toHaveBeenCalledTimes(1);
|
expect(postSpy).toHaveBeenCalledTimes(1);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { HttpHeaders } from '@ngify/http';
|
import { HttpHeaders } from '@ngify/http';
|
||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, of, throwError, timer } from 'rxjs';
|
import { Observable, of, throwError, timer } from 'rxjs';
|
||||||
import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators';
|
import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators';
|
||||||
import { DataService } from '../../api/data.service';
|
import { DataService } from '../../api/data.service';
|
||||||
import type { OpenIdConfiguration } from '../../config/openid-configuration';
|
import { OpenIdConfiguration } from '../../config/openid-configuration';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||||
import { UrlService } from '../../utils/url/url.service';
|
import { UrlService } from '../../utils/url/url.service';
|
||||||
import { TokenValidationService } from '../../validation/token-validation.service';
|
import { TokenValidationService } from '../../validation/token-validation.service';
|
||||||
import type { AuthResult, CallbackContext } from '../callback-context';
|
import { AuthResult, CallbackContext } from '../callback-context';
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
import { isNetworkError } from './error-helper';
|
import { isNetworkError } from './error-helper';
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ export class CodeFlowCallbackHandlerService {
|
|||||||
switchMap((response) => {
|
switchMap((response) => {
|
||||||
if (response) {
|
if (response) {
|
||||||
const authResult: AuthResult = {
|
const authResult: AuthResult = {
|
||||||
...(response as any),
|
...response,
|
||||||
state: callbackContext.state,
|
state: callbackContext.state,
|
||||||
session_state: callbackContext.sessionState,
|
session_state: callbackContext.sessionState,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { HttpErrorResponse } from '@ngify/http';
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
import { isNetworkError } from './error-helper';
|
import { isNetworkError } from './error-helper';
|
||||||
|
|
||||||
describe('error helper', () => {
|
describe('error helper', () => {
|
||||||
@ -27,31 +27,31 @@ describe('error helper', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns true on http error with status = 0', () => {
|
it('returns true on http error with status = 0', () => {
|
||||||
expect(isNetworkError(CONNECTION_ERROR)).toBeTruthy();
|
expect(isNetworkError(CONNECTION_ERROR)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true on http error with status = 0 and unknown error', () => {
|
it('returns true on http error with status = 0 and unknown error', () => {
|
||||||
expect(isNetworkError(UNKNOWN_CONNECTION_ERROR)).toBeTruthy();
|
expect(isNetworkError(UNKNOWN_CONNECTION_ERROR)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true on http error with status <> 0 and error ProgressEvent', () => {
|
it('returns true on http error with status <> 0 and error ProgressEvent', () => {
|
||||||
expect(isNetworkError(PARTIAL_CONNECTION_ERROR)).toBeTruthy();
|
expect(isNetworkError(PARTIAL_CONNECTION_ERROR)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false on non http error', () => {
|
it('returns false on non http error', () => {
|
||||||
expect(isNetworkError(new Error('not a HttpErrorResponse'))).toBeFalsy();
|
expect(isNetworkError(new Error('not a HttpErrorResponse'))).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false on string error', () => {
|
it('returns false on string error', () => {
|
||||||
expect(isNetworkError('not a HttpErrorResponse')).toBeFalsy();
|
expect(isNetworkError('not a HttpErrorResponse')).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false on undefined', () => {
|
it('returns false on undefined', () => {
|
||||||
expect(isNetworkError(undefined)).toBeFalsy();
|
expect(isNetworkError(undefined)).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false on empty http error', () => {
|
it('returns false on empty http error', () => {
|
||||||
expect(isNetworkError(HTTP_ERROR)).toBeFalsy();
|
expect(isNetworkError(HTTP_ERROR)).toBeFalse();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
import { AuthStateService } from '../../auth-state/auth-state.service';
|
import { AuthStateService } from '../../auth-state/auth-state.service';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||||
import { mockProvider } from '../../testing/mock';
|
import { JwtKey, JwtKeys } from '../../validation/jwtkeys';
|
||||||
import type { JwtKey, JwtKeys } from '../../validation/jwtkeys';
|
|
||||||
import { ValidationResult } from '../../validation/validation-result';
|
import { ValidationResult } from '../../validation/validation-result';
|
||||||
import type { AuthResult, CallbackContext } from '../callback-context';
|
import { AuthResult, CallbackContext } from '../callback-context';
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
import { ResetAuthDataService } from '../reset-auth-data.service';
|
import { ResetAuthDataService } from '../reset-auth-data.service';
|
||||||
import { SigninKeyDataService } from '../signin-key-data.service';
|
import { SigninKeyDataService } from '../signin-key-data.service';
|
||||||
@ -47,6 +46,9 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
mockProvider(ResetAuthDataService),
|
mockProvider(ResetAuthDataService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(HistoryJwtKeysCallbackHandlerService);
|
service = TestBed.inject(HistoryJwtKeysCallbackHandlerService);
|
||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
resetAuthDataService = TestBed.inject(ResetAuthDataService);
|
resetAuthDataService = TestBed.inject(ResetAuthDataService);
|
||||||
@ -60,8 +62,8 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('callbackHistoryAndResetJwtKeys', () => {
|
describe('callbackHistoryAndResetJwtKeys', () => {
|
||||||
it('writes authResult into the storage', async () => {
|
it('writes authResult into the storage', waitForAsync(() => {
|
||||||
const storagePersistenceServiceSpy = vi.spyOn(
|
const storagePersistenceServiceSpy = spyOn(
|
||||||
storagePersistenceService,
|
storagePersistenceService,
|
||||||
'write'
|
'write'
|
||||||
);
|
);
|
||||||
@ -73,82 +75,86 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
authResult: DUMMY_AUTH_RESULT,
|
authResult: DUMMY_AUTH_RESULT,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
of({ keys: [] } as JwtKeys)
|
of({ keys: [] } as JwtKeys)
|
||||||
);
|
);
|
||||||
await firstValueFrom(
|
service
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
callbackContext,
|
callbackContext,
|
||||||
allConfigs[0]!,
|
allconfigs[0],
|
||||||
allConfigs
|
allconfigs
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(storagePersistenceServiceSpy.mock.calls).toEqual([
|
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([
|
||||||
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
|
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]],
|
||||||
['jwtKeys', { keys: [] }, allConfigs[0]],
|
['jwtKeys', { keys: [] }, allconfigs[0]],
|
||||||
]);
|
]);
|
||||||
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
|
// write authnResult & jwtKeys
|
||||||
});
|
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('writes refresh_token into the storage without reuse (refresh token rotation)', async () => {
|
it('writes refresh_token into the storage without reuse (refresh token rotation)', waitForAsync(() => {
|
||||||
const DUMMY_AUTH_RESULT = {
|
const DUMMY_AUTH_RESULT = {
|
||||||
refresh_token: 'dummy_refresh_token',
|
refresh_token: 'dummy_refresh_token',
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
};
|
};
|
||||||
|
|
||||||
const storagePersistenceServiceSpy = vi.spyOn(
|
const storagePersistenceServiceSpy = spyOn(
|
||||||
storagePersistenceService,
|
storagePersistenceService,
|
||||||
'write'
|
'write'
|
||||||
);
|
);
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
authResult: DUMMY_AUTH_RESULT,
|
authResult: DUMMY_AUTH_RESULT,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
of({ keys: [] } as JwtKeys)
|
of({ keys: [] } as JwtKeys)
|
||||||
);
|
);
|
||||||
|
|
||||||
await firstValueFrom(
|
service
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
callbackContext,
|
callbackContext,
|
||||||
allConfigs[0]!,
|
allconfigs[0],
|
||||||
allConfigs
|
allconfigs
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(storagePersistenceServiceSpy.mock.calls).toEqual([
|
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([
|
||||||
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
|
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]],
|
||||||
['jwtKeys', { keys: [] }, allConfigs[0]],
|
['jwtKeys', { keys: [] }, allconfigs[0]],
|
||||||
]);
|
]);
|
||||||
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
|
// write authnResult & refresh_token & jwtKeys
|
||||||
});
|
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('writes refresh_token into the storage with reuse (without refresh token rotation)', async () => {
|
it('writes refresh_token into the storage with reuse (without refresh token rotation)', waitForAsync(() => {
|
||||||
const DUMMY_AUTH_RESULT = {
|
const DUMMY_AUTH_RESULT = {
|
||||||
refresh_token: 'dummy_refresh_token',
|
refresh_token: 'dummy_refresh_token',
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
};
|
};
|
||||||
|
|
||||||
const storagePersistenceServiceSpy = vi.spyOn(
|
const storagePersistenceServiceSpy = spyOn(
|
||||||
storagePersistenceService,
|
storagePersistenceService,
|
||||||
'write'
|
'write'
|
||||||
);
|
);
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
authResult: DUMMY_AUTH_RESULT,
|
authResult: DUMMY_AUTH_RESULT,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
@ -156,25 +162,27 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
of({ keys: [] } as JwtKeys)
|
of({ keys: [] } as JwtKeys)
|
||||||
);
|
);
|
||||||
await firstValueFrom(
|
service
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
callbackContext,
|
callbackContext,
|
||||||
allConfigs[0]!,
|
allconfigs[0],
|
||||||
allConfigs
|
allconfigs
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(storagePersistenceServiceSpy.mock.calls).toEqual([
|
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([
|
||||||
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
|
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]],
|
||||||
['reusable_refresh_token', 'dummy_refresh_token', allConfigs[0]],
|
['reusable_refresh_token', 'dummy_refresh_token', allconfigs[0]],
|
||||||
['jwtKeys', { keys: [] }, allConfigs[0]],
|
['jwtKeys', { keys: [] }, allconfigs[0]],
|
||||||
]);
|
]);
|
||||||
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3);
|
// write authnResult & refresh_token & jwtKeys
|
||||||
});
|
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('resetBrowserHistory if historyCleanup is turned on and is not in a renewProcess', async () => {
|
it('resetBrowserHistory if historyCleanup is turned on and is not in a renewProcess', waitForAsync(() => {
|
||||||
const DUMMY_AUTH_RESULT = {
|
const DUMMY_AUTH_RESULT = {
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
};
|
};
|
||||||
@ -182,29 +190,30 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
authResult: DUMMY_AUTH_RESULT,
|
authResult: DUMMY_AUTH_RESULT,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: false,
|
historyCleanupOff: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const windowSpy = vi.spyOn(window.history, 'replaceState');
|
const windowSpy = spyOn(window.history, 'replaceState');
|
||||||
|
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
of({ keys: [] } as JwtKeys)
|
of({ keys: [] } as JwtKeys)
|
||||||
);
|
);
|
||||||
await firstValueFrom(
|
service
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
callbackContext,
|
callbackContext,
|
||||||
allConfigs[0]!,
|
allconfigs[0],
|
||||||
allConfigs
|
allconfigs
|
||||||
)
|
)
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(windowSpy).toHaveBeenCalledTimes(1);
|
expect(windowSpy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('returns callbackContext with jwtkeys filled if everything works fine', async () => {
|
it('returns callbackContext with jwtkeys filled if everything works fine', waitForAsync(() => {
|
||||||
const DUMMY_AUTH_RESULT = {
|
const DUMMY_AUTH_RESULT = {
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
};
|
};
|
||||||
@ -213,31 +222,32 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
authResult: DUMMY_AUTH_RESULT,
|
authResult: DUMMY_AUTH_RESULT,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: false,
|
historyCleanupOff: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
of({ keys: [{ kty: 'henlo' } as JwtKey] } as JwtKeys)
|
of({ keys: [{ kty: 'henlo' } as JwtKey] } as JwtKeys)
|
||||||
);
|
);
|
||||||
const result = await firstValueFrom(
|
service
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
callbackContext,
|
callbackContext,
|
||||||
allConfigs[0]!,
|
allconfigs[0],
|
||||||
allConfigs
|
allconfigs
|
||||||
)
|
)
|
||||||
);
|
.subscribe((result) => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
authResult: DUMMY_AUTH_RESULT,
|
authResult: DUMMY_AUTH_RESULT,
|
||||||
jwtKeys: { keys: [{ kty: 'henlo' }] },
|
jwtKeys: { keys: [{ kty: 'henlo' }] },
|
||||||
} as CallbackContext);
|
} as CallbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('returns error if no jwtKeys have been in the call --> keys are null', async () => {
|
it('returns error if no jwtKeys have been in the call --> keys are null', waitForAsync(() => {
|
||||||
const DUMMY_AUTH_RESULT = {
|
const DUMMY_AUTH_RESULT = {
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
};
|
};
|
||||||
@ -246,32 +256,32 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
authResult: DUMMY_AUTH_RESULT,
|
authResult: DUMMY_AUTH_RESULT,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: false,
|
historyCleanupOff: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
of({} as JwtKeys)
|
of({} as JwtKeys)
|
||||||
);
|
);
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
callbackContext,
|
||||||
callbackContext,
|
allconfigs[0],
|
||||||
allConfigs[0]!,
|
allconfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err.message).toEqual(
|
||||||
expect(err.message).toEqual(
|
`Failed to retrieve signing key with error: Error: Failed to retrieve signing key`
|
||||||
'Failed to retrieve signing key with error: Error: Failed to retrieve signing key'
|
);
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('returns error if no jwtKeys have been in the call --> keys throw an error', async () => {
|
it('returns error if no jwtKeys have been in the call --> keys throw an error', waitForAsync(() => {
|
||||||
const DUMMY_AUTH_RESULT = {
|
const DUMMY_AUTH_RESULT = {
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
};
|
};
|
||||||
@ -279,140 +289,140 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
authResult: DUMMY_AUTH_RESULT,
|
authResult: DUMMY_AUTH_RESULT,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: false,
|
historyCleanupOff: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
throwError(() => new Error('error'))
|
throwError(() => new Error('error'))
|
||||||
);
|
);
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
callbackContext,
|
||||||
callbackContext,
|
allconfigs[0],
|
||||||
allConfigs[0]!,
|
allconfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err.message).toEqual(
|
||||||
expect(err.message).toEqual(
|
`Failed to retrieve signing key with error: Error: Error: error`
|
||||||
'Failed to retrieve signing key with error: Error: Error: error'
|
);
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('returns error if callbackContext.authresult has an error property filled', async () => {
|
it('returns error if callbackContext.authresult has an error property filled', waitForAsync(() => {
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
authResult: { error: 'someError' },
|
authResult: { error: 'someError' },
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
callbackContext,
|
||||||
callbackContext,
|
allconfigs[0],
|
||||||
allConfigs[0]!,
|
allconfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err.message).toEqual(
|
||||||
expect(err.message).toEqual(
|
`AuthCallback AuthResult came with error: someError`
|
||||||
'AuthCallback AuthResult came with error: someError'
|
);
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('calls resetAuthorizationData, resets nonce and authStateService in case of an error', async () => {
|
it('calls resetAuthorizationData, resets nonce and authStateService in case of an error', waitForAsync(() => {
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
authResult: { error: 'someError' },
|
authResult: { error: 'someError' },
|
||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const resetAuthorizationDataSpy = vi.spyOn(
|
const resetAuthorizationDataSpy = spyOn(
|
||||||
resetAuthDataService,
|
resetAuthDataService,
|
||||||
'resetAuthorizationData'
|
'resetAuthorizationData'
|
||||||
);
|
);
|
||||||
const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce');
|
const setNonceSpy = spyOn(flowsDataService, 'setNonce');
|
||||||
const updateAndPublishAuthStateSpy = vi.spyOn(
|
const updateAndPublishAuthStateSpy = spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'updateAndPublishAuthState'
|
'updateAndPublishAuthState'
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
callbackContext,
|
||||||
callbackContext,
|
allconfigs[0],
|
||||||
allConfigs[0]!,
|
allconfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
error: () => {
|
||||||
} catch {
|
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
expect(setNonceSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(setNonceSpy).toHaveBeenCalledTimes(1);
|
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
|
||||||
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
|
isAuthenticated: false,
|
||||||
isAuthenticated: false,
|
validationResult: ValidationResult.SecureTokenServerError,
|
||||||
validationResult: ValidationResult.SecureTokenServerError,
|
isRenewProcess: false,
|
||||||
isRenewProcess: false,
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('calls authStateService.updateAndPublishAuthState with login required if the error is `login_required`', async () => {
|
it('calls authStateService.updateAndPublishAuthState with login required if the error is `login_required`', waitForAsync(() => {
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
authResult: { error: 'login_required' },
|
authResult: { error: 'login_required' },
|
||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const resetAuthorizationDataSpy = vi.spyOn(
|
const resetAuthorizationDataSpy = spyOn(
|
||||||
resetAuthDataService,
|
resetAuthDataService,
|
||||||
'resetAuthorizationData'
|
'resetAuthorizationData'
|
||||||
);
|
);
|
||||||
const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce');
|
const setNonceSpy = spyOn(flowsDataService, 'setNonce');
|
||||||
const updateAndPublishAuthStateSpy = vi.spyOn(
|
const updateAndPublishAuthStateSpy = spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'updateAndPublishAuthState'
|
'updateAndPublishAuthState'
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
callbackContext,
|
||||||
callbackContext,
|
allconfigs[0],
|
||||||
allConfigs[0]!,
|
allconfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
error: () => {
|
||||||
} catch {
|
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
expect(setNonceSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(setNonceSpy).toHaveBeenCalledTimes(1);
|
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
|
||||||
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
|
isAuthenticated: false,
|
||||||
isAuthenticated: false,
|
validationResult: ValidationResult.LoginRequired,
|
||||||
validationResult: ValidationResult.LoginRequired,
|
isRenewProcess: false,
|
||||||
isRenewProcess: false,
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
it('should store jwtKeys', async () => {
|
it('should store jwtKeys', waitForAsync(() => {
|
||||||
const DUMMY_AUTH_RESULT = {
|
const DUMMY_AUTH_RESULT = {
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
};
|
};
|
||||||
@ -420,41 +430,44 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
const initialCallbackContext = {
|
const initialCallbackContext = {
|
||||||
authResult: DUMMY_AUTH_RESULT,
|
authResult: DUMMY_AUTH_RESULT,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const storagePersistenceServiceSpy = vi.spyOn(
|
const storagePersistenceServiceSpy = spyOn(
|
||||||
storagePersistenceService,
|
storagePersistenceService,
|
||||||
'write'
|
'write'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
of(DUMMY_JWT_KEYS)
|
of(DUMMY_JWT_KEYS)
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
service
|
||||||
const callbackContext: CallbackContext = await firstValueFrom(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
initialCallbackContext,
|
||||||
initialCallbackContext,
|
allconfigs[0],
|
||||||
allConfigs[0]!,
|
allconfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
next: (callbackContext: CallbackContext) => {
|
||||||
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
|
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
|
||||||
expect(storagePersistenceServiceSpy.mock.calls).toEqual([
|
expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([
|
||||||
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
|
['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]],
|
||||||
['jwtKeys', DUMMY_JWT_KEYS, allConfigs[0]],
|
['jwtKeys', DUMMY_JWT_KEYS, allconfigs[0]],
|
||||||
]);
|
]);
|
||||||
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
|
|
||||||
} catch (err: any) {
|
|
||||||
expect(err).toBeFalsy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not store jwtKeys on error', async () => {
|
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
expect(err).toBeFalsy();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not store jwtKeys on error', waitForAsync(() => {
|
||||||
const authResult = {
|
const authResult = {
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
access_token: 'some-access-token',
|
access_token: 'some-access-token',
|
||||||
@ -463,41 +476,45 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
authResult,
|
authResult,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
|
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const storagePersistenceServiceSpy = vi.spyOn(
|
const storagePersistenceServiceSpy = spyOn(
|
||||||
storagePersistenceService,
|
storagePersistenceService,
|
||||||
'write'
|
'write'
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
throwError(() => new Error('Error'))
|
throwError(() => new Error('Error'))
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
service
|
||||||
const callbackContext: CallbackContext = await firstValueFrom(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
initialCallbackContext,
|
||||||
initialCallbackContext,
|
allconfigs[0],
|
||||||
allConfigs[0]!,
|
allconfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
next: (callbackContext: CallbackContext) => {
|
||||||
expect(callbackContext).toBeFalsy();
|
expect(callbackContext).toBeFalsy();
|
||||||
} catch (err: any) {
|
},
|
||||||
expect(err).toBeTruthy();
|
error: (err) => {
|
||||||
expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(err).toBeTruthy();
|
||||||
'authnResult',
|
|
||||||
authResult,
|
|
||||||
allConfigs[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fallback to stored jwtKeys on error', async () => {
|
// storagePersistenceService.write() should not have been called with jwtKeys
|
||||||
|
expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith(
|
||||||
|
'authnResult',
|
||||||
|
authResult,
|
||||||
|
allconfigs[0]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fallback to stored jwtKeys on error', waitForAsync(() => {
|
||||||
const authResult = {
|
const authResult = {
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
access_token: 'some-access-token',
|
access_token: 'some-access-token',
|
||||||
@ -506,72 +523,76 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
|
|||||||
authResult,
|
authResult,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
|
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const storagePersistenceServiceSpy = vi.spyOn(
|
const storagePersistenceServiceSpy = spyOn(
|
||||||
storagePersistenceService,
|
storagePersistenceService,
|
||||||
'read'
|
'read'
|
||||||
);
|
);
|
||||||
|
|
||||||
storagePersistenceServiceSpy.mockReturnValue(DUMMY_JWT_KEYS);
|
storagePersistenceServiceSpy.and.returnValue(DUMMY_JWT_KEYS);
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
throwError(() => new Error('Error'))
|
throwError(() => new Error('Error'))
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
service
|
||||||
const callbackContext: CallbackContext = await firstValueFrom(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
initialCallbackContext,
|
||||||
initialCallbackContext,
|
allconfigs[0],
|
||||||
allConfigs[0]!,
|
allconfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
next: (callbackContext: CallbackContext) => {
|
||||||
expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith(
|
||||||
'jwtKeys',
|
'jwtKeys',
|
||||||
allConfigs[0]
|
allconfigs[0]
|
||||||
);
|
);
|
||||||
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
|
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
|
||||||
} catch (err: any) {
|
},
|
||||||
expect(err).toBeFalsy();
|
error: (err) => {
|
||||||
}
|
expect(err).toBeFalsy();
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should throw error if no jwtKeys are stored', async () => {
|
it('should throw error if no jwtKeys are stored', waitForAsync(() => {
|
||||||
const authResult = {
|
const authResult = {
|
||||||
id_token: 'some-id-token',
|
id_token: 'some-id-token',
|
||||||
access_token: 'some-access-token',
|
access_token: 'some-access-token',
|
||||||
} as AuthResult;
|
} as AuthResult;
|
||||||
|
|
||||||
const initialCallbackContext = { authResult } as CallbackContext;
|
const initialCallbackContext = { authResult } as CallbackContext;
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
historyCleanupOff: true,
|
historyCleanupOff: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null);
|
spyOn(storagePersistenceService, 'read').and.returnValue(null);
|
||||||
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
|
spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue(
|
||||||
throwError(() => new Error('Error'))
|
throwError(() => new Error('Error'))
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
service
|
||||||
const callbackContext: CallbackContext = await firstValueFrom(
|
.callbackHistoryAndResetJwtKeys(
|
||||||
service.callbackHistoryAndResetJwtKeys(
|
initialCallbackContext,
|
||||||
initialCallbackContext,
|
allconfigs[0],
|
||||||
allConfigs[0]!,
|
allconfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
next: (callbackContext: CallbackContext) => {
|
||||||
expect(callbackContext).toBeFalsy();
|
expect(callbackContext).toBeFalsy();
|
||||||
} catch (err: any) {
|
},
|
||||||
expect(err).toBeTruthy();
|
error: (err) => {
|
||||||
}
|
expect(err).toBeTruthy();
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('historyCleanUpTurnedOn ', () => {
|
describe('historyCleanUpTurnedOn ', () => {
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { DOCUMENT } from '../../dom';
|
||||||
import { type Observable, of, throwError } from 'rxjs';
|
import { inject, Injectable } from 'injection-js';
|
||||||
|
import { Observable, of, throwError } from 'rxjs';
|
||||||
import { catchError, switchMap, tap } from 'rxjs/operators';
|
import { catchError, switchMap, tap } from 'rxjs/operators';
|
||||||
import { AuthStateService } from '../../auth-state/auth-state.service';
|
import { AuthStateService } from '../../auth-state/auth-state.service';
|
||||||
import type { OpenIdConfiguration } from '../../config/openid-configuration';
|
import { OpenIdConfiguration } from '../../config/openid-configuration';
|
||||||
import { DOCUMENT } from '../../dom';
|
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||||
import type { JwtKeys } from '../../validation/jwtkeys';
|
import { JwtKeys } from '../../validation/jwtkeys';
|
||||||
import { ValidationResult } from '../../validation/validation-result';
|
import { ValidationResult } from '../../validation/validation-result';
|
||||||
import type { CallbackContext } from '../callback-context';
|
import { CallbackContext } from '../callback-context';
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
import { ResetAuthDataService } from '../reset-auth-data.service';
|
import { ResetAuthDataService } from '../reset-auth-data.service';
|
||||||
import { SigninKeyDataService } from '../signin-key-data.service';
|
import { SigninKeyDataService } from '../signin-key-data.service';
|
||||||
@ -98,10 +98,10 @@ export class HistoryJwtKeysCallbackHandlerService {
|
|||||||
// fallback: try to load jwtKeys from storage
|
// fallback: try to load jwtKeys from storage
|
||||||
const storedJwtKeys = this.readSigningKeys(config);
|
const storedJwtKeys = this.readSigningKeys(config);
|
||||||
|
|
||||||
if (storedJwtKeys) {
|
if (!!storedJwtKeys) {
|
||||||
this.loggerService.logWarning(
|
this.loggerService.logWarning(
|
||||||
config,
|
config,
|
||||||
'Failed to retrieve signing keys, fallback to stored keys'
|
`Failed to retrieve signing keys, fallback to stored keys`
|
||||||
);
|
);
|
||||||
|
|
||||||
return of(storedJwtKeys);
|
return of(storedJwtKeys);
|
||||||
@ -116,7 +116,7 @@ export class HistoryJwtKeysCallbackHandlerService {
|
|||||||
return of(callbackContext);
|
return of(callbackContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorMessage = 'Failed to retrieve signing key';
|
const errorMessage = `Failed to retrieve signing key`;
|
||||||
|
|
||||||
this.loggerService.logWarning(config, errorMessage);
|
this.loggerService.logWarning(config, errorMessage);
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { TestBed } from '@/testing';
|
|
||||||
import { firstValueFrom } from 'rxjs';
|
|
||||||
import { vi } from 'vitest';
|
|
||||||
import { DOCUMENT } from '../../dom';
|
import { DOCUMENT } from '../../dom';
|
||||||
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { mockProvider } from '../../testing/mock';
|
import { CallbackContext } from '../callback-context';
|
||||||
import type { CallbackContext } from '../callback-context';
|
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
import { ResetAuthDataService } from '../reset-auth-data.service';
|
import { ResetAuthDataService } from '../reset-auth-data.service';
|
||||||
import { ImplicitFlowCallbackHandlerService } from './implicit-flow-callback-handler.service';
|
import { ImplicitFlowCallbackHandlerService } from './implicit-flow-callback-handler.service';
|
||||||
@ -36,6 +34,9 @@ describe('ImplicitFlowCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(ImplicitFlowCallbackHandlerService);
|
service = TestBed.inject(ImplicitFlowCallbackHandlerService);
|
||||||
flowsDataService = TestBed.inject(FlowsDataService);
|
flowsDataService = TestBed.inject(FlowsDataService);
|
||||||
resetAuthDataService = TestBed.inject(ResetAuthDataService);
|
resetAuthDataService = TestBed.inject(ResetAuthDataService);
|
||||||
@ -46,44 +47,46 @@ describe('ImplicitFlowCallbackHandlerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('implicitFlowCallback', () => {
|
describe('implicitFlowCallback', () => {
|
||||||
it('calls "resetAuthorizationData" if silent renew is not running', async () => {
|
it('calls "resetAuthorizationData" if silent renew is not running', waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||||
const resetAuthorizationDataSpy = vi.spyOn(
|
const resetAuthorizationDataSpy = spyOn(
|
||||||
resetAuthDataService,
|
resetAuthDataService,
|
||||||
'resetAuthorizationData'
|
'resetAuthorizationData'
|
||||||
);
|
);
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
await firstValueFrom(
|
service
|
||||||
service.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')
|
.implicitFlowCallback(allconfigs[0], allconfigs, 'any-hash')
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(resetAuthorizationDataSpy).toHaveBeenCalled();
|
expect(resetAuthorizationDataSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('does NOT calls "resetAuthorizationData" if silent renew is running', async () => {
|
it('does NOT calls "resetAuthorizationData" if silent renew is running', waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||||
const resetAuthorizationDataSpy = vi.spyOn(
|
const resetAuthorizationDataSpy = spyOn(
|
||||||
resetAuthDataService,
|
resetAuthDataService,
|
||||||
'resetAuthorizationData'
|
'resetAuthorizationData'
|
||||||
);
|
);
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
await firstValueFrom(
|
service
|
||||||
service.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')
|
.implicitFlowCallback(allconfigs[0], allconfigs, 'any-hash')
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(resetAuthorizationDataSpy).not.toHaveBeenCalled();
|
expect(resetAuthorizationDataSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('returns callbackContext if all params are good', async () => {
|
it('returns callbackContext if all params are good', waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||||
const expectedCallbackContext = {
|
const expectedCallbackContext = {
|
||||||
code: '',
|
code: '',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
@ -96,20 +99,21 @@ describe('ImplicitFlowCallbackHandlerService', () => {
|
|||||||
existingIdToken: null,
|
existingIdToken: null,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
|
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const callbackContext = await firstValueFrom(
|
service
|
||||||
service.implicitFlowCallback(allConfigs[0]!, allConfigs, 'anyHash')
|
.implicitFlowCallback(allconfigs[0], allconfigs, 'anyHash')
|
||||||
);
|
.subscribe((callbackContext) => {
|
||||||
expect(callbackContext).toEqual(expectedCallbackContext);
|
expect(callbackContext).toEqual(expectedCallbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('uses window location hash if no hash is passed', async () => {
|
it('uses window location hash if no hash is passed', waitForAsync(() => {
|
||||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||||
const expectedCallbackContext = {
|
const expectedCallbackContext = {
|
||||||
code: '',
|
code: '',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
@ -122,16 +126,17 @@ describe('ImplicitFlowCallbackHandlerService', () => {
|
|||||||
existingIdToken: null,
|
existingIdToken: null,
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
|
|
||||||
const allConfigs = [
|
const allconfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const callbackContext = await firstValueFrom(
|
service
|
||||||
service.implicitFlowCallback(allConfigs[0]!, allConfigs)
|
.implicitFlowCallback(allconfigs[0], allconfigs)
|
||||||
);
|
.subscribe((callbackContext) => {
|
||||||
expect(callbackContext).toEqual(expectedCallbackContext);
|
expect(callbackContext).toEqual(expectedCallbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
|
||||||
import { type Observable, of } from 'rxjs';
|
|
||||||
import type { OpenIdConfiguration } from '../../config/openid-configuration';
|
|
||||||
import { DOCUMENT } from '../../dom';
|
import { DOCUMENT } from '../../dom';
|
||||||
|
import { inject, Injectable } from 'injection-js';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { OpenIdConfiguration } from '../../config/openid-configuration';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import type { AuthResult, CallbackContext } from '../callback-context';
|
import { AuthResult, CallbackContext } from '../callback-context';
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
import { ResetAuthDataService } from '../reset-auth-data.service';
|
import { ResetAuthDataService } from '../reset-auth-data.service';
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
import { vi } from 'vitest';
|
|
||||||
import { AuthStateService } from '../../auth-state/auth-state.service';
|
import { AuthStateService } from '../../auth-state/auth-state.service';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { mockProvider } from '../../testing/mock';
|
import { CallbackContext } from '../callback-context';
|
||||||
import type { CallbackContext } from '../callback-context';
|
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
import { RefreshSessionCallbackHandlerService } from './refresh-session-callback-handler.service';
|
import { RefreshSessionCallbackHandlerService } from './refresh-session-callback-handler.service';
|
||||||
|
|
||||||
@ -22,6 +20,9 @@ describe('RefreshSessionCallbackHandlerService', () => {
|
|||||||
mockProvider(FlowsDataService),
|
mockProvider(FlowsDataService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(RefreshSessionCallbackHandlerService);
|
service = TestBed.inject(RefreshSessionCallbackHandlerService);
|
||||||
flowsDataService = TestBed.inject(FlowsDataService);
|
flowsDataService = TestBed.inject(FlowsDataService);
|
||||||
authStateService = TestBed.inject(AuthStateService);
|
authStateService = TestBed.inject(AuthStateService);
|
||||||
@ -32,15 +33,15 @@ describe('RefreshSessionCallbackHandlerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('refreshSessionWithRefreshTokens', () => {
|
describe('refreshSessionWithRefreshTokens', () => {
|
||||||
it('returns callbackContext if all params are good', async () => {
|
it('returns callbackContext if all params are good', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'getExistingOrCreateAuthStateControl'
|
'getExistingOrCreateAuthStateControl'
|
||||||
).mockReturnValue('state-data');
|
).and.returnValue('state-data');
|
||||||
vi.spyOn(authStateService, 'getRefreshToken').mockReturnValue(
|
spyOn(authStateService, 'getRefreshToken').and.returnValue(
|
||||||
'henlo-furiend'
|
'henlo-furiend'
|
||||||
);
|
);
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('henlo-legger');
|
spyOn(authStateService, 'getIdToken').and.returnValue('henlo-legger');
|
||||||
|
|
||||||
const expectedCallbackContext = {
|
const expectedCallbackContext = {
|
||||||
code: '',
|
code: '',
|
||||||
@ -54,27 +55,28 @@ describe('RefreshSessionCallbackHandlerService', () => {
|
|||||||
existingIdToken: 'henlo-legger',
|
existingIdToken: 'henlo-legger',
|
||||||
} as CallbackContext;
|
} as CallbackContext;
|
||||||
|
|
||||||
const callbackContext = await firstValueFrom(
|
service
|
||||||
service.refreshSessionWithRefreshTokens({ configId: 'configId1' })
|
.refreshSessionWithRefreshTokens({ configId: 'configId1' })
|
||||||
);
|
.subscribe((callbackContext) => {
|
||||||
expect(callbackContext).toEqual(expectedCallbackContext);
|
expect(callbackContext).toEqual(expectedCallbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('throws error if no refresh token is given', async () => {
|
it('throws error if no refresh token is given', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'getExistingOrCreateAuthStateControl'
|
'getExistingOrCreateAuthStateControl'
|
||||||
).mockReturnValue('state-data');
|
).and.returnValue('state-data');
|
||||||
vi.spyOn(authStateService, 'getRefreshToken').mockReturnValue('');
|
spyOn(authStateService, 'getRefreshToken').and.returnValue('');
|
||||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('henlo-legger');
|
spyOn(authStateService, 'getIdToken').and.returnValue('henlo-legger');
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.refreshSessionWithRefreshTokens({ configId: 'configId1' })
|
||||||
service.refreshSessionWithRefreshTokens({ configId: 'configId1' })
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err).toBeTruthy();
|
||||||
expect(err).toBeTruthy();
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, of, throwError } from 'rxjs';
|
import { Observable, of, throwError } from 'rxjs';
|
||||||
import { AuthStateService } from '../../auth-state/auth-state.service';
|
import { AuthStateService } from '../../auth-state/auth-state.service';
|
||||||
import type { OpenIdConfiguration } from '../../config/openid-configuration';
|
import { OpenIdConfiguration } from '../../config/openid-configuration';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { TokenValidationService } from '../../validation/token-validation.service';
|
import { TokenValidationService } from '../../validation/token-validation.service';
|
||||||
import type { CallbackContext } from '../callback-context';
|
import { CallbackContext } from '../callback-context';
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -24,7 +24,7 @@ export class RefreshSessionCallbackHandlerService {
|
|||||||
|
|
||||||
this.loggerService.logDebug(
|
this.loggerService.logDebug(
|
||||||
config,
|
config,
|
||||||
`RefreshSession created. Adding myautostate: ${stateData}`
|
'RefreshSession created. Adding myautostate: ' + stateData
|
||||||
);
|
);
|
||||||
const refreshToken = this.authStateService.getRefreshToken(config);
|
const refreshToken = this.authStateService.getRefreshToken(config);
|
||||||
const idToken = this.authStateService.getIdToken(config);
|
const idToken = this.authStateService.getIdToken(config);
|
||||||
@ -53,11 +53,12 @@ export class RefreshSessionCallbackHandlerService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return of(callbackContext);
|
return of(callbackContext);
|
||||||
|
} else {
|
||||||
|
const errorMessage = 'no refresh token found, please login';
|
||||||
|
|
||||||
|
this.loggerService.logError(config, errorMessage);
|
||||||
|
|
||||||
|
return throwError(() => new Error(errorMessage));
|
||||||
}
|
}
|
||||||
const errorMessage = 'no refresh token found, please login';
|
|
||||||
|
|
||||||
this.loggerService.logError(config, errorMessage);
|
|
||||||
|
|
||||||
return throwError(() => new Error(errorMessage));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
import { HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
import { HttpErrorResponse, HttpHeaders } from '@ngify/http';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
import { of, throwError } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
|
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
|
||||||
import { DataService } from '../../api/data.service';
|
import { DataService } from '../../api/data.service';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||||
import { createRetriableStream } from '../../testing/create-retriable-stream.helper';
|
|
||||||
import { mockProvider } from '../../testing/mock';
|
|
||||||
import { UrlService } from '../../utils/url/url.service';
|
import { UrlService } from '../../utils/url/url.service';
|
||||||
import type { CallbackContext } from '../callback-context';
|
import { CallbackContext } from '../callback-context';
|
||||||
import { RefreshTokenCallbackHandlerService } from './refresh-token-callback-handler.service';
|
import { RefreshTokenCallbackHandlerService } from './refresh-token-callback-handler.service';
|
||||||
|
|
||||||
describe('RefreshTokenCallbackHandlerService', () => {
|
describe('RefreshTokenCallbackHandlerService', () => {
|
||||||
@ -26,6 +25,9 @@ describe('RefreshTokenCallbackHandlerService', () => {
|
|||||||
mockProvider(StoragePersistenceService),
|
mockProvider(StoragePersistenceService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(RefreshTokenCallbackHandlerService);
|
service = TestBed.inject(RefreshTokenCallbackHandlerService);
|
||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
dataService = TestBed.inject(DataService);
|
dataService = TestBed.inject(DataService);
|
||||||
@ -44,87 +46,83 @@ describe('RefreshTokenCallbackHandlerService', () => {
|
|||||||
url: 'https://identity-server.test/openid-connect/token',
|
url: 'https://identity-server.test/openid-connect/token',
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws error if no tokenEndpoint is given', async () => {
|
it('throws error if no tokenEndpoint is given', waitForAsync(() => {
|
||||||
try {
|
(service as any)
|
||||||
await firstValueFrom(
|
.refreshTokensRequestTokens({} as CallbackContext)
|
||||||
(service as any).refreshTokensRequestTokens({} as CallbackContext)
|
.subscribe({
|
||||||
);
|
error: (err: unknown) => {
|
||||||
} catch (err: unknown) {
|
expect(err).toBeTruthy();
|
||||||
expect(err).toBeTruthy();
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('calls data service if all params are good', async () => {
|
it('calls data service if all params are good', waitForAsync(() => {
|
||||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
|
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
await firstValueFrom(
|
service
|
||||||
service.refreshTokensRequestTokens({} as CallbackContext, {
|
.refreshTokensRequestTokens({} as CallbackContext, {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
})
|
})
|
||||||
);
|
.subscribe(() => {
|
||||||
expect(postSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(postSpy).toHaveBeenCalledOnceWith(
|
||||||
'tokenEndpoint',
|
'tokenEndpoint',
|
||||||
undefined,
|
undefined,
|
||||||
{ configId: 'configId1' },
|
{ configId: 'configId1' },
|
||||||
expect.any(HttpHeaders)
|
jasmine.any(HttpHeaders)
|
||||||
);
|
);
|
||||||
const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
|
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;
|
||||||
expect(httpHeaders.has('Content-Type')).toBeTruthy();
|
|
||||||
expect(httpHeaders.get('Content-Type')).toBe(
|
|
||||||
'application/x-www-form-urlencoded'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls data service with correct headers if all params are good', async () => {
|
expect(httpHeaders.has('Content-Type')).toBeTrue();
|
||||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
|
expect(httpHeaders.get('Content-Type')).toBe(
|
||||||
|
'application/x-www-form-urlencoded'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
it('calls data service with correct headers if all params are good', waitForAsync(() => {
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
await firstValueFrom(
|
spyOn(storagePersistenceService, 'read')
|
||||||
service.refreshTokensRequestTokens({} as CallbackContext, {
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
|
|
||||||
|
service
|
||||||
|
.refreshTokensRequestTokens({} as CallbackContext, {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
})
|
})
|
||||||
);
|
.subscribe(() => {
|
||||||
const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
|
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;
|
||||||
expect(httpHeaders.has('Content-Type')).toBeTruthy();
|
|
||||||
expect(httpHeaders.get('Content-Type')).toBe(
|
|
||||||
'application/x-www-form-urlencoded'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns error in case of http error', async () => {
|
expect(httpHeaders.has('Content-Type')).toBeTrue();
|
||||||
vi.spyOn(dataService, 'post').mockReturnValue(
|
expect(httpHeaders.get('Content-Type')).toBe(
|
||||||
throwError(() => HTTP_ERROR)
|
'application/x-www-form-urlencoded'
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('returns error in case of http error', waitForAsync(() => {
|
||||||
|
spyOn(dataService, 'post').and.returnValue(throwError(() => HTTP_ERROR));
|
||||||
const config = { configId: 'configId1', authority: 'authority' };
|
const config = { configId: 'configId1', authority: 'authority' };
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', config)
|
||||||
['authWellKnownEndPoints', config],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.refreshTokensRequestTokens({} as CallbackContext, config)
|
||||||
service.refreshTokensRequestTokens({} as CallbackContext, config)
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err).toBeTruthy();
|
||||||
expect(err).toBeTruthy();
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('retries request in case of no connection http error and succeeds', async () => {
|
it('retries request in case of no connection http error and succeeds', waitForAsync(() => {
|
||||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
|
const postSpy = spyOn(dataService, 'post').and.returnValue(
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => CONNECTION_ERROR),
|
throwError(() => CONNECTION_ERROR),
|
||||||
of({})
|
of({})
|
||||||
@ -132,25 +130,26 @@ describe('RefreshTokenCallbackHandlerService', () => {
|
|||||||
);
|
);
|
||||||
const config = { configId: 'configId1', authority: 'authority' };
|
const config = { configId: 'configId1', authority: 'authority' };
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', config)
|
||||||
['authWellKnownEndPoints', config],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
service
|
||||||
const res = await firstValueFrom(
|
.refreshTokensRequestTokens({} as CallbackContext, config)
|
||||||
service.refreshTokensRequestTokens({} as CallbackContext, config)
|
.subscribe({
|
||||||
);
|
next: (res) => {
|
||||||
expect(res).toBeTruthy();
|
expect(res).toBeTruthy();
|
||||||
expect(postSpy).toHaveBeenCalledTimes(1);
|
expect(postSpy).toHaveBeenCalledTimes(1);
|
||||||
} catch (err: any) {
|
},
|
||||||
expect(err).toBeFalsy();
|
error: (err) => {
|
||||||
}
|
// fails if there should be a result
|
||||||
});
|
expect(err).toBeFalsy();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('retries request in case of no connection http error and fails because of http error afterwards', async () => {
|
it('retries request in case of no connection http error and fails because of http error afterwards', waitForAsync(() => {
|
||||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
|
const postSpy = spyOn(dataService, 'post').and.returnValue(
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => CONNECTION_ERROR),
|
throwError(() => CONNECTION_ERROR),
|
||||||
throwError(() => HTTP_ERROR)
|
throwError(() => HTTP_ERROR)
|
||||||
@ -158,21 +157,22 @@ describe('RefreshTokenCallbackHandlerService', () => {
|
|||||||
);
|
);
|
||||||
const config = { configId: 'configId1', authority: 'authority' };
|
const config = { configId: 'configId1', authority: 'authority' };
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', config)
|
||||||
['authWellKnownEndPoints', config],
|
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
service
|
||||||
const res = await firstValueFrom(
|
.refreshTokensRequestTokens({} as CallbackContext, config)
|
||||||
service.refreshTokensRequestTokens({} as CallbackContext, config)
|
.subscribe({
|
||||||
);
|
next: (res) => {
|
||||||
expect(res).toBeFalsy();
|
// fails if there should be a result
|
||||||
} catch (err: any) {
|
expect(res).toBeFalsy();
|
||||||
expect(err).toBeTruthy();
|
},
|
||||||
expect(postSpy).toHaveBeenCalledTimes(1);
|
error: (err) => {
|
||||||
}
|
expect(err).toBeTruthy();
|
||||||
});
|
expect(postSpy).toHaveBeenCalledTimes(1);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { HttpHeaders } from '@ngify/http';
|
import { HttpHeaders } from '@ngify/http';
|
||||||
import { inject, Injectable } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, of, throwError, timer } from 'rxjs';
|
import { Observable, of, throwError, timer } from 'rxjs';
|
||||||
import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators';
|
import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators';
|
||||||
import { DataService } from '../../api/data.service';
|
import { DataService } from '../../api/data.service';
|
||||||
import type { OpenIdConfiguration } from '../../config/openid-configuration';
|
import { OpenIdConfiguration } from '../../config/openid-configuration';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||||
import { UrlService } from '../../utils/url/url.service';
|
import { UrlService } from '../../utils/url/url.service';
|
||||||
import type { AuthResult, CallbackContext } from '../callback-context';
|
import { AuthResult, CallbackContext } from '../callback-context';
|
||||||
import { isNetworkError } from './error-helper';
|
import { isNetworkError } from './error-helper';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { TestBed } from '@/testing';
|
|
||||||
import { firstValueFrom, of } from 'rxjs';
|
|
||||||
import { vi } from 'vitest';
|
|
||||||
import { AuthStateService } from '../../auth-state/auth-state.service';
|
|
||||||
import { DOCUMENT } from '../../dom';
|
import { DOCUMENT } from '../../dom';
|
||||||
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
|
import { AuthStateService } from '../../auth-state/auth-state.service';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { mockProvider } from '../../testing/mock';
|
import { StateValidationResult } from '../../validation/state-validation-result';
|
||||||
import type { StateValidationResult } from '../../validation/state-validation-result';
|
|
||||||
import { StateValidationService } from '../../validation/state-validation.service';
|
import { StateValidationService } from '../../validation/state-validation.service';
|
||||||
import { ValidationResult } from '../../validation/validation-result';
|
import { ValidationResult } from '../../validation/validation-result';
|
||||||
import type { CallbackContext } from '../callback-context';
|
import { CallbackContext } from '../callback-context';
|
||||||
import { ResetAuthDataService } from '../reset-auth-data.service';
|
import { ResetAuthDataService } from '../reset-auth-data.service';
|
||||||
import { StateValidationCallbackHandlerService } from './state-validation-callback-handler.service';
|
import { StateValidationCallbackHandlerService } from './state-validation-callback-handler.service';
|
||||||
|
|
||||||
@ -42,6 +41,9 @@ describe('StateValidationCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(StateValidationCallbackHandlerService);
|
service = TestBed.inject(StateValidationCallbackHandlerService);
|
||||||
stateValidationService = TestBed.inject(StateValidationService);
|
stateValidationService = TestBed.inject(StateValidationService);
|
||||||
loggerService = TestBed.inject(LoggerService);
|
loggerService = TestBed.inject(LoggerService);
|
||||||
@ -54,11 +56,8 @@ describe('StateValidationCallbackHandlerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('callbackStateValidation', () => {
|
describe('callbackStateValidation', () => {
|
||||||
it('returns callbackContext with validationResult if validationResult is valid', async () => {
|
it('returns callbackContext with validationResult if validationResult is valid', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(stateValidationService, 'getValidatedStateResult').and.returnValue(
|
||||||
stateValidationService,
|
|
||||||
'getValidatedStateResult'
|
|
||||||
).mockReturnValue(
|
|
||||||
of({
|
of({
|
||||||
idToken: 'idTokenJustForTesting',
|
idToken: 'idTokenJustForTesting',
|
||||||
authResponseIsValid: true,
|
authResponseIsValid: true,
|
||||||
@ -66,87 +65,82 @@ describe('StateValidationCallbackHandlerService', () => {
|
|||||||
);
|
);
|
||||||
const allConfigs = [{ configId: 'configId1' }];
|
const allConfigs = [{ configId: 'configId1' }];
|
||||||
|
|
||||||
const newCallbackContext = await firstValueFrom(
|
service
|
||||||
service.callbackStateValidation(
|
.callbackStateValidation(
|
||||||
{} as CallbackContext,
|
{} as CallbackContext,
|
||||||
allConfigs[0]!,
|
allConfigs[0],
|
||||||
allConfigs
|
allConfigs
|
||||||
)
|
)
|
||||||
);
|
.subscribe((newCallbackContext) => {
|
||||||
expect(newCallbackContext).toEqual({
|
expect(newCallbackContext).toEqual({
|
||||||
validationResult: {
|
validationResult: {
|
||||||
idToken: 'idTokenJustForTesting',
|
idToken: 'idTokenJustForTesting',
|
||||||
authResponseIsValid: true,
|
authResponseIsValid: true,
|
||||||
},
|
},
|
||||||
} as CallbackContext);
|
} as CallbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('logs error in case of an error', async () => {
|
it('logs error in case of an error', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(stateValidationService, 'getValidatedStateResult').and.returnValue(
|
||||||
stateValidationService,
|
|
||||||
'getValidatedStateResult'
|
|
||||||
).mockReturnValue(
|
|
||||||
of({
|
of({
|
||||||
authResponseIsValid: false,
|
authResponseIsValid: false,
|
||||||
} as StateValidationResult)
|
} as StateValidationResult)
|
||||||
);
|
);
|
||||||
|
|
||||||
const loggerSpy = vi.spyOn(loggerService, 'logWarning');
|
const loggerSpy = spyOn(loggerService, 'logWarning');
|
||||||
const allConfigs = [{ configId: 'configId1' }];
|
const allConfigs = [{ configId: 'configId1' }];
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.callbackStateValidation(
|
||||||
service.callbackStateValidation(
|
{} as CallbackContext,
|
||||||
{} as CallbackContext,
|
allConfigs[0],
|
||||||
allConfigs[0]!,
|
allConfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
error: () => {
|
||||||
} catch {
|
expect(loggerSpy).toHaveBeenCalledOnceWith(
|
||||||
expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
|
allConfigs[0],
|
||||||
allConfigs[0]!,
|
'authorizedCallback, token(s) validation failed, resetting. Hash: &anyFakeHash'
|
||||||
'authorizedCallback, token(s) validation failed, resetting. Hash: &anyFakeHash'
|
);
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('calls resetAuthDataService.resetAuthorizationData and authStateService.updateAndPublishAuthState in case of an error', async () => {
|
it('calls resetAuthDataService.resetAuthorizationData and authStateService.updateAndPublishAuthState in case of an error', waitForAsync(() => {
|
||||||
vi.spyOn(
|
spyOn(stateValidationService, 'getValidatedStateResult').and.returnValue(
|
||||||
stateValidationService,
|
|
||||||
'getValidatedStateResult'
|
|
||||||
).mockReturnValue(
|
|
||||||
of({
|
of({
|
||||||
authResponseIsValid: false,
|
authResponseIsValid: false,
|
||||||
state: ValidationResult.LoginRequired,
|
state: ValidationResult.LoginRequired,
|
||||||
} as StateValidationResult)
|
} as StateValidationResult)
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetAuthorizationDataSpy = vi.spyOn(
|
const resetAuthorizationDataSpy = spyOn(
|
||||||
resetAuthDataService,
|
resetAuthDataService,
|
||||||
'resetAuthorizationData'
|
'resetAuthorizationData'
|
||||||
);
|
);
|
||||||
const updateAndPublishAuthStateSpy = vi.spyOn(
|
const updateAndPublishAuthStateSpy = spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'updateAndPublishAuthState'
|
'updateAndPublishAuthState'
|
||||||
);
|
);
|
||||||
const allConfigs = [{ configId: 'configId1' }];
|
const allConfigs = [{ configId: 'configId1' }];
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.callbackStateValidation(
|
||||||
service.callbackStateValidation(
|
{ isRenewProcess: true } as CallbackContext,
|
||||||
{ isRenewProcess: true } as CallbackContext,
|
allConfigs[0],
|
||||||
allConfigs[0]!,
|
allConfigs
|
||||||
allConfigs
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
error: () => {
|
||||||
} catch {
|
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
|
||||||
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
|
isAuthenticated: false,
|
||||||
isAuthenticated: false,
|
validationResult: ValidationResult.LoginRequired,
|
||||||
validationResult: ValidationResult.LoginRequired,
|
isRenewProcess: true,
|
||||||
isRenewProcess: true,
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { DOCUMENT } from '../../dom';
|
||||||
import type { Observable } from 'rxjs';
|
import { inject, Injectable } from 'injection-js';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { AuthStateService } from '../../auth-state/auth-state.service';
|
import { AuthStateService } from '../../auth-state/auth-state.service';
|
||||||
import type { OpenIdConfiguration } from '../../config/openid-configuration';
|
import { OpenIdConfiguration } from '../../config/openid-configuration';
|
||||||
import { DOCUMENT } from '../../dom';
|
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import type { StateValidationResult } from '../../validation/state-validation-result';
|
import { StateValidationResult } from '../../validation/state-validation-result';
|
||||||
import { StateValidationService } from '../../validation/state-validation.service';
|
import { StateValidationService } from '../../validation/state-validation.service';
|
||||||
import type { CallbackContext } from '../callback-context';
|
import { CallbackContext } from '../callback-context';
|
||||||
import { ResetAuthDataService } from '../reset-auth-data.service';
|
import { ResetAuthDataService } from '../reset-auth-data.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -43,20 +43,21 @@ export class StateValidationCallbackHandlerService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return callbackContext;
|
return callbackContext;
|
||||||
|
} else {
|
||||||
|
const errorMessage = `authorizedCallback, token(s) validation failed, resetting. Hash: ${this.document.location.hash}`;
|
||||||
|
|
||||||
|
this.loggerService.logWarning(configuration, errorMessage);
|
||||||
|
this.resetAuthDataService.resetAuthorizationData(
|
||||||
|
configuration,
|
||||||
|
allConfigs
|
||||||
|
);
|
||||||
|
this.publishUnauthorizedState(
|
||||||
|
callbackContext.validationResult,
|
||||||
|
callbackContext.isRenewProcess
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
const errorMessage = `authorizedCallback, token(s) validation failed, resetting. Hash: ${this.document.location.hash}`;
|
|
||||||
|
|
||||||
this.loggerService.logWarning(configuration, errorMessage);
|
|
||||||
this.resetAuthDataService.resetAuthorizationData(
|
|
||||||
configuration,
|
|
||||||
allConfigs
|
|
||||||
);
|
|
||||||
this.publishUnauthorizedState(
|
|
||||||
callbackContext.validationResult,
|
|
||||||
callbackContext.isRenewProcess
|
|
||||||
);
|
|
||||||
|
|
||||||
throw new Error(errorMessage);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom, of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
import { AuthStateService } from '../../auth-state/auth-state.service';
|
import { AuthStateService } from '../../auth-state/auth-state.service';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { mockProvider } from '../../testing/mock';
|
|
||||||
import { UserService } from '../../user-data/user.service';
|
import { UserService } from '../../user-data/user.service';
|
||||||
import { StateValidationResult } from '../../validation/state-validation-result';
|
import { StateValidationResult } from '../../validation/state-validation-result';
|
||||||
import { ValidationResult } from '../../validation/validation-result';
|
import { ValidationResult } from '../../validation/validation-result';
|
||||||
import type { CallbackContext } from '../callback-context';
|
import { CallbackContext } from '../callback-context';
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
import { ResetAuthDataService } from '../reset-auth-data.service';
|
import { ResetAuthDataService } from '../reset-auth-data.service';
|
||||||
import { UserCallbackHandlerService } from './user-callback-handler.service';
|
import { UserCallbackHandlerService } from './user-callback-handler.service';
|
||||||
@ -30,6 +29,9 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
mockProvider(ResetAuthDataService),
|
mockProvider(ResetAuthDataService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(UserCallbackHandlerService);
|
service = TestBed.inject(UserCallbackHandlerService);
|
||||||
flowsDataService = TestBed.inject(FlowsDataService);
|
flowsDataService = TestBed.inject(FlowsDataService);
|
||||||
authStateService = TestBed.inject(AuthStateService);
|
authStateService = TestBed.inject(AuthStateService);
|
||||||
@ -42,7 +44,7 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('callbackUser', () => {
|
describe('callbackUser', () => {
|
||||||
it('calls flowsDataService.setSessionState with correct params if autoUserInfo is false, isRenewProcess is false and refreshToken is null', async () => {
|
it('calls flowsDataService.setSessionState with correct params if autoUserInfo is false, isRenewProcess is false and refreshToken is null', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult(
|
const svr = new StateValidationResult(
|
||||||
'accesstoken',
|
'accesstoken',
|
||||||
'idtoken',
|
'idtoken',
|
||||||
@ -68,16 +70,17 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const spy = vi.spyOn(flowsDataService, 'setSessionState');
|
const spy = spyOn(flowsDataService, 'setSessionState');
|
||||||
|
|
||||||
const resultCallbackContext = await firstValueFrom(
|
service
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((resultCallbackContext) => {
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith('mystate', allConfigs[0]);
|
expect(spy).toHaveBeenCalledOnceWith('mystate', allConfigs[0]);
|
||||||
expect(resultCallbackContext).toEqual(callbackContext);
|
expect(resultCallbackContext).toEqual(callbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false, isRenewProcess is true and refreshToken is null', async () => {
|
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false, isRenewProcess is true and refreshToken is null', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult(
|
const svr = new StateValidationResult(
|
||||||
'accesstoken',
|
'accesstoken',
|
||||||
'idtoken',
|
'idtoken',
|
||||||
@ -101,16 +104,17 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
autoUserInfo: false,
|
autoUserInfo: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const spy = vi.spyOn(flowsDataService, 'setSessionState');
|
const spy = spyOn(flowsDataService, 'setSessionState');
|
||||||
|
|
||||||
const resultCallbackContext = await firstValueFrom(
|
service
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((resultCallbackContext) => {
|
||||||
expect(spy).not.toHaveBeenCalled();
|
expect(spy).not.toHaveBeenCalled();
|
||||||
expect(resultCallbackContext).toEqual(callbackContext);
|
expect(resultCallbackContext).toEqual(callbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value', async () => {
|
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult(
|
const svr = new StateValidationResult(
|
||||||
'accesstoken',
|
'accesstoken',
|
||||||
'idtoken',
|
'idtoken',
|
||||||
@ -134,16 +138,17 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
autoUserInfo: false,
|
autoUserInfo: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const spy = vi.spyOn(flowsDataService, 'setSessionState');
|
const spy = spyOn(flowsDataService, 'setSessionState');
|
||||||
|
|
||||||
const resultCallbackContext = await firstValueFrom(
|
service
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((resultCallbackContext) => {
|
||||||
expect(spy).not.toHaveBeenCalled();
|
expect(spy).not.toHaveBeenCalled();
|
||||||
expect(resultCallbackContext).toEqual(callbackContext);
|
expect(resultCallbackContext).toEqual(callbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value, id_token is false', async () => {
|
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value, id_token is false', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult('accesstoken', '', true, '');
|
const svr = new StateValidationResult('accesstoken', '', true, '');
|
||||||
const callbackContext = {
|
const callbackContext = {
|
||||||
code: '',
|
code: '',
|
||||||
@ -163,16 +168,17 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const spy = vi.spyOn(flowsDataService, 'setSessionState');
|
const spy = spyOn(flowsDataService, 'setSessionState');
|
||||||
|
|
||||||
const resultCallbackContext = await firstValueFrom(
|
service
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((resultCallbackContext) => {
|
||||||
expect(spy).not.toHaveBeenCalled();
|
expect(spy).not.toHaveBeenCalled();
|
||||||
expect(resultCallbackContext).toEqual(callbackContext);
|
expect(resultCallbackContext).toEqual(callbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is false', async () => {
|
it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is false', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult(
|
const svr = new StateValidationResult(
|
||||||
'accesstoken',
|
'accesstoken',
|
||||||
'idtoken',
|
'idtoken',
|
||||||
@ -198,23 +204,24 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const updateAndPublishAuthStateSpy = vi.spyOn(
|
const updateAndPublishAuthStateSpy = spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'updateAndPublishAuthState'
|
'updateAndPublishAuthState'
|
||||||
);
|
);
|
||||||
|
|
||||||
const resultCallbackContext = await firstValueFrom(
|
service
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((resultCallbackContext) => {
|
||||||
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
|
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
validationResult: ValidationResult.NotSet,
|
validationResult: ValidationResult.NotSet,
|
||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
});
|
});
|
||||||
expect(resultCallbackContext).toEqual(callbackContext);
|
expect(resultCallbackContext).toEqual(callbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls userService.getAndPersistUserDataInStore with correct params if autoUserInfo is true', async () => {
|
it('calls userService.getAndPersistUserDataInStore with correct params if autoUserInfo is true', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult(
|
const svr = new StateValidationResult(
|
||||||
'accesstoken',
|
'accesstoken',
|
||||||
'idtoken',
|
'idtoken',
|
||||||
@ -240,24 +247,26 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const getAndPersistUserDataInStoreSpy = vi
|
const getAndPersistUserDataInStoreSpy = spyOn(
|
||||||
.spyOn(userService, 'getAndPersistUserDataInStore')
|
userService,
|
||||||
.mockReturnValue(of({ user: 'some_data' }));
|
'getAndPersistUserDataInStore'
|
||||||
|
).and.returnValue(of({ user: 'some_data' }));
|
||||||
|
|
||||||
const resultCallbackContext = await firstValueFrom(
|
service
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((resultCallbackContext) => {
|
||||||
expect(getAndPersistUserDataInStoreSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(getAndPersistUserDataInStoreSpy).toHaveBeenCalledOnceWith(
|
||||||
allConfigs[0]!,
|
allConfigs[0],
|
||||||
allConfigs,
|
allConfigs,
|
||||||
false,
|
false,
|
||||||
'idtoken',
|
'idtoken',
|
||||||
'decoded'
|
'decoded'
|
||||||
);
|
);
|
||||||
expect(resultCallbackContext).toEqual(callbackContext);
|
expect(resultCallbackContext).toEqual(callbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is true', async () => {
|
it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is true', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult(
|
const svr = new StateValidationResult(
|
||||||
'accesstoken',
|
'accesstoken',
|
||||||
'idtoken',
|
'idtoken',
|
||||||
@ -284,26 +293,27 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
|
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue(
|
||||||
of({ user: 'some_data' })
|
of({ user: 'some_data' })
|
||||||
);
|
);
|
||||||
const updateAndPublishAuthStateSpy = vi.spyOn(
|
const updateAndPublishAuthStateSpy = spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'updateAndPublishAuthState'
|
'updateAndPublishAuthState'
|
||||||
);
|
);
|
||||||
|
|
||||||
const resultCallbackContext = await firstValueFrom(
|
service
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((resultCallbackContext) => {
|
||||||
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
|
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
validationResult: ValidationResult.MaxOffsetExpired,
|
validationResult: ValidationResult.MaxOffsetExpired,
|
||||||
isRenewProcess: false,
|
isRenewProcess: false,
|
||||||
});
|
});
|
||||||
expect(resultCallbackContext).toEqual(callbackContext);
|
expect(resultCallbackContext).toEqual(callbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls flowsDataService.setSessionState with correct params if user data is present and NOT refresh token', async () => {
|
it('calls flowsDataService.setSessionState with correct params if user data is present and NOT refresh token', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult(
|
const svr = new StateValidationResult(
|
||||||
'accesstoken',
|
'accesstoken',
|
||||||
'idtoken',
|
'idtoken',
|
||||||
@ -330,22 +340,23 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
|
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue(
|
||||||
of({ user: 'some_data' })
|
of({ user: 'some_data' })
|
||||||
);
|
);
|
||||||
const setSessionStateSpy = vi.spyOn(flowsDataService, 'setSessionState');
|
const setSessionStateSpy = spyOn(flowsDataService, 'setSessionState');
|
||||||
|
|
||||||
const resultCallbackContext = await firstValueFrom(
|
service
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((resultCallbackContext) => {
|
||||||
expect(setSessionStateSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(setSessionStateSpy).toHaveBeenCalledOnceWith(
|
||||||
'mystate',
|
'mystate',
|
||||||
allConfigs[0]
|
allConfigs[0]
|
||||||
);
|
);
|
||||||
expect(resultCallbackContext).toEqual(callbackContext);
|
expect(resultCallbackContext).toEqual(callbackContext);
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('calls authStateService.publishUnauthorizedState with correct params if user info which are coming back are null', async () => {
|
it('calls authStateService.publishUnauthorizedState with correct params if user info which are coming back are null', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult(
|
const svr = new StateValidationResult(
|
||||||
'accesstoken',
|
'accesstoken',
|
||||||
'idtoken',
|
'idtoken',
|
||||||
@ -372,31 +383,31 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
|
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue(
|
||||||
of(null)
|
of(null)
|
||||||
);
|
);
|
||||||
const updateAndPublishAuthStateSpy = vi.spyOn(
|
const updateAndPublishAuthStateSpy = spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'updateAndPublishAuthState'
|
'updateAndPublishAuthState'
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({
|
||||||
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
|
isAuthenticated: false,
|
||||||
isAuthenticated: false,
|
validationResult: ValidationResult.MaxOffsetExpired,
|
||||||
validationResult: ValidationResult.MaxOffsetExpired,
|
isRenewProcess: false,
|
||||||
isRenewProcess: false,
|
});
|
||||||
|
expect(err.message).toEqual(
|
||||||
|
'Failed to retrieve user info with error: Error: Called for userData but they were null'
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(err.message).toEqual(
|
}));
|
||||||
'Failed to retrieve user info with error: Error: Called for userData but they were null'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls resetAuthDataService.resetAuthorizationData if user info which are coming back are null', async () => {
|
it('calls resetAuthDataService.resetAuthorizationData if user info which are coming back are null', waitForAsync(() => {
|
||||||
const svr = new StateValidationResult(
|
const svr = new StateValidationResult(
|
||||||
'accesstoken',
|
'accesstoken',
|
||||||
'idtoken',
|
'idtoken',
|
||||||
@ -423,24 +434,24 @@ describe('UserCallbackHandlerService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue(
|
spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue(
|
||||||
of(null)
|
of(null)
|
||||||
);
|
);
|
||||||
const resetAuthorizationDataSpy = vi.spyOn(
|
const resetAuthorizationDataSpy = spyOn(
|
||||||
resetAuthDataService,
|
resetAuthDataService,
|
||||||
'resetAuthorizationData'
|
'resetAuthorizationData'
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
service
|
||||||
await firstValueFrom(
|
.callbackUser(callbackContext, allConfigs[0], allConfigs)
|
||||||
service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
|
.subscribe({
|
||||||
);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
expect(err.message).toEqual(
|
||||||
expect(err.message).toEqual(
|
'Failed to retrieve user info with error: Error: Called for userData but they were null'
|
||||||
'Failed to retrieve user info with error: Error: Called for userData but they were null'
|
);
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { type Observable, of, throwError } from 'rxjs';
|
import { Observable, of, throwError } from 'rxjs';
|
||||||
import { catchError, switchMap } from 'rxjs/operators';
|
import { catchError, switchMap } from 'rxjs/operators';
|
||||||
import { AuthStateService } from '../../auth-state/auth-state.service';
|
import { AuthStateService } from '../../auth-state/auth-state.service';
|
||||||
import type { OpenIdConfiguration } from '../../config/openid-configuration';
|
import { OpenIdConfiguration } from '../../config/openid-configuration';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { UserService } from '../../user-data/user.service';
|
import { UserService } from '../../user-data/user.service';
|
||||||
import type { StateValidationResult } from '../../validation/state-validation-result';
|
import { StateValidationResult } from '../../validation/state-validation-result';
|
||||||
import type { CallbackContext } from '../callback-context';
|
import { CallbackContext } from '../callback-context';
|
||||||
import { FlowsDataService } from '../flows-data.service';
|
import { FlowsDataService } from '../flows-data.service';
|
||||||
import { ResetAuthDataService } from '../reset-auth-data.service';
|
import { ResetAuthDataService } from '../reset-auth-data.service';
|
||||||
|
|
||||||
@ -35,7 +35,6 @@ export class UserCallbackHandlerService {
|
|||||||
if (!autoUserInfo) {
|
if (!autoUserInfo) {
|
||||||
if (!isRenewProcess || renewUserInfoAfterTokenRenew) {
|
if (!isRenewProcess || renewUserInfoAfterTokenRenew) {
|
||||||
// userData is set to the id_token decoded, auto get user data set to false
|
// userData is set to the id_token decoded, auto get user data set to false
|
||||||
// biome-ignore lint/nursery/useCollapsedIf: <explanation>
|
|
||||||
if (validationResult?.decodedIdToken) {
|
if (validationResult?.decodedIdToken) {
|
||||||
this.userService.setUserDataToStore(
|
this.userService.setUserDataToStore(
|
||||||
validationResult.decodedIdToken,
|
validationResult.decodedIdToken,
|
||||||
@ -67,7 +66,7 @@ export class UserCallbackHandlerService {
|
|||||||
)
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((userData) => {
|
switchMap((userData) => {
|
||||||
if (userData) {
|
if (!!userData) {
|
||||||
if (!refreshToken) {
|
if (!refreshToken) {
|
||||||
this.flowsDataService.setSessionState(
|
this.flowsDataService.setSessionState(
|
||||||
authResult?.session_state,
|
authResult?.session_state,
|
||||||
@ -78,17 +77,18 @@ export class UserCallbackHandlerService {
|
|||||||
this.publishAuthState(validationResult, isRenewProcess);
|
this.publishAuthState(validationResult, isRenewProcess);
|
||||||
|
|
||||||
return of(callbackContext);
|
return of(callbackContext);
|
||||||
|
} else {
|
||||||
|
this.resetAuthDataService.resetAuthorizationData(
|
||||||
|
configuration,
|
||||||
|
allConfigs
|
||||||
|
);
|
||||||
|
this.publishUnauthenticatedState(validationResult, isRenewProcess);
|
||||||
|
const errorMessage = `Called for userData but they were ${userData}`;
|
||||||
|
|
||||||
|
this.loggerService.logWarning(configuration, errorMessage);
|
||||||
|
|
||||||
|
return throwError(() => new Error(errorMessage));
|
||||||
}
|
}
|
||||||
this.resetAuthDataService.resetAuthorizationData(
|
|
||||||
configuration,
|
|
||||||
allConfigs
|
|
||||||
);
|
|
||||||
this.publishUnauthenticatedState(validationResult, isRenewProcess);
|
|
||||||
const errorMessage = `Called for userData but they were ${userData}`;
|
|
||||||
|
|
||||||
this.loggerService.logWarning(configuration, errorMessage);
|
|
||||||
|
|
||||||
return throwError(() => new Error(errorMessage));
|
|
||||||
}),
|
}),
|
||||||
catchError((err) => {
|
catchError((err) => {
|
||||||
const errorMessage = `Failed to retrieve user info with error: ${err}`;
|
const errorMessage = `Failed to retrieve user info with error: ${err}`;
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { CryptoService } from '../utils/crypto/crypto.service';
|
import { CryptoService } from '../utils/crypto/crypto.service';
|
||||||
import { FlowsDataService } from './flows-data.service';
|
import { FlowsDataService } from './flows-data.service';
|
||||||
import { RandomService } from './random/random.service';
|
import { RandomService } from './random/random.service';
|
||||||
@ -21,13 +20,15 @@ describe('Flows Data Service', () => {
|
|||||||
mockProvider(StoragePersistenceService),
|
mockProvider(StoragePersistenceService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(FlowsDataService);
|
service = TestBed.inject(FlowsDataService);
|
||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
});
|
});
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.useRealTimers();
|
jasmine.clock().uninstall();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
@ -36,12 +37,12 @@ describe('Flows Data Service', () => {
|
|||||||
|
|
||||||
describe('createNonce', () => {
|
describe('createNonce', () => {
|
||||||
it('createNonce returns nonce and stores it', () => {
|
it('createNonce returns nonce and stores it', () => {
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
const spy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
const result = service.createNonce({ configId: 'configId1' });
|
const result = service.createNonce({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith('authNonce', result, {
|
expect(spy).toHaveBeenCalledOnceWith('authNonce', result, {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -49,38 +50,32 @@ describe('Flows Data Service', () => {
|
|||||||
|
|
||||||
describe('AuthStateControl', () => {
|
describe('AuthStateControl', () => {
|
||||||
it('getAuthStateControl returns property from store', () => {
|
it('getAuthStateControl returns property from store', () => {
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'read');
|
const spy = spyOn(storagePersistenceService, 'read');
|
||||||
|
|
||||||
service.getAuthStateControl({ configId: 'configId1' });
|
service.getAuthStateControl({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith('authStateControl', {
|
expect(spy).toHaveBeenCalledOnceWith('authStateControl', {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('setAuthStateControl saves property in store', () => {
|
it('setAuthStateControl saves property in store', () => {
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
const spy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
service.setAuthStateControl('ToSave', { configId: 'configId1' });
|
service.setAuthStateControl('ToSave', { configId: 'configId1' });
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
expect(spy).toHaveBeenCalledOnceWith('authStateControl', 'ToSave', {
|
||||||
'authStateControl',
|
configId: 'configId1',
|
||||||
'ToSave',
|
});
|
||||||
{
|
|
||||||
configId: 'configId1',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getExistingOrCreateAuthStateControl', () => {
|
describe('getExistingOrCreateAuthStateControl', () => {
|
||||||
it('if nothing stored it creates a 40 char one and saves the authStateControl', () => {
|
it('if nothing stored it creates a 40 char one and saves the authStateControl', () => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authStateControl', { configId: 'configId1' })
|
||||||
['authStateControl', { configId: 'configId1' }],
|
.and.returnValue(null);
|
||||||
() => null
|
const setSpy = spyOn(storagePersistenceService, 'write');
|
||||||
);
|
|
||||||
const setSpy = vi.spyOn(storagePersistenceService, 'write');
|
|
||||||
|
|
||||||
const result = service.getExistingOrCreateAuthStateControl({
|
const result = service.getExistingOrCreateAuthStateControl({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -88,22 +83,16 @@ describe('Flows Data Service', () => {
|
|||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
expect(result.length).toBe(41);
|
expect(result.length).toBe(41);
|
||||||
expect(setSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(setSpy).toHaveBeenCalledOnceWith('authStateControl', result, {
|
||||||
'authStateControl',
|
configId: 'configId1',
|
||||||
result,
|
});
|
||||||
{
|
|
||||||
configId: 'configId1',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if stored it returns the value and does NOT Store the value again', () => {
|
it('if stored it returns the value and does NOT Store the value again', () => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authStateControl', { configId: 'configId1' })
|
||||||
['authStateControl', { configId: 'configId1' }],
|
.and.returnValue('someAuthStateControl');
|
||||||
() => 'someAuthStateControl'
|
const setSpy = spyOn(storagePersistenceService, 'write');
|
||||||
);
|
|
||||||
const setSpy = vi.spyOn(storagePersistenceService, 'write');
|
|
||||||
|
|
||||||
const result = service.getExistingOrCreateAuthStateControl({
|
const result = service.getExistingOrCreateAuthStateControl({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
@ -117,11 +106,11 @@ describe('Flows Data Service', () => {
|
|||||||
|
|
||||||
describe('setSessionState', () => {
|
describe('setSessionState', () => {
|
||||||
it('setSessionState saves the value in the storage', () => {
|
it('setSessionState saves the value in the storage', () => {
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
const spy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
service.setSessionState('Genesis', { configId: 'configId1' });
|
service.setSessionState('Genesis', { configId: 'configId1' });
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith('session_state', 'Genesis', {
|
expect(spy).toHaveBeenCalledOnceWith('session_state', 'Genesis', {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -129,7 +118,7 @@ describe('Flows Data Service', () => {
|
|||||||
|
|
||||||
describe('resetStorageFlowData', () => {
|
describe('resetStorageFlowData', () => {
|
||||||
it('resetStorageFlowData calls correct method on storagePersistenceService', () => {
|
it('resetStorageFlowData calls correct method on storagePersistenceService', () => {
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'resetStorageFlowData');
|
const spy = spyOn(storagePersistenceService, 'resetStorageFlowData');
|
||||||
|
|
||||||
service.resetStorageFlowData({ configId: 'configId1' });
|
service.resetStorageFlowData({ configId: 'configId1' });
|
||||||
|
|
||||||
@ -139,28 +128,26 @@ describe('Flows Data Service', () => {
|
|||||||
|
|
||||||
describe('codeVerifier', () => {
|
describe('codeVerifier', () => {
|
||||||
it('getCodeVerifier returns value from the store', () => {
|
it('getCodeVerifier returns value from the store', () => {
|
||||||
const spy = mockImplementationWhenArgsEqual(
|
const spy = spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('codeVerifier', { configId: 'configId1' })
|
||||||
['codeVerifier', { configId: 'configId1' }],
|
.and.returnValue('Genesis');
|
||||||
() => 'Genesis'
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = service.getCodeVerifier({ configId: 'configId1' });
|
const result = service.getCodeVerifier({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBe('Genesis');
|
expect(result).toBe('Genesis');
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith('codeVerifier', {
|
expect(spy).toHaveBeenCalledOnceWith('codeVerifier', {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createCodeVerifier returns random createCodeVerifier and stores it', () => {
|
it('createCodeVerifier returns random createCodeVerifier and stores it', () => {
|
||||||
const setSpy = vi.spyOn(storagePersistenceService, 'write');
|
const setSpy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
const result = service.createCodeVerifier({ configId: 'configId1' });
|
const result = service.createCodeVerifier({ configId: 'configId1' });
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
expect(result.length).toBe(67);
|
expect(result.length).toBe(67);
|
||||||
expect(setSpy).toHaveBeenCalledExactlyOnceWith('codeVerifier', result, {
|
expect(setSpy).toHaveBeenCalledOnceWith('codeVerifier', result, {
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -172,33 +159,28 @@ describe('Flows Data Service', () => {
|
|||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
};
|
};
|
||||||
|
|
||||||
vi.useRealTimers();
|
jasmine.clock().uninstall();
|
||||||
vi.useFakeTimers();
|
jasmine.clock().install();
|
||||||
|
|
||||||
const baseTime = new Date();
|
const baseTime = new Date();
|
||||||
|
|
||||||
vi.setSystemTime(baseTime);
|
jasmine.clock().mockDate(baseTime);
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('storageCodeFlowInProgress', config)
|
||||||
['storageCodeFlowInProgress', config],
|
.and.returnValue(true);
|
||||||
() => true
|
const spyWrite = spyOn(storagePersistenceService, 'write');
|
||||||
);
|
|
||||||
const spyWrite = vi.spyOn(storagePersistenceService, 'write');
|
|
||||||
|
|
||||||
const isCodeFlowInProgressResult = service.isCodeFlowInProgress(config);
|
const isCodeFlowInProgressResult = service.isCodeFlowInProgress(config);
|
||||||
|
|
||||||
expect(spyWrite).not.toHaveBeenCalled();
|
expect(spyWrite).not.toHaveBeenCalled();
|
||||||
expect(isCodeFlowInProgressResult).toBeTruthy();
|
expect(isCodeFlowInProgressResult).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('state object does not exist returns false result', () => {
|
it('state object does not exist returns false result', () => {
|
||||||
// arrange
|
// arrange
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('storageCodeFlowInProgress', { configId: 'configId1' })
|
||||||
['storageCodeFlowInProgress', { configId: 'configId1' }],
|
.and.returnValue(null);
|
||||||
() => null
|
|
||||||
);
|
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const isCodeFlowInProgressResult = service.isCodeFlowInProgress({
|
const isCodeFlowInProgressResult = service.isCodeFlowInProgress({
|
||||||
@ -206,83 +188,71 @@ describe('Flows Data Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
expect(isCodeFlowInProgressResult).toBeFalsy();
|
expect(isCodeFlowInProgressResult).toBeFalse();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setCodeFlowInProgress', () => {
|
describe('setCodeFlowInProgress', () => {
|
||||||
it('set setCodeFlowInProgress to `in progress` when called', () => {
|
it('set setCodeFlowInProgress to `in progress` when called', () => {
|
||||||
vi.useRealTimers();
|
jasmine.clock().uninstall();
|
||||||
vi.useFakeTimers();
|
jasmine.clock().install();
|
||||||
const baseTime = new Date();
|
const baseTime = new Date();
|
||||||
|
|
||||||
vi.setSystemTime(baseTime);
|
jasmine.clock().mockDate(baseTime);
|
||||||
|
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
const spy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
service.setCodeFlowInProgress({ configId: 'configId1' });
|
service.setCodeFlowInProgress({ configId: 'configId1' });
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
expect(spy).toHaveBeenCalledOnceWith('storageCodeFlowInProgress', true, {
|
||||||
'storageCodeFlowInProgress',
|
configId: 'configId1',
|
||||||
true,
|
});
|
||||||
{
|
|
||||||
configId: 'configId1',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('resetCodeFlowInProgress', () => {
|
describe('resetCodeFlowInProgress', () => {
|
||||||
it('set resetCodeFlowInProgress to false when called', () => {
|
it('set resetCodeFlowInProgress to false when called', () => {
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
const spy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
service.resetCodeFlowInProgress({ configId: 'configId1' });
|
service.resetCodeFlowInProgress({ configId: 'configId1' });
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
expect(spy).toHaveBeenCalledOnceWith('storageCodeFlowInProgress', false, {
|
||||||
'storageCodeFlowInProgress',
|
configId: 'configId1',
|
||||||
false,
|
});
|
||||||
{
|
|
||||||
configId: 'configId1',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isSilentRenewRunning', () => {
|
describe('isSilentRenewRunning', () => {
|
||||||
it('silent renew process timeout exceeded reset state object and returns false result', async () => {
|
it('silent renew process timeout exceeded reset state object and returns false result', () => {
|
||||||
const config = {
|
const config = {
|
||||||
silentRenewTimeoutInSeconds: 10,
|
silentRenewTimeoutInSeconds: 10,
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
};
|
};
|
||||||
|
|
||||||
vi.useRealTimers();
|
jasmine.clock().uninstall();
|
||||||
|
jasmine.clock().install();
|
||||||
const baseTime = new Date();
|
const baseTime = new Date();
|
||||||
vi.useFakeTimers();
|
|
||||||
|
|
||||||
vi.setSystemTime(baseTime);
|
jasmine.clock().mockDate(baseTime);
|
||||||
|
|
||||||
const storageObject = {
|
const storageObject = {
|
||||||
state: 'running',
|
state: 'running',
|
||||||
dateOfLaunchedProcessUtc: baseTime.toISOString(),
|
dateOfLaunchedProcessUtc: baseTime.toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('storageSilentRenewRunning', config)
|
||||||
['storageSilentRenewRunning', config],
|
.and.returnValue(JSON.stringify(storageObject));
|
||||||
() => JSON.stringify(storageObject)
|
const spyWrite = spyOn(storagePersistenceService, 'write');
|
||||||
);
|
|
||||||
const spyWrite = vi.spyOn(storagePersistenceService, 'write');
|
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(
|
jasmine.clock().tick((config.silentRenewTimeoutInSeconds + 1) * 1000);
|
||||||
(config.silentRenewTimeoutInSeconds + 1) * 1000
|
|
||||||
);
|
|
||||||
|
|
||||||
const isSilentRenewRunningResult = service.isSilentRenewRunning(config);
|
const isSilentRenewRunningResult = service.isSilentRenewRunning(config);
|
||||||
|
|
||||||
expect(spyWrite).toHaveBeenCalledExactlyOnceWith(
|
expect(spyWrite).toHaveBeenCalledOnceWith(
|
||||||
'storageSilentRenewRunning',
|
'storageSilentRenewRunning',
|
||||||
'',
|
'',
|
||||||
config
|
config
|
||||||
);
|
);
|
||||||
expect(isSilentRenewRunningResult).toBeFalsy();
|
expect(isSilentRenewRunningResult).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks silent renew process and returns result', () => {
|
it('checks silent renew process and returns result', () => {
|
||||||
@ -291,62 +261,58 @@ describe('Flows Data Service', () => {
|
|||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
};
|
};
|
||||||
|
|
||||||
vi.useRealTimers();
|
jasmine.clock().uninstall();
|
||||||
vi.useFakeTimers();
|
jasmine.clock().install();
|
||||||
const baseTime = new Date();
|
const baseTime = new Date();
|
||||||
|
|
||||||
vi.setSystemTime(baseTime);
|
jasmine.clock().mockDate(baseTime);
|
||||||
|
|
||||||
const storageObject = {
|
const storageObject = {
|
||||||
state: 'running',
|
state: 'running',
|
||||||
dateOfLaunchedProcessUtc: baseTime.toISOString(),
|
dateOfLaunchedProcessUtc: baseTime.toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('storageSilentRenewRunning', config)
|
||||||
['storageSilentRenewRunning', config],
|
.and.returnValue(JSON.stringify(storageObject));
|
||||||
() => JSON.stringify(storageObject)
|
const spyWrite = spyOn(storagePersistenceService, 'write');
|
||||||
);
|
|
||||||
const spyWrite = vi.spyOn(storagePersistenceService, 'write');
|
|
||||||
|
|
||||||
const isSilentRenewRunningResult = service.isSilentRenewRunning(config);
|
const isSilentRenewRunningResult = service.isSilentRenewRunning(config);
|
||||||
|
|
||||||
expect(spyWrite).not.toHaveBeenCalled();
|
expect(spyWrite).not.toHaveBeenCalled();
|
||||||
expect(isSilentRenewRunningResult).toBeTruthy();
|
expect(isSilentRenewRunningResult).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('state object does not exist returns false result', () => {
|
it('state object does not exist returns false result', () => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('storageSilentRenewRunning', { configId: 'configId1' })
|
||||||
['storageSilentRenewRunning', { configId: 'configId1' }],
|
.and.returnValue(null);
|
||||||
() => null
|
|
||||||
);
|
|
||||||
|
|
||||||
const isSilentRenewRunningResult = service.isSilentRenewRunning({
|
const isSilentRenewRunningResult = service.isSilentRenewRunning({
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(isSilentRenewRunningResult).toBeFalsy();
|
expect(isSilentRenewRunningResult).toBeFalse();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setSilentRenewRunning', () => {
|
describe('setSilentRenewRunning', () => {
|
||||||
it('set setSilentRenewRunning to `running` with lauched time when called', () => {
|
it('set setSilentRenewRunning to `running` with lauched time when called', () => {
|
||||||
vi.useRealTimers();
|
jasmine.clock().uninstall();
|
||||||
vi.useFakeTimers();
|
jasmine.clock().install();
|
||||||
const baseTime = new Date();
|
const baseTime = new Date();
|
||||||
|
|
||||||
vi.setSystemTime(baseTime);
|
jasmine.clock().mockDate(baseTime);
|
||||||
|
|
||||||
const storageObject = {
|
const storageObject = {
|
||||||
state: 'running',
|
state: 'running',
|
||||||
dateOfLaunchedProcessUtc: baseTime.toISOString(),
|
dateOfLaunchedProcessUtc: baseTime.toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
const spy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
service.setSilentRenewRunning({ configId: 'configId1' });
|
service.setSilentRenewRunning({ configId: 'configId1' });
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
expect(spy).toHaveBeenCalledOnceWith(
|
||||||
'storageSilentRenewRunning',
|
'storageSilentRenewRunning',
|
||||||
JSON.stringify(storageObject),
|
JSON.stringify(storageObject),
|
||||||
{ configId: 'configId1' }
|
{ configId: 'configId1' }
|
||||||
@ -356,16 +322,12 @@ describe('Flows Data Service', () => {
|
|||||||
|
|
||||||
describe('resetSilentRenewRunning', () => {
|
describe('resetSilentRenewRunning', () => {
|
||||||
it('set resetSilentRenewRunning to empty string when called', () => {
|
it('set resetSilentRenewRunning to empty string when called', () => {
|
||||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
const spy = spyOn(storagePersistenceService, 'write');
|
||||||
|
|
||||||
service.resetSilentRenewRunning({ configId: 'configId1' });
|
service.resetSilentRenewRunning({ configId: 'configId1' });
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
expect(spy).toHaveBeenCalledOnceWith('storageSilentRenewRunning', '', {
|
||||||
'storageSilentRenewRunning',
|
configId: 'configId1',
|
||||||
'',
|
});
|
||||||
{
|
|
||||||
configId: 'configId1',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import type { SilentRenewRunning } from './flows.models';
|
import { SilentRenewRunning } from './flows.models';
|
||||||
import { RandomService } from './random/random.service';
|
import { RandomService } from './random/random.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -18,7 +18,7 @@ export class FlowsDataService {
|
|||||||
createNonce(configuration: OpenIdConfiguration): string {
|
createNonce(configuration: OpenIdConfiguration): string {
|
||||||
const nonce = this.randomService.createRandom(40, configuration);
|
const nonce = this.randomService.createRandom(40, configuration);
|
||||||
|
|
||||||
this.loggerService.logDebug(configuration, `Nonce created. nonce:${nonce}`);
|
this.loggerService.logDebug(configuration, 'Nonce created. nonce:' + nonce);
|
||||||
this.setNonce(nonce, configuration);
|
this.setNonce(nonce, configuration);
|
||||||
|
|
||||||
return nonce;
|
return nonce;
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { firstValueFrom, of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
import { mockProvider } from '../testing/mock';
|
import { CallbackContext } from './callback-context';
|
||||||
import type { CallbackContext } from './callback-context';
|
|
||||||
import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service';
|
import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service';
|
||||||
import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service';
|
import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service';
|
||||||
import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service';
|
import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service';
|
||||||
@ -35,6 +34,9 @@ describe('Flows Service', () => {
|
|||||||
mockProvider(RefreshTokenCallbackHandlerService),
|
mockProvider(RefreshTokenCallbackHandlerService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(FlowsService);
|
service = TestBed.inject(FlowsService);
|
||||||
codeFlowCallbackHandlerService = TestBed.inject(
|
codeFlowCallbackHandlerService = TestBed.inject(
|
||||||
CodeFlowCallbackHandlerService
|
CodeFlowCallbackHandlerService
|
||||||
@ -62,164 +64,163 @@ describe('Flows Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('processCodeFlowCallback', () => {
|
describe('processCodeFlowCallback', () => {
|
||||||
it('calls all methods correctly', async () => {
|
it('calls all methods correctly', waitForAsync(() => {
|
||||||
const codeFlowCallbackSpy = vi
|
const codeFlowCallbackSpy = spyOn(
|
||||||
.spyOn(codeFlowCallbackHandlerService, 'codeFlowCallback')
|
codeFlowCallbackHandlerService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'codeFlowCallback'
|
||||||
const codeFlowCodeRequestSpy = vi
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.spyOn(codeFlowCallbackHandlerService, 'codeFlowCodeRequest')
|
const codeFlowCodeRequestSpy = spyOn(
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
codeFlowCallbackHandlerService,
|
||||||
const callbackHistoryAndResetJwtKeysSpy = vi
|
'codeFlowCodeRequest'
|
||||||
.spyOn(
|
).and.returnValue(of({} as CallbackContext));
|
||||||
historyJwtKeysCallbackHandlerService,
|
const callbackHistoryAndResetJwtKeysSpy = spyOn(
|
||||||
'callbackHistoryAndResetJwtKeys'
|
historyJwtKeysCallbackHandlerService,
|
||||||
)
|
'callbackHistoryAndResetJwtKeys'
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
).and.returnValue(of({} as CallbackContext));
|
||||||
const callbackStateValidationSpy = vi
|
const callbackStateValidationSpy = spyOn(
|
||||||
.spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation')
|
stateValidationCallbackHandlerService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'callbackStateValidation'
|
||||||
const callbackUserSpy = vi
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.spyOn(userCallbackHandlerService, 'callbackUser')
|
const callbackUserSpy = spyOn(
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
userCallbackHandlerService,
|
||||||
|
'callbackUser'
|
||||||
|
).and.returnValue(of({} as CallbackContext));
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const value = await firstValueFrom(
|
service
|
||||||
service.processCodeFlowCallback(
|
.processCodeFlowCallback('some-url1234', allConfigs[0], allConfigs)
|
||||||
'some-url1234',
|
.subscribe((value) => {
|
||||||
allConfigs[0]!,
|
expect(value).toEqual({} as CallbackContext);
|
||||||
allConfigs
|
expect(codeFlowCallbackSpy).toHaveBeenCalledOnceWith(
|
||||||
)
|
'some-url1234',
|
||||||
);
|
allConfigs[0]
|
||||||
expect(value).toEqual({} as CallbackContext);
|
);
|
||||||
expect(codeFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith(
|
expect(codeFlowCodeRequestSpy).toHaveBeenCalledTimes(1);
|
||||||
'some-url1234',
|
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalledTimes(1);
|
||||||
allConfigs[0]
|
expect(callbackStateValidationSpy).toHaveBeenCalledTimes(1);
|
||||||
);
|
expect(callbackUserSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(codeFlowCodeRequestSpy).toHaveBeenCalledTimes(1);
|
});
|
||||||
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalledTimes(1);
|
}));
|
||||||
expect(callbackStateValidationSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(callbackUserSpy).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('processSilentRenewCodeFlowCallback', () => {
|
describe('processSilentRenewCodeFlowCallback', () => {
|
||||||
it('calls all methods correctly', async () => {
|
it('calls all methods correctly', waitForAsync(() => {
|
||||||
const codeFlowCodeRequestSpy = vi
|
const codeFlowCodeRequestSpy = spyOn(
|
||||||
.spyOn(codeFlowCallbackHandlerService, 'codeFlowCodeRequest')
|
codeFlowCallbackHandlerService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'codeFlowCodeRequest'
|
||||||
const callbackHistoryAndResetJwtKeysSpy = vi
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.spyOn(
|
const callbackHistoryAndResetJwtKeysSpy = spyOn(
|
||||||
historyJwtKeysCallbackHandlerService,
|
historyJwtKeysCallbackHandlerService,
|
||||||
'callbackHistoryAndResetJwtKeys'
|
'callbackHistoryAndResetJwtKeys'
|
||||||
)
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
const callbackStateValidationSpy = spyOn(
|
||||||
const callbackStateValidationSpy = vi
|
stateValidationCallbackHandlerService,
|
||||||
.spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation')
|
'callbackStateValidation'
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
).and.returnValue(of({} as CallbackContext));
|
||||||
const callbackUserSpy = vi
|
const callbackUserSpy = spyOn(
|
||||||
.spyOn(userCallbackHandlerService, 'callbackUser')
|
userCallbackHandlerService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'callbackUser'
|
||||||
|
).and.returnValue(of({} as CallbackContext));
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const value = await firstValueFrom(
|
service
|
||||||
service.processSilentRenewCodeFlowCallback(
|
.processSilentRenewCodeFlowCallback(
|
||||||
{} as CallbackContext,
|
{} as CallbackContext,
|
||||||
allConfigs[0]!,
|
allConfigs[0],
|
||||||
allConfigs
|
allConfigs
|
||||||
)
|
)
|
||||||
);
|
.subscribe((value) => {
|
||||||
expect(value).toEqual({} as CallbackContext);
|
expect(value).toEqual({} as CallbackContext);
|
||||||
expect(codeFlowCodeRequestSpy).toHaveBeenCalled();
|
expect(codeFlowCodeRequestSpy).toHaveBeenCalled();
|
||||||
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();
|
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();
|
||||||
expect(callbackStateValidationSpy).toHaveBeenCalled();
|
expect(callbackStateValidationSpy).toHaveBeenCalled();
|
||||||
expect(callbackUserSpy).toHaveBeenCalled();
|
expect(callbackUserSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('processImplicitFlowCallback', () => {
|
describe('processImplicitFlowCallback', () => {
|
||||||
it('calls all methods correctly', async () => {
|
it('calls all methods correctly', waitForAsync(() => {
|
||||||
const implicitFlowCallbackSpy = vi
|
const implicitFlowCallbackSpy = spyOn(
|
||||||
.spyOn(implicitFlowCallbackHandlerService, 'implicitFlowCallback')
|
implicitFlowCallbackHandlerService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'implicitFlowCallback'
|
||||||
const callbackHistoryAndResetJwtKeysSpy = vi
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.spyOn(
|
const callbackHistoryAndResetJwtKeysSpy = spyOn(
|
||||||
historyJwtKeysCallbackHandlerService,
|
historyJwtKeysCallbackHandlerService,
|
||||||
'callbackHistoryAndResetJwtKeys'
|
'callbackHistoryAndResetJwtKeys'
|
||||||
)
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
const callbackStateValidationSpy = spyOn(
|
||||||
const callbackStateValidationSpy = vi
|
stateValidationCallbackHandlerService,
|
||||||
.spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation')
|
'callbackStateValidation'
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
).and.returnValue(of({} as CallbackContext));
|
||||||
const callbackUserSpy = vi
|
const callbackUserSpy = spyOn(
|
||||||
.spyOn(userCallbackHandlerService, 'callbackUser')
|
userCallbackHandlerService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'callbackUser'
|
||||||
|
).and.returnValue(of({} as CallbackContext));
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const value = await firstValueFrom(
|
service
|
||||||
service.processImplicitFlowCallback(
|
.processImplicitFlowCallback(allConfigs[0], allConfigs, 'any-hash')
|
||||||
allConfigs[0]!,
|
.subscribe((value) => {
|
||||||
allConfigs,
|
expect(value).toEqual({} as CallbackContext);
|
||||||
'any-hash'
|
expect(implicitFlowCallbackSpy).toHaveBeenCalled();
|
||||||
)
|
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();
|
||||||
);
|
expect(callbackStateValidationSpy).toHaveBeenCalled();
|
||||||
expect(value).toEqual({} as CallbackContext);
|
expect(callbackUserSpy).toHaveBeenCalled();
|
||||||
expect(implicitFlowCallbackSpy).toHaveBeenCalled();
|
});
|
||||||
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();
|
}));
|
||||||
expect(callbackStateValidationSpy).toHaveBeenCalled();
|
|
||||||
expect(callbackUserSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('processRefreshToken', () => {
|
describe('processRefreshToken', () => {
|
||||||
it('calls all methods correctly', async () => {
|
it('calls all methods correctly', waitForAsync(() => {
|
||||||
const refreshSessionWithRefreshTokensSpy = vi
|
const refreshSessionWithRefreshTokensSpy = spyOn(
|
||||||
.spyOn(
|
refreshSessionCallbackHandlerService,
|
||||||
refreshSessionCallbackHandlerService,
|
'refreshSessionWithRefreshTokens'
|
||||||
'refreshSessionWithRefreshTokens'
|
).and.returnValue(of({} as CallbackContext));
|
||||||
)
|
const refreshTokensRequestTokensSpy = spyOn(
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
refreshTokenCallbackHandlerService,
|
||||||
const refreshTokensRequestTokensSpy = vi
|
'refreshTokensRequestTokens'
|
||||||
.spyOn(refreshTokenCallbackHandlerService, 'refreshTokensRequestTokens')
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
const callbackHistoryAndResetJwtKeysSpy = spyOn(
|
||||||
const callbackHistoryAndResetJwtKeysSpy = vi
|
historyJwtKeysCallbackHandlerService,
|
||||||
.spyOn(
|
'callbackHistoryAndResetJwtKeys'
|
||||||
historyJwtKeysCallbackHandlerService,
|
).and.returnValue(of({} as CallbackContext));
|
||||||
'callbackHistoryAndResetJwtKeys'
|
const callbackStateValidationSpy = spyOn(
|
||||||
)
|
stateValidationCallbackHandlerService,
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
'callbackStateValidation'
|
||||||
const callbackStateValidationSpy = vi
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation')
|
const callbackUserSpy = spyOn(
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
userCallbackHandlerService,
|
||||||
const callbackUserSpy = vi
|
'callbackUser'
|
||||||
.spyOn(userCallbackHandlerService, 'callbackUser')
|
).and.returnValue(of({} as CallbackContext));
|
||||||
.mockReturnValue(of({} as CallbackContext));
|
|
||||||
const allConfigs = [
|
const allConfigs = [
|
||||||
{
|
{
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const value = await firstValueFrom(
|
service
|
||||||
service.processRefreshToken(allConfigs[0]!, allConfigs)
|
.processRefreshToken(allConfigs[0], allConfigs)
|
||||||
);
|
.subscribe((value) => {
|
||||||
expect(value).toEqual({} as CallbackContext);
|
expect(value).toEqual({} as CallbackContext);
|
||||||
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
|
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
|
||||||
expect(refreshTokensRequestTokensSpy).toHaveBeenCalled();
|
expect(refreshTokensRequestTokensSpy).toHaveBeenCalled();
|
||||||
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();
|
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();
|
||||||
expect(callbackStateValidationSpy).toHaveBeenCalled();
|
expect(callbackStateValidationSpy).toHaveBeenCalled();
|
||||||
expect(callbackUserSpy).toHaveBeenCalled();
|
expect(callbackUserSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import type { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { concatMap } from 'rxjs/operators';
|
import { concatMap } from 'rxjs/operators';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import type { CallbackContext } from './callback-context';
|
import { CallbackContext } from './callback-context';
|
||||||
import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service';
|
import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service';
|
||||||
import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service';
|
import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service';
|
||||||
import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service';
|
import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { mockProvider } from '../../../test/auto-mock';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { mockProvider } from '../../testing/mock';
|
|
||||||
import { CryptoService } from '../../utils/crypto/crypto.service';
|
import { CryptoService } from '../../utils/crypto/crypto.service';
|
||||||
import { RandomService } from './random.service';
|
import { RandomService } from './random.service';
|
||||||
|
|
||||||
@ -11,6 +11,9 @@ describe('RandomService Tests', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [RandomService, mockProvider(LoggerService), CryptoService],
|
providers: [RandomService, mockProvider(LoggerService), CryptoService],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
randomService = TestBed.inject(RandomService);
|
randomService = TestBed.inject(RandomService);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import type { OpenIdConfiguration } from '../../config/openid-configuration';
|
import { OpenIdConfiguration } from '../../config/openid-configuration';
|
||||||
import { LoggerService } from '../../logging/logger.service';
|
import { LoggerService } from '../../logging/logger.service';
|
||||||
import { CryptoService } from '../../utils/crypto/crypto.service';
|
import { CryptoService } from '../../utils/crypto/crypto.service';
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ export class RandomService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private toHex(dec: number): string {
|
private toHex(dec: number): string {
|
||||||
return `0${dec.toString(16)}`.substr(-2);
|
return ('0' + dec.toString(16)).substr(-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private randomString(length: number): string {
|
private randomString(length: number): string {
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { TestBed } from '@/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { UserService } from '../user-data/user.service';
|
import { UserService } from '../user-data/user.service';
|
||||||
import { FlowsDataService } from './flows-data.service';
|
import { FlowsDataService } from './flows-data.service';
|
||||||
import { ResetAuthDataService } from './reset-auth-data.service';
|
import { ResetAuthDataService } from './reset-auth-data.service';
|
||||||
@ -23,6 +22,9 @@ describe('ResetAuthDataService', () => {
|
|||||||
mockProvider(LoggerService),
|
mockProvider(LoggerService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(ResetAuthDataService);
|
service = TestBed.inject(ResetAuthDataService);
|
||||||
userService = TestBed.inject(UserService);
|
userService = TestBed.inject(UserService);
|
||||||
flowsDataService = TestBed.inject(FlowsDataService);
|
flowsDataService = TestBed.inject(FlowsDataService);
|
||||||
@ -35,7 +37,7 @@ describe('ResetAuthDataService', () => {
|
|||||||
|
|
||||||
describe('resetAuthorizationData', () => {
|
describe('resetAuthorizationData', () => {
|
||||||
it('calls resetUserDataInStore when autoUserInfo is true', () => {
|
it('calls resetUserDataInStore when autoUserInfo is true', () => {
|
||||||
const resetUserDataInStoreSpy = vi.spyOn(
|
const resetUserDataInStoreSpy = spyOn(
|
||||||
userService,
|
userService,
|
||||||
'resetUserDataInStore'
|
'resetUserDataInStore'
|
||||||
);
|
);
|
||||||
@ -45,16 +47,16 @@ describe('ResetAuthDataService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
service.resetAuthorizationData(allConfigs[0]!, allConfigs);
|
service.resetAuthorizationData(allConfigs[0], allConfigs);
|
||||||
expect(resetUserDataInStoreSpy).toHaveBeenCalled();
|
expect(resetUserDataInStoreSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls correct methods', () => {
|
it('calls correct methods', () => {
|
||||||
const resetStorageFlowDataSpy = vi.spyOn(
|
const resetStorageFlowDataSpy = spyOn(
|
||||||
flowsDataService,
|
flowsDataService,
|
||||||
'resetStorageFlowData'
|
'resetStorageFlowData'
|
||||||
);
|
);
|
||||||
const setUnauthorizedAndFireEventSpy = vi.spyOn(
|
const setUnauthorizedAndFireEventSpy = spyOn(
|
||||||
authStateService,
|
authStateService,
|
||||||
'setUnauthenticatedAndFireEvent'
|
'setUnauthenticatedAndFireEvent'
|
||||||
);
|
);
|
||||||
@ -64,7 +66,7 @@ describe('ResetAuthDataService', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
service.resetAuthorizationData(allConfigs[0]!, allConfigs);
|
service.resetAuthorizationData(allConfigs[0], allConfigs);
|
||||||
|
|
||||||
expect(resetStorageFlowDataSpy).toHaveBeenCalled();
|
expect(resetStorageFlowDataSpy).toHaveBeenCalled();
|
||||||
expect(setUnauthorizedAndFireEventSpy).toHaveBeenCalled();
|
expect(setUnauthorizedAndFireEventSpy).toHaveBeenCalled();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Injectable, inject } from 'injection-js';
|
import { inject, Injectable } from 'injection-js';
|
||||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { UserService } from '../user-data/user.service';
|
import { UserService } from '../user-data/user.service';
|
||||||
import { FlowsDataService } from './flows-data.service';
|
import { FlowsDataService } from './flows-data.service';
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
import { HttpResponse } from '@angular/common/http';
|
||||||
import { HttpResponse } from '@ngify/http';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { EmptyError, firstValueFrom, isObservable, of, throwError } from 'rxjs';
|
import { isObservable, of, throwError } from 'rxjs';
|
||||||
import { vi } from 'vitest';
|
import { mockProvider } from '../../test/auto-mock';
|
||||||
|
import { createRetriableStream } from '../../test/create-retriable-stream.helper';
|
||||||
import { DataService } from '../api/data.service';
|
import { DataService } from '../api/data.service';
|
||||||
import { LoggerService } from '../logging/logger.service';
|
import { LoggerService } from '../logging/logger.service';
|
||||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||||
import { createRetriableStream } from '../testing/create-retriable-stream.helper';
|
|
||||||
import { mockProvider } from '../testing/mock';
|
|
||||||
import { SigninKeyDataService } from './signin-key-data.service';
|
import { SigninKeyDataService } from './signin-key-data.service';
|
||||||
|
|
||||||
const DUMMY_JWKS = {
|
const DUMMY_JWKS = {
|
||||||
@ -40,6 +39,9 @@ describe('Signin Key Data Service', () => {
|
|||||||
mockProvider(StoragePersistenceService),
|
mockProvider(StoragePersistenceService),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
service = TestBed.inject(SigninKeyDataService);
|
service = TestBed.inject(SigninKeyDataService);
|
||||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||||
dataService = TestBed.inject(DataService);
|
dataService = TestBed.inject(DataService);
|
||||||
@ -51,84 +53,73 @@ describe('Signin Key Data Service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getSigningKeys', () => {
|
describe('getSigningKeys', () => {
|
||||||
it('throws error when no wellKnownEndpoints given', async () => {
|
it('throws error when no wellKnownEndpoints given', waitForAsync(() => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue(null);
|
||||||
() => null
|
|
||||||
);
|
|
||||||
const result = service.getSigningKeys({ configId: 'configId1' });
|
const result = service.getSigningKeys({ configId: 'configId1' });
|
||||||
|
|
||||||
try {
|
result.subscribe({
|
||||||
await firstValueFrom(result);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err).toBeTruthy();
|
||||||
expect(err).toBeTruthy();
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('throws error when no jwksUri given', async () => {
|
it('throws error when no jwksUri given', waitForAsync(() => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue({ jwksUri: null });
|
||||||
() => ({ jwksUri: null })
|
|
||||||
);
|
|
||||||
const result = service.getSigningKeys({ configId: 'configId1' });
|
const result = service.getSigningKeys({ configId: 'configId1' });
|
||||||
|
|
||||||
try {
|
result.subscribe({
|
||||||
await firstValueFrom(result);
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err).toBeTruthy();
|
||||||
expect(err).toBeTruthy();
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('calls dataservice if jwksurl is given', async () => {
|
it('calls dataservice if jwksurl is given', waitForAsync(() => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue({ jwksUri: 'someUrl' });
|
||||||
() => ({ jwksUri: 'someUrl' })
|
const spy = spyOn(dataService, 'get').and.callFake(() => of());
|
||||||
);
|
|
||||||
const spy = vi.spyOn(dataService, 'get').mockImplementation(() => of());
|
|
||||||
|
|
||||||
const result = service.getSigningKeys({ configId: 'configId1' });
|
const result = service.getSigningKeys({ configId: 'configId1' });
|
||||||
|
|
||||||
try {
|
result.subscribe({
|
||||||
await firstValueFrom(result);
|
complete: () => {
|
||||||
} catch (err: any) {
|
expect(spy).toHaveBeenCalledOnceWith('someUrl', {
|
||||||
if (err instanceof EmptyError) {
|
|
||||||
expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl', {
|
|
||||||
configId: 'configId1',
|
configId: 'configId1',
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('should retry once', async () => {
|
it('should retry once', waitForAsync(() => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue({ jwksUri: 'someUrl' });
|
||||||
() => ({ jwksUri: 'someUrl' })
|
spyOn(dataService, 'get').and.returnValue(
|
||||||
);
|
|
||||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => new Error('Error')),
|
throwError(() => new Error('Error')),
|
||||||
of(DUMMY_JWKS)
|
of(DUMMY_JWKS)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const res = await firstValueFrom(
|
service.getSigningKeys({ configId: 'configId1' }).subscribe({
|
||||||
service.getSigningKeys({ configId: 'configId1' })
|
next: (res) => {
|
||||||
);
|
expect(res).toBeTruthy();
|
||||||
expect(res).toBeTruthy();
|
expect(res).toEqual(DUMMY_JWKS);
|
||||||
expect(res).toEqual(DUMMY_JWKS);
|
},
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should retry twice', async () => {
|
it('should retry twice', waitForAsync(() => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue({ jwksUri: 'someUrl' });
|
||||||
() => ({ jwksUri: 'someUrl' })
|
spyOn(dataService, 'get').and.returnValue(
|
||||||
);
|
|
||||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => new Error('Error')),
|
throwError(() => new Error('Error')),
|
||||||
throwError(() => new Error('Error')),
|
throwError(() => new Error('Error')),
|
||||||
@ -136,20 +127,19 @@ describe('Signin Key Data Service', () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const res = await firstValueFrom(
|
service.getSigningKeys({ configId: 'configId1' }).subscribe({
|
||||||
service.getSigningKeys({ configId: 'configId1' })
|
next: (res) => {
|
||||||
);
|
expect(res).toBeTruthy();
|
||||||
expect(res).toBeTruthy();
|
expect(res).toEqual(DUMMY_JWKS);
|
||||||
expect(res).toEqual(DUMMY_JWKS);
|
},
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should fail after three tries', async () => {
|
it('should fail after three tries', waitForAsync(() => {
|
||||||
mockImplementationWhenArgsEqual(
|
spyOn(storagePersistenceService, 'read')
|
||||||
vi.spyOn(storagePersistenceService, 'read'),
|
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
.and.returnValue({ jwksUri: 'someUrl' });
|
||||||
() => ({ jwksUri: 'someUrl' })
|
spyOn(dataService, 'get').and.returnValue(
|
||||||
);
|
|
||||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
|
||||||
createRetriableStream(
|
createRetriableStream(
|
||||||
throwError(() => new Error('Error')),
|
throwError(() => new Error('Error')),
|
||||||
throwError(() => new Error('Error')),
|
throwError(() => new Error('Error')),
|
||||||
@ -158,75 +148,73 @@ describe('Signin Key Data Service', () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
service.getSigningKeys({ configId: 'configId1' }).subscribe({
|
||||||
await firstValueFrom(service.getSigningKeys({ configId: 'configId1' }));
|
error: (err) => {
|
||||||
} catch (err: any) {
|
expect(err).toBeTruthy();
|
||||||
expect(err).toBeTruthy();
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handleErrorGetSigningKeys', () => {
|
describe('handleErrorGetSigningKeys', () => {
|
||||||
it('keeps observable if error is catched', () => {
|
it('keeps observable if error is catched', waitForAsync(() => {
|
||||||
const result = (service as any).handleErrorGetSigningKeys(
|
const result = (service as any).handleErrorGetSigningKeys(
|
||||||
new HttpResponse()
|
new HttpResponse()
|
||||||
);
|
);
|
||||||
const hasTypeObservable = isObservable(result);
|
const hasTypeObservable = isObservable(result);
|
||||||
|
|
||||||
expect(hasTypeObservable).toBeTruthy();
|
expect(hasTypeObservable).toBeTrue();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('logs error if error is response', async () => {
|
it('logs error if error is response', waitForAsync(() => {
|
||||||
const logSpy = vi.spyOn(loggerService, 'logError');
|
const logSpy = spyOn(loggerService, 'logError');
|
||||||
|
|
||||||
try {
|
(service as any)
|
||||||
await firstValueFrom(
|
.handleErrorGetSigningKeys(
|
||||||
(service as any).handleErrorGetSigningKeys(
|
new HttpResponse({ status: 400, statusText: 'nono' }),
|
||||||
new HttpResponse({ status: 400, statusText: 'nono' }),
|
{ configId: 'configId1' }
|
||||||
{ configId: 'configId1' }
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
error: () => {
|
||||||
} catch {
|
expect(logSpy).toHaveBeenCalledOnceWith(
|
||||||
expect(logSpy).toHaveBeenCalledExactlyOnceWith(
|
{ configId: 'configId1' },
|
||||||
{ configId: 'configId1' },
|
'400 - nono {}'
|
||||||
'400 - nono {}'
|
);
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('logs error if error is not a response', async () => {
|
it('logs error if error is not a response', waitForAsync(() => {
|
||||||
const logSpy = vi.spyOn(loggerService, 'logError');
|
const logSpy = spyOn(loggerService, 'logError');
|
||||||
|
|
||||||
try {
|
(service as any)
|
||||||
await firstValueFrom(
|
.handleErrorGetSigningKeys('Just some Error', { configId: 'configId1' })
|
||||||
(service as any).handleErrorGetSigningKeys('Just some Error', {
|
.subscribe({
|
||||||
configId: 'configId1',
|
error: () => {
|
||||||
})
|
expect(logSpy).toHaveBeenCalledOnceWith(
|
||||||
);
|
{ configId: 'configId1' },
|
||||||
} catch {
|
'Just some Error'
|
||||||
expect(logSpy).toHaveBeenCalledExactlyOnceWith(
|
);
|
||||||
{ configId: 'configId1' },
|
},
|
||||||
'Just some Error'
|
});
|
||||||
);
|
}));
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('logs error if error with message property is not a response', async () => {
|
it('logs error if error with message property is not a response', waitForAsync(() => {
|
||||||
const logSpy = vi.spyOn(loggerService, 'logError');
|
const logSpy = spyOn(loggerService, 'logError');
|
||||||
|
|
||||||
try {
|
(service as any)
|
||||||
await firstValueFrom(
|
.handleErrorGetSigningKeys(
|
||||||
(service as any).handleErrorGetSigningKeys(
|
{ message: 'Just some Error' },
|
||||||
{ message: 'Just some Error' },
|
{ configId: 'configId1' }
|
||||||
{ configId: 'configId1' }
|
)
|
||||||
)
|
.subscribe({
|
||||||
);
|
error: () => {
|
||||||
} catch {
|
expect(logSpy).toHaveBeenCalledOnceWith(
|
||||||
expect(logSpy).toHaveBeenCalledExactlyOnceWith(
|
{ configId: 'configId1' },
|
||||||
{ configId: 'configId1' },
|
'Just some Error'
|
||||||
'Just some Error'
|
);
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user