From 1785df25e2e6b7fb4d5270efc3cd96d730e22bcc Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Thu, 30 Jan 2025 20:02:28 +0800 Subject: [PATCH] feat: init --- README.md | 4 +- biome.json => biome.jsonc | 14 +- karma.conf.js | 50 - package-lock.json | 3006 +++++++++++++++++ package.json | 15 +- pnpm-lock.yaml | 41 +- src/api/data.service.spec.ts | 73 +- src/auth-config.spec.ts | 2 +- src/auth-config.ts | 6 +- src/auth-state/auth-state.service.spec.ts | 334 +- src/auth-state/auth-state.service.ts | 16 +- src/auth-state/check-auth.service.spec.ts | 565 ++-- src/auth-state/check-auth.service.ts | 8 +- src/auth.module.spec.ts | 19 +- src/auth.module.ts | 56 +- .../auto-login-partial-routes.guard.spec.ts | 276 +- .../auto-login-partial-routes.guard.ts | 23 +- src/auto-login/auto-login.service.spec.ts | 45 +- src/callback/callback.service.spec.ts | 164 +- src/callback/callback.service.ts | 6 +- .../code-flow-callback.service.spec.ts | 109 +- src/callback/code-flow-callback.service.ts | 8 +- .../implicit-flow-callback.service.spec.ts | 101 +- .../implicit-flow-callback.service.ts | 8 +- src/callback/interval.service.spec.ts | 11 +- src/callback/interval.service.ts | 23 +- .../periodically-token-check.service.spec.ts | 271 +- .../periodically-token-check.service.ts | 77 +- ...resh-session-refresh-token.service.spec.ts | 38 +- .../refresh-session-refresh-token.service.ts | 8 +- src/callback/refresh-session.service.spec.ts | 355 +- src/callback/refresh-session.service.ts | 12 +- .../auth-well-known-data.service.spec.ts | 134 +- .../auth-well-known.service.spec.ts | 62 +- src/config/config.service.spec.ts | 136 +- src/config/config.service.ts | 22 +- src/config/loader/config-loader.spec.ts | 34 +- src/config/loader/config-loader.ts | 8 +- src/config/openid-configuration.ts | 6 +- .../config-validation.service.spec.ts | 67 +- src/extractors/jwk.extractor.spec.ts | 3 +- ...code-flow-callback-handler.service.spec.ts | 203 +- .../callback-handling/error-helper.spec.ts | 16 +- ...-jwt-keys-callback-handler.service.spec.ts | 257 +- ...story-jwt-keys-callback-handler.service.ts | 12 +- ...icit-flow-callback-handler.service.spec.ts | 54 +- .../implicit-flow-callback-handler.service.ts | 8 +- ...h-session-callback-handler.service.spec.ts | 31 +- ...esh-token-callback-handler.service.spec.ts | 95 +- ...alidation-callback-handler.service.spec.ts | 58 +- ...ate-validation-callback-handler.service.ts | 12 +- .../user-callback-handler.service.spec.ts | 118 +- .../user-callback-handler.service.ts | 10 +- src/flows/flows-data.service.spec.ts | 162 +- src/flows/flows-data.service.ts | 8 +- src/flows/flows.service.spec.ts | 174 +- src/flows/flows.service.ts | 8 +- src/flows/random/random.service.spec.ts | 5 +- src/flows/reset-auth-data.service.spec.ts | 15 +- src/flows/reset-auth-data.service.ts | 4 +- src/flows/signin-key-data.service.spec.ts | 121 +- src/http/index.ts | 11 + src/iframe/check-session.service.spec.ts | 152 +- .../refresh-session-iframe.service.spec.ts | 39 +- src/iframe/refresh-session-iframe.service.ts | 4 +- src/iframe/silent-renew.service.spec.ts | 195 +- src/iframe/silent-renew.service.ts | 8 +- src/index.ts | 3 +- src/injection/convention.ts | 7 + src/injection/index.ts | 3 + src/injection/inject.ts | 10 + src/injection/module.ts | 4 + src/interceptor/auth.interceptor.spec.ts | 109 +- .../closest-matching-route.service.spec.ts | 5 +- src/logging/logger.service.spec.ts | 73 +- src/logging/logger.service.ts | 9 +- src/login/login.service.spec.ts | 85 +- src/login/login.service.ts | 19 +- src/login/par/par-login.service.spec.ts | 285 +- src/login/par/par-login.service.ts | 16 +- src/login/par/par.service.spec.ts | 145 +- src/login/popup/popup-login.service.spec.ts | 125 +- src/login/popup/popup-login.service.ts | 14 +- src/login/popup/popup.service.spec.ts | 128 +- .../response-type-validation.service.spec.ts | 21 +- .../standard/standard-login.service.spec.ts | 160 +- .../logoff-revocation.service.spec.ts | 420 +-- .../logoff-revocation.service.ts | 8 +- src/oidc.security.service.spec.ts | 602 ++-- src/oidc.security.service.ts | 65 +- src/provide-auth.spec.ts | 35 +- src/provide-auth.ts | 16 +- src/public-events/event-types.ts | 25 +- .../public-events.service.spec.ts | 15 +- src/router/index.ts | 27 + src/storage/browser-storage.service.spec.ts | 61 +- .../default-localstorage.service.spec.ts | 17 +- .../default-sessionstorage.service.spec.ts | 17 +- .../storage-persistence.service.spec.ts | 128 +- src/storage/storage-persistence.service.ts | 7 +- .../create-retriable-stream.helper.ts | 4 +- src/testing/index.ts | 7 + src/{test.ts => testing/init-test.ts} | 6 +- test/auto-mock.ts => src/testing/mock.ts | 19 +- src/testing/router.ts | 12 + src/testing/spy.ts | 53 + src/testing/testbed.ts | 90 + src/user-data/user-service.spec.ts | 440 +-- src/user-data/user.service.ts | 50 +- src/utils/crypto/crypto.service.spec.ts | 3 +- src/utils/equality/equality.service.spec.ts | 3 +- .../flowHelper/flow-helper.service.spec.ts | 59 +- src/utils/flowHelper/flow-helper.service.ts | 2 +- .../platform-provider.spec.ts | 3 +- src/utils/redirect/redirect.service.spec.ts | 7 +- .../tokenHelper/token-helper.service.spec.ts | 5 +- src/utils/url/current-url.service.spec.ts | 5 +- src/utils/url/url.service.spec.ts | 709 ++-- .../jwk-window-crypto.service.spec.ts | 7 +- .../jwt-window-crypto.service.spec.ts | 7 +- .../state-validation.service.spec.ts | 1040 +++--- .../token-validation.service.spec.ts | 66 +- tsconfig.json | 28 +- tsconfig.lib.json | 17 +- tsconfig.spec.json | 24 +- 125 files changed, 8601 insertions(+), 4725 deletions(-) rename biome.json => biome.jsonc (63%) delete mode 100644 karma.conf.js create mode 100644 package-lock.json create mode 100644 src/http/index.ts create mode 100644 src/injection/convention.ts create mode 100644 src/injection/index.ts create mode 100644 src/injection/inject.ts create mode 100644 src/injection/module.ts create mode 100644 src/router/index.ts rename {test => src/testing}/create-retriable-stream.helper.ts (84%) create mode 100644 src/testing/index.ts rename src/{test.ts => testing/init-test.ts} (63%) rename test/auto-mock.ts => src/testing/mock.ts (78%) create mode 100644 src/testing/router.ts create mode 100644 src/testing/spy.ts create mode 100644 src/testing/testbed.ts diff --git a/README.md b/README.md index 0162167..0989a48 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Angular Lib for OpenID Connect & OAuth2 +TODO + ![Build Status](https://github.com/damienbod/oidc-client-rx/actions/workflows/build.yml/badge.svg?branch=main) [![npm](https://img.shields.io/npm/v/oidc-client-rx.svg)](https://www.npmjs.com/package/oidc-client-rx) [![npm](https://img.shields.io/npm/dm/oidc-client-rx.svg)](https://www.npmjs.com/package/oidc-client-rx) [![npm](https://img.shields.io/npm/l/oidc-client-rx.svg)](https://www.npmjs.com/package/oidc-client-rx) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![Coverage Status](https://coveralls.io/repos/github/damienbod/oidc-client-rx/badge.svg?branch=main)](https://coveralls.io/github/damienbod/oidc-client-rx?branch=main)

@@ -155,7 +157,7 @@ const token = this.oidcSecurityService.getAccessToken().subscribe(...); And then you can use it in the HttpHeaders ```ts -import { HttpHeaders } from '@angular/common/http'; +import { HttpHeaders } from '@ngify/http'; const token = this.oidcSecurityServices.getAccessToken().subscribe((token) => { const httpOptions = { diff --git a/biome.json b/biome.jsonc similarity index 63% rename from biome.json rename to biome.jsonc index 170a920..201671e 100644 --- a/biome.json +++ b/biome.jsonc @@ -18,8 +18,14 @@ } }, "files": { - "ignore": [ - ".vscode/*.json" - ] - } + "ignore": [".vscode/*.json"] + }, + "overrides": [ + { + "include": ["src/**/*.spec.ts", "src/test.ts", "test"], + "javascript": { + "globals": ["describe", "beforeEach", "it", "expect"] + } + } + ] } diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 6a1fb11..0000000 --- a/karma.conf.js +++ /dev/null @@ -1,50 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage'), - require('@angular-devkit/build-angular/plugins/karma'), - ], - client: { - jasmine: { - // you can add configuration options for Jasmine here - // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html - // for example, you can disable the random execution with `random: false` - // or set a specific seed with `seed: 4321` - }, - clearContext: false, // leave Jasmine Spec Runner output visible in browser - }, - jasmineHtmlReporter: { - suppressAll: true, // removes the duplicated traces - }, - coverageReporter: { - dir: require('path').join( - __dirname, - '../../coverage/oidc-client-rx' - ), - subdir: '.', - reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'lcov' }], - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - customLaunchers: { - ChromeHeadlessNoSandbox: { - base: 'ChromeHeadless', - flags: ['--no-sandbox'], - }, - }, - singleRun: false, - restartOnFileChange: true, - }); -}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f623415 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3006 @@ +{ + "name": "oidc-client-rx", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "oidc-client-rx", + "version": "0.1.0", + "dependencies": { + "@ngify/http": "^2.0.4", + "injection-js": "git+https://github.com/mgechev/injection-js.git#81a10e0", + "rxjs": ">=7.4.0" + }, + "devDependencies": { + "@evilmartians/lefthook": "^1.0.3", + "@playwright/test": "^1.49.1", + "@rslib/core": "^0.3.1", + "@types/node": "^22.10.1", + "@vitest/coverage-v8": "^3.0.1", + "rfc4648": "^1.5.0", + "typescript": "^5.7.3", + "ultracite": "^4.1.15", + "vitest": "^3.0.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@evilmartians/lefthook": { + "version": "1.10.10", + "resolved": "https://registry.npmjs.org/@evilmartians/lefthook/-/lefthook-1.10.10.tgz", + "integrity": "sha512-MRIA0zJzUBbmcbecI7QjI08li4ffpmZ6DeVydEiZSg0vSx5mElEMEjDEjkI60eSV0XOm7LRbQKz2rfW6NqH8Cw==", + "cpu": [ + "x64", + "arm64", + "ia32" + ], + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "bin": { + "lefthook": "bin/index.js" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@module-federation/error-codes": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.8.4.tgz", + "integrity": "sha512-55LYmrDdKb4jt+qr8qE8U3al62ZANp3FhfVaNPOaAmdTh0jHdD8M3yf5HKFlr5xVkVO4eV/F/J2NCfpbh+pEXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@module-federation/runtime": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.8.4.tgz", + "integrity": "sha512-yZeZ7z2Rx4gv/0E97oLTF3V6N25vglmwXGgoeju/W2YjsFvWzVtCDI7zRRb0mJhU6+jmSM8jP1DeQGbea/AiZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.8.4", + "@module-federation/sdk": "0.8.4" + } + }, + "node_modules/@module-federation/runtime-tools": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.8.4.tgz", + "integrity": "sha512-fjVOsItJ1u5YY6E9FnS56UDwZgqEQUrWFnouRiPtK123LUuqUI9FH4redZoKWlE1PB0ir1Z3tnqy8eFYzPO38Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.8.4", + "@module-federation/webpack-bundler-runtime": "0.8.4" + } + }, + "node_modules/@module-federation/sdk": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.8.4.tgz", + "integrity": "sha512-waABomIjg/5m1rPDBWYG4KUhS5r7OUUY7S+avpaVIY/tkPWB3ibRDKy2dNLLAMaLKq0u+B1qIdEp4NIWkqhqpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "isomorphic-rslog": "0.0.6" + } + }, + "node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.8.4.tgz", + "integrity": "sha512-HggROJhvHPUX7uqBD/XlajGygMNM1DG0+4OAkk8MBQe4a18QzrRNzZt6XQbRTSG4OaEoyRWhQHvYD3Yps405tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.8.4", + "@module-federation/sdk": "0.8.4" + } + }, + "node_modules/@ngify/core": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@ngify/core/-/core-2.0.4.tgz", + "integrity": "sha512-MyZ6TrD4NEEEpy5yBoAtCyAXySpLvi6kBrvJk1vl9s4dkU5H+/bKBeAy5gPvdbLQ6c6NHD3Y4cnnMBbhdtVOnA==", + "dependencies": { + "tslib": "^2.3.0" + } + }, + "node_modules/@ngify/http": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@ngify/http/-/http-2.0.4.tgz", + "integrity": "sha512-3w3mMadsrkO0/xgLC5+79qqOJcRc+XvcBoOMZZBPFdiWg7wHWHULv48KHFVau+GbzvnXEza46W/xYyOMyR0F8g==", + "dependencies": { + "@ngify/core": "2.0.4", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "rxjs": "^7.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@playwright/test": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.0.tgz", + "integrity": "sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.50.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz", + "integrity": "sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.32.1.tgz", + "integrity": "sha512-If3PDskT77q7zgqVqYuj7WG3WC08G1kwXGVFi9Jr8nY6eHucREHkfpX79c0ACAjLj3QIWKPJR7w4i+f5EdLH5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.32.1.tgz", + "integrity": "sha512-zCpKHioQ9KgZToFp5Wvz6zaWbMzYQ2LJHQ+QixDKq52KKrF65ueu6Af4hLlLWHjX1Wf/0G5kSJM9PySW9IrvHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.32.1.tgz", + "integrity": "sha512-sFvF+t2+TyUo/ZQqUcifrJIgznx58oFZbdHS9TvHq3xhPVL9nOp+yZ6LKrO9GWTP+6DbFtoyLDbjTpR62Mbr3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.32.1.tgz", + "integrity": "sha512-NbOa+7InvMWRcY9RG+B6kKIMD/FsnQPH0MWUvDlQB1iXnF/UcKSudCXZtv4lW+C276g3w5AxPbfry5rSYvyeYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.32.1.tgz", + "integrity": "sha512-JRBRmwvHPXR881j2xjry8HZ86wIPK2CcDw0EXchE1UgU0ubWp9nvlT7cZYKc6bkypBt745b4bglf3+xJ7hXWWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.32.1.tgz", + "integrity": "sha512-PKvszb+9o/vVdUzCCjL0sKHukEQV39tD3fepXxYrHE3sTKrRdCydI7uldRLbjLmDA3TFDmh418XH19NOsDRH8g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.32.1.tgz", + "integrity": "sha512-9WHEMV6Y89eL606ReYowXuGF1Yb2vwfKWKdD1A5h+OYnPZSJvxbEjxTRKPgi7tkP2DSnW0YLab1ooy+i/FQp/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.32.1.tgz", + "integrity": "sha512-tZWc9iEt5fGJ1CL2LRPw8OttkCBDs+D8D3oEM8mH8S1ICZCtFJhD7DZ3XMGM8kpqHvhGUTvNUYVDnmkj4BDXnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.32.1.tgz", + "integrity": "sha512-FTYc2YoTWUsBz5GTTgGkRYYJ5NGJIi/rCY4oK/I8aKowx1ToXeoVVbIE4LGAjsauvlhjfl0MYacxClLld1VrOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.32.1.tgz", + "integrity": "sha512-F51qLdOtpS6P1zJVRzYM0v6MrBNypyPEN1GfMiz0gPu9jN8ScGaEFIZQwteSsGKg799oR5EaP7+B2jHgL+d+Kw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.32.1.tgz", + "integrity": "sha512-wO0WkfSppfX4YFm5KhdCCpnpGbtgQNj/tgvYzrVYFKDpven8w2N6Gg5nB6w+wAMO3AIfSTWeTjfVe+uZ23zAlg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.32.1.tgz", + "integrity": "sha512-iWswS9cIXfJO1MFYtI/4jjlrGb/V58oMu4dYJIKnR5UIwbkzR0PJ09O0PDZT0oJ3LYWXBSWahNf/Mjo6i1E5/g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.32.1.tgz", + "integrity": "sha512-RKt8NI9tebzmEthMnfVgG3i/XeECkMPS+ibVZjZ6mNekpbbUmkNWuIN2yHsb/mBPyZke4nlI4YqIdFPgKuoyQQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.1.tgz", + "integrity": "sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.32.1.tgz", + "integrity": "sha512-BLoiyHDOWoS3uccNSADMza6V6vCNiphi94tQlVIL5de+r6r/CCQuNnerf+1g2mnk2b6edp5dk0nhdZ7aEjOBsA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.32.1.tgz", + "integrity": "sha512-w2l3UnlgYTNNU+Z6wOR8YdaioqfEnwPjIsJ66KxKAf0p+AuL2FHeTX6qvM+p/Ue3XPBVNyVSfCrfZiQh7vZHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.32.1.tgz", + "integrity": "sha512-Am9H+TGLomPGkBnaPWie4F3x+yQ2rr4Bk2jpwy+iV+Gel9jLAu/KqT8k3X4jxFPW6Zf8OMnehyutsd+eHoq1WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.32.1.tgz", + "integrity": "sha512-ar80GhdZb4DgmW3myIS9nRFYcpJRSME8iqWgzH2i44u+IdrzmiXVxeFnExQ5v4JYUSpg94bWjevMG8JHf1Da5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rsbuild/core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-1.2.3.tgz", + "integrity": "sha512-lUCt8gQe9E2PI3srcEJ1Na3GQYmsYuvAqK0f/k00HM0pEjrbOFC9Xq2kR85UoXHFqlTCIw/fLLDe91PKRCbKAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@rspack/core": "1.2.2", + "@rspack/lite-tapable": "~1.0.1", + "@swc/helpers": "^0.5.15", + "core-js": "~3.40.0" + }, + "bin": { + "rsbuild": "bin/rsbuild.js" + }, + "engines": { + "node": ">=16.7.0" + } + }, + "node_modules/@rslib/core": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@rslib/core/-/core-0.3.2.tgz", + "integrity": "sha512-BdR/1kjR+Jk614je0ijjEwGoY44fLtPg6PoWHoKfzMkqNMtFg40AwMaBS04SylvR4BuUt1Q+9k6NQ/IWdE5HWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rsbuild/core": "1.2.0-beta.1", + "rsbuild-plugin-dts": "0.3.2", + "tinyglobby": "^0.2.10" + }, + "bin": { + "rslib": "bin/rslib.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@rslib/core/node_modules/@rsbuild/core": { + "version": "1.2.0-beta.1", + "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-1.2.0-beta.1.tgz", + "integrity": "sha512-+TzwALD2xEpgc2FQgjn1CtNtE2CTS1FELuWXpjI4AABjytYDGdaOHvHyCLXHiJpuvDdnyJumoN/T8i8zhbTkPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rspack/core": "1.2.0-beta.0", + "@rspack/lite-tapable": "~1.0.1", + "@swc/helpers": "^0.5.15", + "core-js": "~3.40.0" + }, + "bin": { + "rsbuild": "bin/rsbuild.js" + }, + "engines": { + "node": ">=16.7.0" + } + }, + "node_modules/@rslib/core/node_modules/@rspack/binding": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.2.0-beta.0.tgz", + "integrity": "sha512-ZUBWVKCVC3uunZhjH7FAVLP83r/6QvPmYViA6n0JF3ycBmcJLkHJb26v42j6d5EfYfTtNvfRUlzHckIkFDQeDQ==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.2.0-beta.0", + "@rspack/binding-darwin-x64": "1.2.0-beta.0", + "@rspack/binding-linux-arm64-gnu": "1.2.0-beta.0", + "@rspack/binding-linux-arm64-musl": "1.2.0-beta.0", + "@rspack/binding-linux-x64-gnu": "1.2.0-beta.0", + "@rspack/binding-linux-x64-musl": "1.2.0-beta.0", + "@rspack/binding-win32-arm64-msvc": "1.2.0-beta.0", + "@rspack/binding-win32-ia32-msvc": "1.2.0-beta.0", + "@rspack/binding-win32-x64-msvc": "1.2.0-beta.0" + } + }, + "node_modules/@rslib/core/node_modules/@rspack/binding-darwin-arm64": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.2.0-beta.0.tgz", + "integrity": "sha512-g8NgY4OIjZf5LabAKOHNr2rs/WzVaxXpOSVsdHztQL6ETdeEpIPUul4p/5zivFNcrvJxVVeHzKJHyB5lqxDcTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rslib/core/node_modules/@rspack/binding-darwin-x64": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.2.0-beta.0.tgz", + "integrity": "sha512-+BH/1UpG96exJc6KhDOiSHAb05bUwxbYCd37HAJwcLxQgB7IEmPtBYvV5CtHysteM5NBtbNeeAcyXK+dIYvUew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rslib/core/node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.0-beta.0.tgz", + "integrity": "sha512-LdIBNy5WAXJ1J9nB3bEyvqz7mJrMN/7cCtPHMmFBR1aTFbh1NAjYZl24fc+f59aSV5jAU9wfTrOtqtSwnXg4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rslib/core/node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.0-beta.0.tgz", + "integrity": "sha512-4tRi87UyEWV25X6Ul68kJJ/de/cwmASmrIUrCYmdWEdtWMN46UOz0OvxCYvcHTf0DCP8M1CZf0cSiRuG/hsxGA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rslib/core/node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.0-beta.0.tgz", + "integrity": "sha512-rWWrPwUH3V4yG46acZDIlqr7H/yCxbu+WdPhdIRBvgT7bisQkZa2HYx6MXmUXxx94U2iFy5bh+un0ho5FZOeCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rslib/core/node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.0-beta.0.tgz", + "integrity": "sha512-9pgL17Bk8aSrTBx6VfQbb313RInDjlY9DfgV5ybbSsWaFs/oAs4oPy+kepWWixfb9Y2q/74bSBPrBNTBYQpknw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rslib/core/node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.0-beta.0.tgz", + "integrity": "sha512-JQ06Q3uTclIk4AvKUqx0Royx2PqVcUuumEUFVWERbd01fntkQqI3RFrPazBYAIvk5JmXk40+CKA1CsFef4RKOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rslib/core/node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.2.0-beta.0.tgz", + "integrity": "sha512-rNz/sXjXLAqCZkDuTumqm9Aa47Hiu45+vkJ0XvbirJ0A+dzuyGjdtlinwLyZtCY+dVAlS+AcX5znJLlpTSnjjA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rslib/core/node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.0-beta.0.tgz", + "integrity": "sha512-LKFcgaeEo7G6YLE5aKIbeWzXUpVZc02u0q4as0TjTTRBHd8r21GeaGJVh1Xm9YBkHpIX8Ho1DMftYVd+F6gHzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rslib/core/node_modules/@rspack/core": { + "version": "1.2.0-beta.0", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.2.0-beta.0.tgz", + "integrity": "sha512-0o0EYNeCwbRrh1l+P6HEKGS3Y+SVE/+J6SqDGGBsOixt/YzFeYNeaePWUnFfQ8a27jp9s//Ix6iuxMYGjWmitA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime-tools": "0.8.4", + "@rspack/binding": "1.2.0-beta.0", + "@rspack/lite-tapable": "1.0.1", + "caniuse-lite": "^1.0.30001616" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/binding": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.2.2.tgz", + "integrity": "sha512-GCZwpGFYlLTdJ2soPLwjw9z4LSZ+GdpbHNfBt3Cm/f/bAF8n6mZc7dHUqN893RFh7MPU17HNEL3fMw7XR+6pHg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.2.2", + "@rspack/binding-darwin-x64": "1.2.2", + "@rspack/binding-linux-arm64-gnu": "1.2.2", + "@rspack/binding-linux-arm64-musl": "1.2.2", + "@rspack/binding-linux-x64-gnu": "1.2.2", + "@rspack/binding-linux-x64-musl": "1.2.2", + "@rspack/binding-win32-arm64-msvc": "1.2.2", + "@rspack/binding-win32-ia32-msvc": "1.2.2", + "@rspack/binding-win32-x64-msvc": "1.2.2" + } + }, + "node_modules/@rspack/binding-darwin-arm64": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.2.2.tgz", + "integrity": "sha512-h23F8zEkXWhwMeScm0ZnN78Zh7hCDalxIWsm7bBS0eKadnlegUDwwCF8WE+8NjWr7bRzv0p3QBWlS5ufkcL4eA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rspack/binding-darwin-x64": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.2.2.tgz", + "integrity": "sha512-vG5s7FkEvwrGLfksyDRHwKAHUkhZt1zHZZXJQn4gZKjTBonje8ezdc7IFlDiWpC4S+oBYp73nDWkUzkGRbSdcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.2.tgz", + "integrity": "sha512-VykY/kiYOzO8E1nYzfJ9+gQEHxb5B6lt5wa8M6xFi5B6jEGU+OsaGskmAZB9/GFImeFDHxDPvhUalI4R9p8O2Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.2.tgz", + "integrity": "sha512-Z5vAC4wGfXi8XXZ6hs8Q06TYjr3zHf819HB4DI5i4C1eQTeKdZSyoFD0NHFG23bP4NWJffp8KhmoObcy9jBT5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.2.tgz", + "integrity": "sha512-o3pDaL+cH5EeRbDE9gZcdZpBgp5iXvYZBBhe8vZQllYgI4zN5MJEuleV7WplG3UwTXlgZg3Kht4RORSOPn96vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.2.tgz", + "integrity": "sha512-RE3e0xe4DdchHssttKzryDwjLkbrNk/4H59TkkWeGYJcLw41tmcOZVFQUOwKLUvXWVyif/vjvV/w1SMlqB4wQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.2.tgz", + "integrity": "sha512-R+PKBYn6uzTaDdVqTHvjqiJPBr5ZHg1wg5UmFDLNH9OklzVFyQh1JInSdJRb7lzfzTRz6bEkkwUFBPQK/CGScw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.2.2.tgz", + "integrity": "sha512-dBqz3sRAGZ2f31FgzKLDvIRfq2haRP3X3XVCT0PsiMcvt7QJng+26aYYMy2THatd/nM8IwExYeitHWeiMBoruw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.2.tgz", + "integrity": "sha512-eeAvaN831KG553cMSHkVldyk6YQn4ujgRHov6r1wtREq7CD3/ka9LMkJUepCN85K7XtwYT0N4KpFIQyf5GTGoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/core": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.2.2.tgz", + "integrity": "sha512-EeHAmY65Uj62hSbUKesbrcWGE7jfUI887RD03G++Gj8jS4WPHEu1TFODXNOXg6pa7zyIvs2BK0Bm16Kwz8AEaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/runtime-tools": "0.8.4", + "@rspack/binding": "1.2.2", + "@rspack/lite-tapable": "1.0.1", + "caniuse-lite": "^1.0.30001616" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@rspack/tracing": "^1.x", + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@rspack/tracing": { + "optional": true + }, + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/lite-tapable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz", + "integrity": "sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz", + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.4.tgz", + "integrity": "sha512-f0twgRCHgbs24Dp8cLWagzcObXMcuKtAwgxjJV/nnysPAJJk1JiKu/W0gIehZLmkljhJXU/E0/dmuQzsA/4jhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.0.4", + "vitest": "3.0.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.4.tgz", + "integrity": "sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.4", + "@vitest/utils": "3.0.4", + "chai": "^5.1.2", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.4.tgz", + "integrity": "sha512-gEef35vKafJlfQbnyOXZ0Gcr9IBUsMTyTLXsEQwuyYAerpHqvXhzdBnDFuHLpFqth3F7b6BaFr4qV/Cs1ULx5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.4.tgz", + "integrity": "sha512-ts0fba+dEhK2aC9PFuZ9LTpULHpY/nd6jhAQ5IMU7Gaj7crPCTdCFfgvXxruRBLFS+MLraicCuFXxISEq8C93g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.4.tgz", + "integrity": "sha512-dKHzTQ7n9sExAcWH/0sh1elVgwc7OJ2lMOBrAm73J7AH6Pf9T12Zh3lNE1TETZaqrWFXtLlx3NVrLRb5hCK+iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.0.4", + "pathe": "^2.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.4.tgz", + "integrity": "sha512-+p5knMLwIk7lTQkM3NonZ9zBewzVp9EVkVpvNta0/PlFWpiqLaRcF4+33L1it3uRUCh0BGLOaXPPGEjNKfWb4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.4.tgz", + "integrity": "sha512-sXIMF0oauYyUy2hN49VFTYodzEAu744MmGcPR3ZBsPM20G+1/cSW/n1U+3Yu/zHxX2bIDe1oJASOkml+osTU6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.4.tgz", + "integrity": "sha512-8BqC1ksYsHtbWH+DfpOAKrFw3jl3Uf9J7yeFh85Pz52IWuh1hBBtyfEbRNNZNjl8H8A5yMLH9/t+k7HIKzQcZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.4", + "loupe": "^3.1.2", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001696", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", + "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/core-js": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", + "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/injection-js": { + "version": "2.4.0", + "resolved": "git+ssh://git@github.com/mgechev/injection-js.git#81a10e03929a913486f5ccb88bb893c67beeb228", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.5" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isomorphic-rslog": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/isomorphic-rslog/-/isomorphic-rslog-0.0.6.tgz", + "integrity": "sha512-HM0q6XqQ93psDlqvuViNs/Ea3hAyGDkIdVAHlrEocjjAwGrs1fZ+EdQjS9eUPacnYB7Y8SoDdSY3H8p3ce205A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.6" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.0.tgz", + "integrity": "sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.50.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.0.tgz", + "integrity": "sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rfc4648": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.4.tgz", + "integrity": "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.1.tgz", + "integrity": "sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.32.1", + "@rollup/rollup-android-arm64": "4.32.1", + "@rollup/rollup-darwin-arm64": "4.32.1", + "@rollup/rollup-darwin-x64": "4.32.1", + "@rollup/rollup-freebsd-arm64": "4.32.1", + "@rollup/rollup-freebsd-x64": "4.32.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.32.1", + "@rollup/rollup-linux-arm-musleabihf": "4.32.1", + "@rollup/rollup-linux-arm64-gnu": "4.32.1", + "@rollup/rollup-linux-arm64-musl": "4.32.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.32.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.32.1", + "@rollup/rollup-linux-riscv64-gnu": "4.32.1", + "@rollup/rollup-linux-s390x-gnu": "4.32.1", + "@rollup/rollup-linux-x64-gnu": "4.32.1", + "@rollup/rollup-linux-x64-musl": "4.32.1", + "@rollup/rollup-win32-arm64-msvc": "4.32.1", + "@rollup/rollup-win32-ia32-msvc": "4.32.1", + "@rollup/rollup-win32-x64-msvc": "4.32.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rsbuild-plugin-dts": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/rsbuild-plugin-dts/-/rsbuild-plugin-dts-0.3.2.tgz", + "integrity": "sha512-Gjaf5ANA6R9t92Va6utNG1iyRxCE0QLoswKvR5UkOkX5GUpNyWveze+k/6L/qrkgqGRuJqUuYX3lrwatrOUZ3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "picocolors": "1.1.1", + "tinyglobby": "^0.2.10" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7", + "@rsbuild/core": "1.x", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ultracite": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/ultracite/-/ultracite-4.1.17.tgz", + "integrity": "sha512-CdugkIPgpaQle6Yzn4foEV4t+ocLmos2y/64hr+I9ZJ0XVDrO52AeTioc+n7wTxwUxuEF/VxBkcxXhyQYu9hzg==", + "dev": true, + "license": "ISC", + "dependencies": { + "commander": "^12.1.0" + }, + "bin": { + "ultracite": "scripts/run.mjs" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", + "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.24.2", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.4.tgz", + "integrity": "sha512-7JZKEzcYV2Nx3u6rlvN8qdo3QV7Fxyt6hx+CCKz9fbWxdX5IvUOmTWEAxMrWxaiSf7CKGLJQ5rFu8prb/jBjOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.2", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vitest": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.4.tgz", + "integrity": "sha512-6XG8oTKy2gnJIFTHP6LD7ExFeNLxiTkK3CfMvT7IfR8IN+BYICCf0lXUQmX7i7JoxUP8QmeP4mTnWXgflu4yjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.0.4", + "@vitest/mocker": "3.0.4", + "@vitest/pretty-format": "^3.0.4", + "@vitest/runner": "3.0.4", + "@vitest/snapshot": "3.0.4", + "@vitest/spy": "3.0.4", + "@vitest/utils": "3.0.4", + "chai": "^5.1.2", + "debug": "^4.4.0", + "expect-type": "^1.1.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.4", + "@vitest/ui": "3.0.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json index 69d57db..6fa083a 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,7 @@ "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/index.d.ts", - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "build": "rslib build", "dev": "rslib build --watch", @@ -39,19 +37,24 @@ "dependencies": { "@ngify/http": "^2.0.4", "injection-js": "git+https://github.com/mgechev/injection-js.git#81a10e0", - "rxjs": ">=7.4.0" + "reflect-metadata": "^0.2.2" + }, + "peerDependencies": { + "rxjs": "^7.4.0||>=8.0.0" }, "devDependencies": { "@evilmartians/lefthook": "^1.0.3", "@playwright/test": "^1.49.1", "@rslib/core": "^0.3.1", - "@types/jasmine": "^4.0.0", + "@types/lodash-es": "^4.17.12", "@types/node": "^22.10.1", "@vitest/coverage-v8": "^3.0.1", + "lodash-es": "^4.17.21", "rfc4648": "^1.5.0", "typescript": "^5.7.3", "ultracite": "^4.1.15", - "vitest": "^3.0.1" + "vitest": "^3.0.1", + "rxjs": "^7.4.0" }, "keywords": [ "rxjs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 742ef71..003f594 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,9 @@ importers: injection-js: specifier: git+https://github.com/mgechev/injection-js.git#81a10e0 version: https://codeload.github.com/mgechev/injection-js/tar.gz/81a10e0 - rxjs: - specifier: '>=7.4.0' - version: 7.8.1 + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 devDependencies: '@evilmartians/lefthook': specifier: ^1.0.3 @@ -27,18 +27,24 @@ importers: '@rslib/core': specifier: ^0.3.1 version: 0.3.1(typescript@5.7.3) - '@types/jasmine': - specifier: ^4.0.0 - version: 4.6.4 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 '@types/node': specifier: ^22.10.1 version: 22.10.7 '@vitest/coverage-v8': specifier: ^3.0.1 version: 3.0.1(vitest@3.0.1(@types/node@22.10.7)) + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 rfc4648: specifier: ^1.5.0 version: 1.5.4 + rxjs: + specifier: ^7.4.0 + version: 7.8.1 typescript: specifier: ^5.7.3 version: 5.7.3 @@ -469,8 +475,11 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/jasmine@4.6.4': - resolution: {integrity: sha512-qCw5sVW+ylTnrEhe5kfX4l6MgU9REXIVDa/lWEcvTOUmd+LqDYwyjovDq+Zk9blElaEHOj1URDQ/djEBVRf+pw==} + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.15': + resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==} '@types/node@22.10.7': resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} @@ -677,6 +686,9 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + loupe@3.1.2: resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} @@ -748,6 +760,9 @@ packages: resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} engines: {node: ^10 || ^12 || >=14} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + rfc4648@1.5.4: resolution: {integrity: sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==} @@ -1244,7 +1259,11 @@ snapshots: '@types/estree@1.0.6': {} - '@types/jasmine@4.6.4': {} + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.15 + + '@types/lodash@4.17.15': {} '@types/node@22.10.7': dependencies: @@ -1469,6 +1488,8 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + lodash-es@4.17.21: {} + loupe@3.1.2: {} lru-cache@10.4.3: {} @@ -1528,6 +1549,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + reflect-metadata@0.2.2: {} + rfc4648@1.5.4: {} rollup@4.30.1: diff --git a/src/api/data.service.spec.ts b/src/api/data.service.spec.ts index ba33128..2061ae9 100644 --- a/src/api/data.service.spec.ts +++ b/src/api/data.service.spec.ts @@ -1,13 +1,15 @@ +import { TestBed } from '@/testing/testbed'; import { HttpHeaders, provideHttpClient, withInterceptorsFromDi, -} from '@angular/common/http'; +} from '@ngify/http'; import { HttpTestingController, provideHttpClientTesting, -} from '@angular/common/http/testing'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +} from '@ngify/http/testing'; +import { lastValueFrom } from 'rxjs'; +import { vi } from 'vitest'; import { DataService } from './data.service'; import { HttpBaseService } from './http-base.service'; @@ -25,9 +27,6 @@ describe('Data Service', () => { provideHttpClientTesting(), ], }); - }); - - beforeEach(() => { dataService = TestBed.inject(DataService); httpMock = TestBed.inject(HttpTestingController); }); @@ -37,7 +36,7 @@ describe('Data Service', () => { }); describe('get', () => { - it('get call sets the accept header', waitForAsync(() => { + it('get call sets the accept header', async () => { const url = 'testurl'; dataService @@ -53,9 +52,9 @@ describe('Data Service', () => { req.flush('bodyData'); httpMock.verify(); - })); + }); - it('get call with token the accept header and the token', waitForAsync(() => { + it('get call with token the accept header and the token', async () => { const url = 'testurl'; const token = 'token'; @@ -68,14 +67,14 @@ describe('Data Service', () => { expect(req.request.method).toBe('GET'); expect(req.request.headers.get('Accept')).toBe('application/json'); - expect(req.request.headers.get('Authorization')).toBe('Bearer ' + token); + expect(req.request.headers.get('Authorization')).toBe(`Bearer ${token}`); req.flush('bodyData'); httpMock.verify(); - })); + }); - it('call without ngsw-bypass param by default', waitForAsync(() => { + it('call without ngsw-bypass param by default', async () => { const url = 'testurl'; dataService @@ -92,9 +91,9 @@ describe('Data Service', () => { req.flush('bodyData'); httpMock.verify(); - })); + }); - it('call with ngsw-bypass param', waitForAsync(() => { + it('call with ngsw-bypass param', async () => { const url = 'testurl'; dataService @@ -102,7 +101,7 @@ describe('Data Service', () => { .subscribe((data: unknown) => { expect(data).toBe('bodyData'); }); - const req = httpMock.expectOne(url + '?ngsw-bypass='); + const req = httpMock.expectOne(`${url}?ngsw-bypass=`); expect(req.request.method).toBe('GET'); expect(req.request.headers.get('Accept')).toBe('application/json'); @@ -111,11 +110,11 @@ describe('Data Service', () => { req.flush('bodyData'); httpMock.verify(); - })); + }); }); describe('post', () => { - it('call sets the accept header when no other params given', waitForAsync(() => { + it('call sets the accept header when no other params given', async () => { const url = 'testurl'; dataService @@ -128,18 +127,23 @@ describe('Data Service', () => { req.flush('bodyData'); - httpMock.verify(); - })); + await httpMock.verify(); + }); - it('call sets custom headers ONLY (No ACCEPT header) when custom headers are given', waitForAsync(() => { + it('call sets custom headers ONLY (No ACCEPT header) when custom headers are given', async () => { const url = 'testurl'; let headers = new HttpHeaders(); headers = headers.set('X-MyHeader', 'Genesis'); - dataService - .post(url, { some: 'thing' }, { configId: 'configId1' }, headers) - .subscribe(); + await lastValueFrom( + dataService.post( + url, + { some: 'thing' }, + { configId: 'configId1' }, + headers + ) + ); const req = httpMock.expectOne(url); expect(req.request.method).toBe('POST'); @@ -149,14 +153,14 @@ describe('Data Service', () => { req.flush('bodyData'); httpMock.verify(); - })); + }); - it('call without ngsw-bypass param by default', waitForAsync(() => { + it('call without ngsw-bypass param by default', async () => { const url = 'testurl'; - dataService - .post(url, { some: 'thing' }, { configId: 'configId1' }) - .subscribe(); + await lastValueFrom( + dataService.post(url, { some: 'thing' }, { configId: 'configId1' }) + ); const req = httpMock.expectOne(url); expect(req.request.method).toBe('POST'); @@ -166,18 +170,19 @@ describe('Data Service', () => { req.flush('bodyData'); httpMock.verify(); - })); + }); - it('call with ngsw-bypass param', waitForAsync(() => { + it('call with ngsw-bypass param', async () => { const url = 'testurl'; - dataService - .post( + await lastValueFrom( + dataService.post( url, { some: 'thing' }, { configId: 'configId1', ngswBypass: true } ) - .subscribe(); + ); + // biome-ignore lint/style/useTemplate: const req = httpMock.expectOne(url + '?ngsw-bypass='); expect(req.request.method).toBe('POST'); @@ -187,6 +192,6 @@ describe('Data Service', () => { req.flush('bodyData'); httpMock.verify(); - })); + }); }); }); diff --git a/src/auth-config.spec.ts b/src/auth-config.spec.ts index c1e706d..5d8fe28 100644 --- a/src/auth-config.spec.ts +++ b/src/auth-config.spec.ts @@ -1,4 +1,4 @@ -import { PassedInitialConfig, createStaticLoader } from './auth-config'; +import { type PassedInitialConfig, createStaticLoader } from './auth-config'; describe('AuthConfig', () => { describe('createStaticLoader', () => { diff --git a/src/auth-config.ts b/src/auth-config.ts index 9c785ec..6b441bc 100644 --- a/src/auth-config.ts +++ b/src/auth-config.ts @@ -1,9 +1,9 @@ -import { InjectionToken, Provider } from 'injection-js'; +import { InjectionToken, type Provider } from 'injection-js'; import { - StsConfigLoader, + type StsConfigLoader, StsConfigStaticLoader, } from './config/loader/config-loader'; -import { OpenIdConfiguration } from './config/openid-configuration'; +import type { OpenIdConfiguration } from './config/openid-configuration'; export interface PassedInitialConfig { config?: OpenIdConfiguration | OpenIdConfiguration[]; diff --git a/src/auth-state/auth-state.service.spec.ts b/src/auth-state/auth-state.service.spec.ts index 7f1f239..5379e1e 100644 --- a/src/auth-state/auth-state.service.spec.ts +++ b/src/auth-state/auth-state.service.spec.ts @@ -1,13 +1,14 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { Observable } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; import { EventTypes } from '../public-events/event-types'; import { PublicEventsService } from '../public-events/public-events.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockProvider } from '../testing/mock'; import { PlatformProvider } from '../utils/platform-provider/platform.provider'; import { TokenValidationService } from '../validation/token-validation.service'; -import { ValidationResult } from '../validation/validation-result'; +import type { ValidationResult } from '../validation/validation-result'; import { AuthStateService } from './auth-state.service'; describe('Auth State Service', () => { @@ -27,9 +28,6 @@ describe('Auth State Service', () => { mockProvider(StoragePersistenceService), ], }); - }); - - beforeEach(() => { authStateService = TestBed.inject(AuthStateService); storagePersistenceService = TestBed.inject(StoragePersistenceService); eventsService = TestBed.inject(PublicEventsService); @@ -41,12 +39,12 @@ describe('Auth State Service', () => { }); it('public authorize$ is observable$', () => { - expect(authStateService.authenticated$).toEqual(jasmine.any(Observable)); + expect(authStateService.authenticated$).toBeInstanceOf(Observable); }); describe('setAuthorizedAndFireEvent', () => { it('throws correct event with single config', () => { - const spy = spyOn( + const spy = vi.spyOn( (authStateService as any).authenticatedInternal$, 'next' ); @@ -55,7 +53,7 @@ describe('Auth State Service', () => { { configId: 'configId1' }, ]); - expect(spy).toHaveBeenCalledOnceWith({ + expect(spy).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: true, allConfigsAuthenticated: [ { configId: 'configId1', isAuthenticated: true }, @@ -64,7 +62,7 @@ describe('Auth State Service', () => { }); it('throws correct event with multiple configs', () => { - const spy = spyOn( + const spy = vi.spyOn( (authStateService as any).authenticatedInternal$, 'next' ); @@ -74,7 +72,7 @@ describe('Auth State Service', () => { { configId: 'configId2' }, ]); - expect(spy).toHaveBeenCalledOnceWith({ + expect(spy).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, allConfigsAuthenticated: [ { configId: 'configId1', isAuthenticated: false }, @@ -86,26 +84,34 @@ describe('Auth State Service', () => { it('throws correct event with multiple configs, one is authenticated', () => { const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }]; - spyOn(storagePersistenceService, 'getAccessToken') - .withArgs(allConfigs[0]) - .and.returnValue('someAccessToken') - .withArgs(allConfigs[1]) - .and.returnValue(''); + mockImplementationWhenArgsEqual( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'getAccessToken'), + [allConfigs[0]!], + () => 'someAccessToken' + ), + [allConfigs[1]!], + () => '' + ); - spyOn(storagePersistenceService, 'getIdToken') - .withArgs(allConfigs[0]) - .and.returnValue('someIdToken') - .withArgs(allConfigs[1]) - .and.returnValue(''); + mockImplementationWhenArgsEqual( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'getIdToken'), + [allConfigs[0]!], + () => 'someIdToken' + ), + [allConfigs[1]!], + () => '' + ); - const spy = spyOn( + const spy = vi.spyOn( (authStateService as any).authenticatedInternal$, 'next' ); authStateService.setAuthenticatedAndFireEvent(allConfigs); - expect(spy).toHaveBeenCalledOnceWith({ + expect(spy).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, allConfigsAuthenticated: [ { configId: 'configId1', isAuthenticated: true }, @@ -117,17 +123,20 @@ describe('Auth State Service', () => { describe('setUnauthorizedAndFireEvent', () => { it('persist AuthState In Storage', () => { - const spy = spyOn(storagePersistenceService, 'resetAuthStateInStorage'); + const spy = vi.spyOn( + storagePersistenceService, + 'resetAuthStateInStorage' + ); authStateService.setUnauthenticatedAndFireEvent( { configId: 'configId1' }, [{ configId: 'configId1' }] ); - expect(spy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + expect(spy).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' }); }); it('throws correct event with single config', () => { - const spy = spyOn( + const spy = vi.spyOn( (authStateService as any).authenticatedInternal$, 'next' ); @@ -137,7 +146,7 @@ describe('Auth State Service', () => { [{ configId: 'configId1' }] ); - expect(spy).toHaveBeenCalledOnceWith({ + expect(spy).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, allConfigsAuthenticated: [ { configId: 'configId1', isAuthenticated: false }, @@ -146,7 +155,7 @@ describe('Auth State Service', () => { }); it('throws correct event with multiple configs', () => { - const spy = spyOn( + const spy = vi.spyOn( (authStateService as any).authenticatedInternal$, 'next' ); @@ -156,7 +165,7 @@ describe('Auth State Service', () => { [{ configId: 'configId1' }, { configId: 'configId2' }] ); - expect(spy).toHaveBeenCalledOnceWith({ + expect(spy).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, allConfigsAuthenticated: [ { configId: 'configId1', isAuthenticated: false }, @@ -166,19 +175,27 @@ describe('Auth State Service', () => { }); it('throws correct event with multiple configs, one is authenticated', () => { - spyOn(storagePersistenceService, 'getAccessToken') - .withArgs({ configId: 'configId1' }) - .and.returnValue('someAccessToken') - .withArgs({ configId: 'configId2' }) - .and.returnValue(''); + mockImplementationWhenArgsEqual( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'getAccessToken'), + [{ configId: 'configId1' }], + () => 'someAccessToken' + ), + [{ configId: 'configId2' }], + () => '' + ); - spyOn(storagePersistenceService, 'getIdToken') - .withArgs({ configId: 'configId1' }) - .and.returnValue('someIdToken') - .withArgs({ configId: 'configId2' }) - .and.returnValue(''); + mockImplementationWhenArgsEqual( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'getIdToken'), + [{ configId: 'configId1' }], + () => 'someIdToken' + ), + [{ configId: 'configId2' }], + () => '' + ); - const spy = spyOn( + const spy = vi.spyOn( (authStateService as any).authenticatedInternal$, 'next' ); @@ -188,7 +205,7 @@ describe('Auth State Service', () => { [{ configId: 'configId1' }, { configId: 'configId2' }] ); - expect(spy).toHaveBeenCalledOnceWith({ + expect(spy).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, allConfigsAuthenticated: [ { configId: 'configId1', isAuthenticated: true }, @@ -200,24 +217,27 @@ describe('Auth State Service', () => { describe('updateAndPublishAuthState', () => { it('calls eventsService', () => { - spyOn(eventsService, 'fireEvent'); + vi.spyOn(eventsService, 'fireEvent'); - authStateService.updateAndPublishAuthState({ + const arg = { isAuthenticated: false, isRenewProcess: false, validationResult: {} as ValidationResult, - }); + }; - expect(eventsService.fireEvent).toHaveBeenCalledOnceWith( + authStateService.updateAndPublishAuthState(arg); + + expect(eventsService.fireEvent).toHaveBeenCalledOnce(); + expect(eventsService.fireEvent).toHaveBeenCalledExactlyOnceWith( EventTypes.NewAuthenticationResult, - jasmine.any(Object) + arg ); }); }); describe('setAuthorizationData', () => { it('stores accessToken', () => { - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); const authResult = { id_token: 'idtoken', access_token: 'accesstoken', @@ -237,18 +257,18 @@ describe('Auth State Service', () => { [{ configId: 'configId1' }] ); expect(spy).toHaveBeenCalledTimes(2); - expect(spy.calls.allArgs()).toEqual([ + expect(spy).toHaveBeenCalledWith([ ['authzData', 'accesstoken', { configId: 'configId1' }], [ 'access_token_expires_at', - jasmine.any(Number), + expect.any(Number), { configId: 'configId1' }, ], ]); }); it('does not crash and store accessToken when authResult is null', () => { - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); const authResult = null; authStateService.setAuthorizationData( @@ -262,7 +282,7 @@ describe('Auth State Service', () => { }); it('calls setAuthenticatedAndFireEvent() method', () => { - const spy = spyOn(authStateService, 'setAuthenticatedAndFireEvent'); + const spy = vi.spyOn(authStateService, 'setAuthenticatedAndFireEvent'); const authResult = { id_token: 'idtoken', access_token: 'accesstoken', @@ -288,28 +308,29 @@ describe('Auth State Service', () => { describe('getAccessToken', () => { it('isAuthorized is false returns null', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(''); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(''); const result = authStateService.getAccessToken({ configId: 'configId1' }); expect(result).toBe(''); }); it('returns false if storagePersistenceService returns something falsy but authorized', () => { - spyOn(authStateService, 'isAuthenticated').and.returnValue(true); - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); + vi.spyOn(authStateService, 'isAuthenticated').mockReturnValue(true); + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(''); const result = authStateService.getAccessToken({ configId: 'configId1' }); expect(result).toBe(''); }); it('isAuthorized is true returns decodeURIComponent(token)', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'HenloLegger' ); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'HenloFuriend' ); + const result = authStateService.getAccessToken({ configId: 'configId1' }); expect(result).toBe(decodeURIComponent('HenloLegger')); @@ -318,12 +339,14 @@ describe('Auth State Service', () => { describe('getAuthenticationResult', () => { it('isAuthorized is false returns null', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(''); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(''); - spyOn(storagePersistenceService, 'getAuthenticationResult') - .withArgs({ configId: 'configId1' }) - .and.returnValue({}); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'getAuthenticationResult'), + [{ configId: 'configId1' }], + () => ({}) + ); const result = authStateService.getAuthenticationResult({ configId: 'configId1', @@ -333,10 +356,13 @@ describe('Auth State Service', () => { }); it('returns false if storagePersistenceService returns something falsy but authorized', () => { - spyOn(authStateService, 'isAuthenticated').and.returnValue(true); - spyOn(storagePersistenceService, 'getAuthenticationResult') - .withArgs({ configId: 'configId1' }) - .and.returnValue({}); + vi.spyOn(authStateService, 'isAuthenticated').mockReturnValue(true); + + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'getAuthenticationResult'), + [{ configId: 'configId1' }], + () => ({}) + ); const result = authStateService.getAuthenticationResult({ configId: 'configId1', @@ -346,15 +372,18 @@ describe('Auth State Service', () => { }); it('isAuthorized is true returns object', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'HenloLegger' ); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'HenloFuriend' ); - spyOn(storagePersistenceService, 'getAuthenticationResult') - .withArgs({ configId: 'configId1' }) - .and.returnValue({ scope: 'HenloFuriend' }); + + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'getAuthenticationResult'), + [{ configId: 'configId1' }], + () => ({ scope: 'HenloFuriend' }) + ); const result = authStateService.getAuthenticationResult({ configId: 'configId1', @@ -366,18 +395,18 @@ describe('Auth State Service', () => { describe('getIdToken', () => { it('isAuthorized is false returns null', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(''); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(''); const result = authStateService.getIdToken({ configId: 'configId1' }); expect(result).toBe(''); }); it('isAuthorized is true returns decodeURIComponent(token)', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'HenloLegger' ); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'HenloFuriend' ); const result = authStateService.getIdToken({ configId: 'configId1' }); @@ -388,8 +417,8 @@ describe('Auth State Service', () => { describe('getRefreshToken', () => { it('isAuthorized is false returns null', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(''); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(''); const result = authStateService.getRefreshToken({ configId: 'configId1', }); @@ -398,13 +427,13 @@ describe('Auth State Service', () => { }); it('isAuthorized is true returns decodeURIComponent(token)', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'HenloLegger' ); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'HenloFuriend' ); - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( 'HenloRefreshLegger' ); const result = authStateService.getRefreshToken({ @@ -417,105 +446,105 @@ describe('Auth State Service', () => { describe('areAuthStorageTokensValid', () => { it('isAuthorized is false returns false', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(''); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(''); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(''); const result = authStateService.areAuthStorageTokensValid({ configId: 'configId1', }); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('isAuthorized is true and id_token is expired returns true', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'HenloLegger' ); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'HenloFuriend' ); - spyOn( + vi.spyOn( authStateService as any, 'hasIdTokenExpiredAndRenewCheckIsEnabled' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authStateService as any, 'hasAccessTokenExpiredIfExpiryExists' - ).and.returnValue(false); + ).mockReturnValue(false); const result = authStateService.areAuthStorageTokensValid({ configId: 'configId1', }); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('isAuthorized is true and access_token is expired returns true', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'HenloLegger' ); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'HenloFuriend' ); - spyOn( + vi.spyOn( authStateService as any, 'hasIdTokenExpiredAndRenewCheckIsEnabled' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( authStateService as any, 'hasAccessTokenExpiredIfExpiryExists' - ).and.returnValue(true); + ).mockReturnValue(true); const result = authStateService.areAuthStorageTokensValid({ configId: 'configId1', }); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('isAuthorized is true and id_token is not expired returns true', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'HenloLegger' ); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'HenloFuriend' ); - spyOn( + vi.spyOn( authStateService as any, 'hasIdTokenExpiredAndRenewCheckIsEnabled' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( authStateService as any, 'hasAccessTokenExpiredIfExpiryExists' - ).and.returnValue(false); + ).mockReturnValue(false); const result = authStateService.areAuthStorageTokensValid({ configId: 'configId1', }); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); it('authState is AuthorizedState.Authorized and id_token is not expired fires event', () => { - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'HenloLegger' ); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'HenloFuriend' ); - spyOn( + vi.spyOn( authStateService as any, 'hasIdTokenExpiredAndRenewCheckIsEnabled' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( authStateService as any, 'hasAccessTokenExpiredIfExpiryExists' - ).and.returnValue(false); + ).mockReturnValue(false); const result = authStateService.areAuthStorageTokensValid({ configId: 'configId1', }); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); }); @@ -527,56 +556,65 @@ describe('Auth State Service', () => { triggerRefreshWhenIdTokenExpired: true, }; - spyOn(storagePersistenceService, 'getIdToken') - .withArgs(config) - .and.returnValue('idToken'); - const spy = spyOn( - tokenValidationService, - 'hasIdTokenExpired' - ).and.callFake((_a, _b) => true); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'getIdToken'), + [config], + () => 'idToken' + ); + const spy = vi + .spyOn(tokenValidationService, 'hasIdTokenExpired') + .mockImplementation((_a, _b) => true); authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config); - expect(spy).toHaveBeenCalledOnceWith('idToken', config, 30); + expect(spy).toHaveBeenCalledExactlyOnceWith('idToken', config, 30); }); it('fires event if idToken is expired', () => { - spyOn(tokenValidationService, 'hasIdTokenExpired').and.callFake( + vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockImplementation( (_a, _b) => true ); - const spy = spyOn(eventsService, 'fireEvent'); + const spy = vi.spyOn(eventsService, 'fireEvent'); const config = { configId: 'configId1', renewTimeBeforeTokenExpiresInSeconds: 30, triggerRefreshWhenIdTokenExpired: true, }; - spyOn(storagePersistenceService, 'read') - .withArgs('authnResult', config) - .and.returnValue('idToken'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authnResult', config], + () => 'idToken' + ); const result = authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config); expect(result).toBe(true); - expect(spy).toHaveBeenCalledOnceWith(EventTypes.IdTokenExpired, true); + expect(spy).toHaveBeenCalledExactlyOnceWith( + EventTypes.IdTokenExpired, + true + ); }); it('does NOT fire event if idToken is NOT expired', () => { - spyOn(tokenValidationService, 'hasIdTokenExpired').and.callFake( + vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockImplementation( (_a, _b) => false ); - const spy = spyOn(eventsService, 'fireEvent'); + const spy = vi.spyOn(eventsService, 'fireEvent'); const config = { configId: 'configId1', renewTimeBeforeTokenExpiresInSeconds: 30, }; - spyOn(storagePersistenceService, 'read') - .withArgs('authnResult', config) - .and.returnValue('idToken'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authnResult', config], + () => 'idToken' + ); + const result = authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config); @@ -595,41 +633,45 @@ describe('Auth State Service', () => { renewTimeBeforeTokenExpiresInSeconds: 5, }; - spyOn(storagePersistenceService, 'read') - .withArgs('access_token_expires_at', config) - .and.returnValue(date); - const spy = spyOn( - tokenValidationService, - 'validateAccessTokenNotExpired' - ).and.returnValue(validateAccessTokenNotExpiredResult); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['access_token_expires_at', config], + () => date + ); + const spy = vi + .spyOn(tokenValidationService, 'validateAccessTokenNotExpired') + .mockReturnValue(validateAccessTokenNotExpiredResult); const result = authStateService.hasAccessTokenExpiredIfExpiryExists(config); - expect(spy).toHaveBeenCalledOnceWith(date, config, 5); + expect(spy).toHaveBeenCalledExactlyOnceWith(date, config, 5); expect(result).toEqual(expectedResult); }); it('throws event when token is expired', () => { const validateAccessTokenNotExpiredResult = false; const expectedResult = !validateAccessTokenNotExpiredResult; - // spyOn(configurationProvider, 'getOpenIDConfiguration').and.returnValue({ renewTimeBeforeTokenExpiresInSeconds: 5 }); + // vi.spyOn(configurationProvider, 'getOpenIDConfiguration').mockReturnValue({ renewTimeBeforeTokenExpiresInSeconds: 5 }); const date = new Date(new Date().toUTCString()); const config = { configId: 'configId1', renewTimeBeforeTokenExpiresInSeconds: 5, }; - spyOn(eventsService, 'fireEvent'); + vi.spyOn(eventsService, 'fireEvent'); - spyOn(storagePersistenceService, 'read') - .withArgs('access_token_expires_at', config) - .and.returnValue(date); - spyOn( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['access_token_expires_at', config], + () => date + ); + + vi.spyOn( tokenValidationService, 'validateAccessTokenNotExpired' - ).and.returnValue(validateAccessTokenNotExpiredResult); + ).mockReturnValue(validateAccessTokenNotExpiredResult); authStateService.hasAccessTokenExpiredIfExpiryExists(config); - expect(eventsService.fireEvent).toHaveBeenCalledOnceWith( + expect(eventsService.fireEvent).toHaveBeenCalledExactlyOnceWith( EventTypes.TokenExpired, expectedResult ); diff --git a/src/auth-state/auth-state.service.ts b/src/auth-state/auth-state.service.ts index 7c51a94..baf74a7 100644 --- a/src/auth-state/auth-state.service.ts +++ b/src/auth-state/auth-state.service.ts @@ -1,15 +1,15 @@ import { Injectable, inject } from 'injection-js'; -import { BehaviorSubject, Observable, throwError } from 'rxjs'; +import { BehaviorSubject, type Observable, throwError } from 'rxjs'; import { distinctUntilChanged } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { AuthResult } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { AuthResult } from '../flows/callback-context'; import { LoggerService } from '../logging/logger.service'; import { EventTypes } from '../public-events/event-types'; import { PublicEventsService } from '../public-events/public-events.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { TokenValidationService } from '../validation/token-validation.service'; -import { AuthenticatedResult } from './auth-result'; -import { AuthStateResult } from './auth-state'; +import type { AuthenticatedResult } from './auth-result'; +import type { AuthStateResult } from './auth-state'; const DEFAULT_AUTHRESULT = { isAuthenticated: false, @@ -293,7 +293,7 @@ export class AuthStateService { }; } - return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs); + return this.checkallConfigsIfTheyAreAuthenticated(allConfigs); } private composeUnAuthenticatedResult( @@ -310,10 +310,10 @@ export class AuthStateService { }; } - return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs); + return this.checkallConfigsIfTheyAreAuthenticated(allConfigs); } - private checkAllConfigsIfTheyAreAuthenticated( + private checkallConfigsIfTheyAreAuthenticated( allConfigs: OpenIdConfiguration[] ): AuthenticatedResult { const allConfigsAuthenticated = allConfigs.map((config) => ({ diff --git a/src/auth-state/check-auth.service.spec.ts b/src/auth-state/check-auth.service.spec.ts index d7f9534..5f491b7 100644 --- a/src/auth-state/check-auth.service.spec.ts +++ b/src/auth-state/check-auth.service.spec.ts @@ -1,7 +1,10 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; +import { + TestBed, + mockImplementationWhenArgsEqual, + mockRouterProvider, +} from '@/testing'; import { of, throwError } from 'rxjs'; -import { mockAbstractProvider, mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { AutoLoginService } from '../auto-login/auto-login.service'; import { CallbackService } from '../callback/callback.service'; import { PeriodicallyTokenCheckService } from '../callback/periodically-token-check.service'; @@ -10,16 +13,17 @@ import { StsConfigLoader, StsConfigStaticLoader, } from '../config/loader/config-loader'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { CheckSessionService } from '../iframe/check-session.service'; import { SilentRenewService } from '../iframe/silent-renew.service'; import { LoggerService } from '../logging/logger.service'; -import { LoginResponse } from '../login/login-response'; +import type { LoginResponse } from '../login/login-response'; import { PopUpService } from '../login/popup/popup.service'; import { EventTypes } from '../public-events/event-types'; import { PublicEventsService } from '../public-events/public-events.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockAbstractProvider, mockProvider } from '../testing/mock'; import { UserService } from '../user-data/user.service'; import { CurrentUrlService } from '../utils/url/current-url.service'; import { AuthStateService } from './auth-state.service'; @@ -42,8 +46,9 @@ describe('CheckAuthService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule], + imports: [], providers: [ + mockRouterProvider(), mockProvider(CheckSessionService), mockProvider(SilentRenewService), mockProvider(UserService), @@ -60,9 +65,6 @@ describe('CheckAuthService', () => { mockProvider(StoragePersistenceService), ], }); - }); - - beforeEach(() => { checkAuthService = TestBed.inject(CheckAuthService); refreshSessionService = TestBed.inject(RefreshSessionService); userService = TestBed.inject(UserService); @@ -80,6 +82,7 @@ describe('CheckAuthService', () => { publicEventsService = TestBed.inject(PublicEventsService); }); + // biome-ignore lint/correctness/noUndeclaredVariables: afterEach(() => { storagePersistenceService.clear({} as OpenIdConfiguration); }); @@ -89,97 +92,95 @@ describe('CheckAuthService', () => { }); describe('checkAuth', () => { - it('uses config with matching state when url has state param and config with state param is stored', waitForAsync(() => { - spyOn(currentUrlService, 'getStateParamFromCurrentUrl').and.returnValue( - 'the-state-param' - ); + it('uses config with matching state when url has state param and config with state param is stored', async () => { + vi.spyOn( + currentUrlService, + 'getStateParamFromCurrentUrl' + ).mockReturnValue('the-state-param'); const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(storagePersistenceService, 'read') - .withArgs('authStateControl', allConfigs[0]) - .and.returnValue('the-state-param'); - const spy = spyOn( - checkAuthService as any, - 'checkAuthWithConfig' - ).and.callThrough(); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authStateControl', allConfigs[0]!], + () => 'the-state-param' + ); + const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith( - allConfigs[0], + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { + expect(spy).toHaveBeenCalledExactlyOnceWith( + allConfigs[0]!, allConfigs, undefined ); }); - })); + }); - it('throws error when url has state param and stored config with matching state param is not found', waitForAsync(() => { - spyOn(currentUrlService, 'getStateParamFromCurrentUrl').and.returnValue( - 'the-state-param' - ); + it('throws error when url has state param and stored config with matching state param is not found', async () => { + vi.spyOn( + currentUrlService, + 'getStateParamFromCurrentUrl' + ).mockReturnValue('the-state-param'); const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(storagePersistenceService, 'read') - .withArgs('authStateControl', allConfigs[0]) - .and.returnValue('not-matching-state-param'); - const spy = spyOn( - checkAuthService as any, - 'checkAuthWithConfig' - ).and.callThrough(); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authStateControl', allConfigs[0]!], + () => 'not-matching-state-param' + ); + const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe({ + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe({ error: (err) => { expect(err).toBeTruthy(); expect(spy).not.toHaveBeenCalled(); }, }); - })); + }); - it('uses first/default config when no param is passed', waitForAsync(() => { - spyOn(currentUrlService, 'getStateParamFromCurrentUrl').and.returnValue( - null - ); + it('uses first/default config when no param is passed', async () => { + vi.spyOn( + currentUrlService, + 'getStateParamFromCurrentUrl' + ).mockReturnValue(null); const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - const spy = spyOn( - checkAuthService as any, - 'checkAuthWithConfig' - ).and.callThrough(); + const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { + expect(spy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1', authority: 'some-authority' }, allConfigs, undefined ); }); - })); + }); - it('returns null and sendMessageToMainWindow if currently in a popup', waitForAsync(() => { + it('returns null and sendMessageToMainWindow if currently in a popup', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(popUpService as any, 'canAccessSessionStorage').and.returnValue( + vi.spyOn(popUpService as any, 'canAccessSessionStorage').mockReturnValue( true ); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOnProperty(popUpService as any, 'windowInternal').and.returnValue({ + vi.spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({ opener: {} as Window, }); - spyOn(storagePersistenceService, 'read').and.returnValue(null); + vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null); - spyOn(popUpService, 'isCurrentlyInPopup').and.returnValue(true); - const popupSpy = spyOn(popUpService, 'sendMessageToMainWindow'); + vi.spyOn(popUpService, 'isCurrentlyInPopup').mockReturnValue(true); + const popupSpy = vi.spyOn(popUpService, 'sendMessageToMainWindow'); checkAuthService - .checkAuth(allConfigs[0], allConfigs) + .checkAuth(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: false, @@ -191,29 +192,28 @@ describe('CheckAuthService', () => { }); expect(popupSpy).toHaveBeenCalled(); }); - })); + }); - it('returns isAuthenticated: false with error message in case handleCallbackAndFireEvents throws an error', waitForAsync(() => { + it('returns isAuthenticated: false with error message in case handleCallbackAndFireEvents throws an error', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'isCallback').and.returnValue(true); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(true); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const spy = spyOn( - callBackService, - 'handleCallbackAndFireEvents' - ).and.returnValue(throwError(() => new Error('ERROR'))); + const spy = vi + .spyOn(callBackService, 'handleCallbackAndFireEvents') + .mockReturnValue(throwError(() => new Error('ERROR'))); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); checkAuthService - .checkAuth(allConfigs[0], allConfigs) + .checkAuth(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: false, @@ -225,30 +225,29 @@ describe('CheckAuthService', () => { }); expect(spy).toHaveBeenCalled(); }); - })); + }); - it('calls callbackService.handlePossibleStsCallback with current url when callback is true', waitForAsync(() => { + it('calls callbackService.handlePossibleStsCallback with current url when callback is true', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'isCallback').and.returnValue(true); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(true); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(authStateService, 'getAccessToken').and.returnValue('at'); - spyOn(authStateService, 'getIdToken').and.returnValue('idt'); + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('at'); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idt'); - const spy = spyOn( - callBackService, - 'handleCallbackAndFireEvents' - ).and.returnValue(of({} as CallbackContext)); + const spy = vi + .spyOn(callBackService, 'handleCallbackAndFireEvents') + .mockReturnValue(of({} as CallbackContext)); checkAuthService - .checkAuth(allConfigs[0], allConfigs) + .checkAuth(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: true, @@ -259,31 +258,30 @@ describe('CheckAuthService', () => { }); expect(spy).toHaveBeenCalled(); }); - })); + }); - it('does NOT call handleCallbackAndFireEvents with current url when callback is false', waitForAsync(() => { + it('does NOT call handleCallbackAndFireEvents with current url when callback is false', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'isCallback').and.returnValue(false); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(false); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const spy = spyOn( - callBackService, - 'handleCallbackAndFireEvents' - ).and.returnValue(of({} as CallbackContext)); + const spy = vi + .spyOn(callBackService, 'handleCallbackAndFireEvents') + .mockReturnValue(of({} as CallbackContext)); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(authStateService, 'getAccessToken').and.returnValue('at'); - spyOn(authStateService, 'getIdToken').and.returnValue('idt'); + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('at'); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idt'); checkAuthService - .checkAuth(allConfigs[0], allConfigs) + .checkAuth(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: true, @@ -294,37 +292,37 @@ describe('CheckAuthService', () => { }); expect(spy).not.toHaveBeenCalled(); }); - })); + }); - it('does fire the auth and user data events when it is not a callback from the security token service and is authenticated', waitForAsync(() => { + it('does fire the auth and user data events when it is not a callback from the security token service and is authenticated', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'isCallback').and.returnValue(false); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(false); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(userService, 'getUserDataFromStore').and.returnValue({ + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue({ some: 'user-data', }); - spyOn(authStateService, 'getAccessToken').and.returnValue('at'); - spyOn(authStateService, 'getIdToken').and.returnValue('idt'); + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('at'); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idt'); - const setAuthorizedAndFireEventSpy = spyOn( + const setAuthorizedAndFireEventSpy = vi.spyOn( authStateService, 'setAuthenticatedAndFireEvent' ); - const userServiceSpy = spyOn(userService, 'publishUserDataIfExists'); + const userServiceSpy = vi.spyOn(userService, 'publishUserDataIfExists'); checkAuthService - .checkAuth(allConfigs[0], allConfigs) + .checkAuth(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: true, @@ -338,34 +336,34 @@ describe('CheckAuthService', () => { expect(setAuthorizedAndFireEventSpy).toHaveBeenCalled(); expect(userServiceSpy).toHaveBeenCalled(); }); - })); + }); - it('does NOT fire the auth and user data events when it is not a callback from the security token service and is NOT authenticated', waitForAsync(() => { + it('does NOT fire the auth and user data events when it is not a callback from the security token service and is NOT authenticated', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'isCallback').and.returnValue(false); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(false); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - spyOn(authStateService, 'getAccessToken').and.returnValue('at'); - spyOn(authStateService, 'getIdToken').and.returnValue('it'); - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('at'); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('it'); + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - const setAuthorizedAndFireEventSpy = spyOn( + const setAuthorizedAndFireEventSpy = vi.spyOn( authStateService, 'setAuthenticatedAndFireEvent' ); - const userServiceSpy = spyOn(userService, 'publishUserDataIfExists'); + const userServiceSpy = vi.spyOn(userService, 'publishUserDataIfExists'); checkAuthService - .checkAuth(allConfigs[0], allConfigs) + .checkAuth(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: false, @@ -377,27 +375,27 @@ describe('CheckAuthService', () => { expect(setAuthorizedAndFireEventSpy).not.toHaveBeenCalled(); expect(userServiceSpy).not.toHaveBeenCalled(); }); - })); + }); - it('if authenticated return true', waitForAsync(() => { + it('if authenticated return true', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(authStateService, 'getAccessToken').and.returnValue('at'); - spyOn(authStateService, 'getIdToken').and.returnValue('idt'); - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('at'); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idt'); + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); checkAuthService - .checkAuth(allConfigs[0], allConfigs) + .checkAuth(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: true, @@ -407,273 +405,279 @@ describe('CheckAuthService', () => { idToken: 'idt', }); }); - })); + }); - it('if authenticated set auth and fires event ', waitForAsync(() => { + it('if authenticated set auth and fires event ', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(callBackService, 'isCallback').and.returnValue(false); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(false); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const spy = spyOn(authStateService, 'setAuthenticatedAndFireEvent'); + const spy = vi.spyOn(authStateService, 'setAuthenticatedAndFireEvent'); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { expect(spy).toHaveBeenCalled(); }); - })); + }); - it('if authenticated publishUserdataIfExists', waitForAsync(() => { + it('if authenticated publishUserdataIfExists', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const spy = spyOn(userService, 'publishUserDataIfExists'); + const spy = vi.spyOn(userService, 'publishUserDataIfExists'); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { expect(spy).toHaveBeenCalled(); }); - })); + }); - it('if authenticated callbackService startTokenValidationPeriodically', waitForAsync(() => { + it('if authenticated callbackService startTokenValidationPeriodically', async () => { const config = { authority: 'authority', tokenRefreshInSeconds: 7, }; const allConfigs = [config]; - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - const spy = spyOn( + const spy = vi.spyOn( periodicallyTokenCheckService, 'startTokenValidationPeriodically' ); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { expect(spy).toHaveBeenCalled(); }); - })); + }); - it('if isCheckSessionConfigured call checkSessionService.start()', waitForAsync(() => { + it('if isCheckSessionConfigured call checkSessionService.start()', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - spyOn(checkSessionService, 'isCheckSessionConfigured').and.returnValue( + vi.spyOn(checkSessionService, 'isCheckSessionConfigured').mockReturnValue( true ); - const spy = spyOn(checkSessionService, 'start'); + const spy = vi.spyOn(checkSessionService, 'start'); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { expect(spy).toHaveBeenCalled(); }); - })); + }); - it('if isSilentRenewConfigured call getOrCreateIframe()', waitForAsync(() => { + it('if isSilentRenewConfigured call getOrCreateIframe()', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - spyOn(silentRenewService, 'isSilentRenewConfigured').and.returnValue( + vi.spyOn(silentRenewService, 'isSilentRenewConfigured').mockReturnValue( true ); - const spy = spyOn(silentRenewService, 'getOrCreateIframe'); + const spy = vi.spyOn(silentRenewService, 'getOrCreateIframe'); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { expect(spy).toHaveBeenCalled(); }); - })); + }); - it('calls checkSavedRedirectRouteAndNavigate if authenticated', waitForAsync(() => { + it('calls checkSavedRedirectRouteAndNavigate if authenticated', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const spy = spyOn(autoLoginService, 'checkSavedRedirectRouteAndNavigate'); + const spy = vi.spyOn( + autoLoginService, + 'checkSavedRedirectRouteAndNavigate' + ); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledOnceWith(allConfigs[0]); + expect(spy).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); }); - })); + }); - it('does not call checkSavedRedirectRouteAndNavigate if not authenticated', waitForAsync(() => { + it('does not call checkSavedRedirectRouteAndNavigate if not authenticated', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const spy = spyOn(autoLoginService, 'checkSavedRedirectRouteAndNavigate'); + const spy = vi.spyOn( + autoLoginService, + 'checkSavedRedirectRouteAndNavigate' + ); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { expect(spy).toHaveBeenCalledTimes(0); }); - })); + }); - it('fires CheckingAuth-Event on start and finished event on end', waitForAsync(() => { + it('fires CheckingAuth-Event on start and finished event on end', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const fireEventSpy = spyOn(publicEventsService, 'fireEvent'); + const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent'); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { - expect(fireEventSpy.calls.allArgs()).toEqual([ + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { + expect(fireEventSpy).toHaveBeenCalledWith([ [EventTypes.CheckingAuth], [EventTypes.CheckingAuthFinished], ]); }); - })); + }); - it('fires CheckingAuth-Event on start and CheckingAuthFinishedWithError event on end if exception occurs', waitForAsync(() => { + it('fires CheckingAuth-Event on start and CheckingAuthFinishedWithError event on end if exception occurs', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - const fireEventSpy = spyOn(publicEventsService, 'fireEvent'); + const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent'); - spyOn(callBackService, 'isCallback').and.returnValue(true); - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(true); + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( throwError(() => new Error('ERROR')) ); - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { - expect(fireEventSpy.calls.allArgs()).toEqual([ + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { + expect(fireEventSpy).toHaveBeenCalledWith([ [EventTypes.CheckingAuth], [EventTypes.CheckingAuthFinishedWithError, 'ERROR'], ]); }); - })); + }); - it('fires CheckingAuth-Event on start and finished event on end if not authenticated', waitForAsync(() => { + it('fires CheckingAuth-Event on start and finished event on end if not authenticated', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(currentUrlService, 'getCurrentUrl').and.returnValue( + vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const fireEventSpy = spyOn(publicEventsService, 'fireEvent'); + const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent'); - checkAuthService.checkAuth(allConfigs[0], allConfigs).subscribe(() => { - expect(fireEventSpy.calls.allArgs()).toEqual([ + checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { + expect(fireEventSpy).toBeCalledWith([ [EventTypes.CheckingAuth], [EventTypes.CheckingAuthFinished], ]); }); - })); + }); }); describe('checkAuthIncludingServer', () => { - it('if isSilentRenewConfigured call getOrCreateIframe()', waitForAsync(() => { + it('if isSilentRenewConfigured call getOrCreateIframe()', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue( + vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue( of({ isAuthenticated: true } as LoginResponse) ); - spyOn(silentRenewService, 'isSilentRenewConfigured').and.returnValue( + vi.spyOn(silentRenewService, 'isSilentRenewConfigured').mockReturnValue( true ); - const spy = spyOn(silentRenewService, 'getOrCreateIframe'); + const spy = vi.spyOn(silentRenewService, 'getOrCreateIframe'); checkAuthService - .checkAuthIncludingServer(allConfigs[0], allConfigs) + .checkAuthIncludingServer(allConfigs[0]!, allConfigs) .subscribe(() => { expect(spy).toHaveBeenCalled(); }); - })); + }); - it('does forceRefreshSession get called and is NOT authenticated', waitForAsync(() => { + it('does forceRefreshSession get called and is NOT authenticated', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'isCallback').and.returnValue(false); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(false); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue( + vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue( of({ idToken: 'idToken', accessToken: 'access_token', @@ -684,42 +688,45 @@ describe('CheckAuthService', () => { ); checkAuthService - .checkAuthIncludingServer(allConfigs[0], allConfigs) + .checkAuthIncludingServer(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toBeTruthy(); }); - })); + }); - it('should start check session and validation after forceRefreshSession has been called and is authenticated after forcing with silentrenew', waitForAsync(() => { + it('should start check session and validation after forceRefreshSession has been called and is authenticated after forcing with silentrenew', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'isCallback').and.returnValue(false); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(false); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(checkSessionService, 'isCheckSessionConfigured').and.returnValue( + vi.spyOn(checkSessionService, 'isCheckSessionConfigured').mockReturnValue( true ); - spyOn(silentRenewService, 'isSilentRenewConfigured').and.returnValue( + vi.spyOn(silentRenewService, 'isSilentRenewConfigured').mockReturnValue( true ); - const checkSessionServiceStartSpy = spyOn(checkSessionService, 'start'); - const periodicallyTokenCheckServiceSpy = spyOn( + const checkSessionServiceStartSpy = vi.spyOn( + checkSessionService, + 'start' + ); + const periodicallyTokenCheckServiceSpy = vi.spyOn( periodicallyTokenCheckService, 'startTokenValidationPeriodically' ); - const getOrCreateIframeSpy = spyOn( + const getOrCreateIframeSpy = vi.spyOn( silentRenewService, 'getOrCreateIframe' ); - spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue( + vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue( of({ idToken: 'idToken', accessToken: 'access_token', @@ -730,46 +737,51 @@ describe('CheckAuthService', () => { ); checkAuthService - .checkAuthIncludingServer(allConfigs[0], allConfigs) + .checkAuthIncludingServer(allConfigs[0]!, allConfigs) .subscribe(() => { - expect(checkSessionServiceStartSpy).toHaveBeenCalledOnceWith( + expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith( allConfigs[0] ); expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1); - expect(getOrCreateIframeSpy).toHaveBeenCalledOnceWith(allConfigs[0]); + expect(getOrCreateIframeSpy).toHaveBeenCalledExactlyOnceWith( + allConfigs[0] + ); }); - })); + }); - it('should start check session and validation after forceRefreshSession has been called and is authenticated after forcing without silentrenew', waitForAsync(() => { + it('should start check session and validation after forceRefreshSession has been called and is authenticated after forcing without silentrenew', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority' }, ]; - spyOn(callBackService, 'isCallback').and.returnValue(false); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(callBackService, 'isCallback').mockReturnValue(false); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue( + vi.spyOn(callBackService, 'handleCallbackAndFireEvents').mockReturnValue( of({} as CallbackContext) ); - spyOn(checkSessionService, 'isCheckSessionConfigured').and.returnValue( + vi.spyOn(checkSessionService, 'isCheckSessionConfigured').mockReturnValue( true ); - spyOn(silentRenewService, 'isSilentRenewConfigured').and.returnValue( + vi.spyOn(silentRenewService, 'isSilentRenewConfigured').mockReturnValue( false ); - const checkSessionServiceStartSpy = spyOn(checkSessionService, 'start'); - const periodicallyTokenCheckServiceSpy = spyOn( + const checkSessionServiceStartSpy = vi.spyOn( + checkSessionService, + 'start' + ); + const periodicallyTokenCheckServiceSpy = vi.spyOn( periodicallyTokenCheckService, 'startTokenValidationPeriodically' ); - const getOrCreateIframeSpy = spyOn( + const getOrCreateIframeSpy = vi.spyOn( silentRenewService, 'getOrCreateIframe' ); - spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue( + vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue( of({ idToken: 'idToken', accessToken: 'access_token', @@ -780,69 +792,67 @@ describe('CheckAuthService', () => { ); checkAuthService - .checkAuthIncludingServer(allConfigs[0], allConfigs) + .checkAuthIncludingServer(allConfigs[0]!, allConfigs) .subscribe(() => { - expect(checkSessionServiceStartSpy).toHaveBeenCalledOnceWith( + expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith( allConfigs[0] ); expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1); expect(getOrCreateIframeSpy).not.toHaveBeenCalled(); }); - })); + }); }); describe('checkAuthMultiple', () => { - it('uses config with matching state when url has state param and config with state param is stored', waitForAsync(() => { + it('uses config with matching state when url has state param and config with state param is stored', async () => { const allConfigs = [ { configId: 'configId1', authority: 'some-authority1' }, { configId: 'configId2', authority: 'some-authority2' }, ]; - spyOn(currentUrlService, 'getStateParamFromCurrentUrl').and.returnValue( - 'the-state-param' + vi.spyOn( + currentUrlService, + 'getStateParamFromCurrentUrl' + ).mockReturnValue('the-state-param'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authStateControl', allConfigs[0]!], + () => 'the-state-param' ); - spyOn(storagePersistenceService, 'read') - .withArgs('authStateControl', allConfigs[0]) - .and.returnValue('the-state-param'); - const spy = spyOn( - checkAuthService as any, - 'checkAuthWithConfig' - ).and.callThrough(); + const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); checkAuthService.checkAuthMultiple(allConfigs).subscribe((result) => { expect(Array.isArray(result)).toBe(true); expect(spy).toHaveBeenCalledTimes(2); - expect(spy.calls.argsFor(0)).toEqual([ - allConfigs[0], + expect(vi.mocked(spy).mock.calls[0]).toEqual([ + allConfigs[0]!, allConfigs, undefined, ]); - expect(spy.calls.argsFor(1)).toEqual([ + expect(vi.mocked(spy).mock.calls[1]).toEqual([ allConfigs[1], allConfigs, undefined, ]); }); - })); + }); - it('uses config from passed configId if configId was passed and returns all results', waitForAsync(() => { - spyOn(currentUrlService, 'getStateParamFromCurrentUrl').and.returnValue( - null - ); + it('uses config from passed configId if configId was passed and returns all results', async () => { + vi.spyOn( + currentUrlService, + 'getStateParamFromCurrentUrl' + ).mockReturnValue(null); const allConfigs = [ { configId: 'configId1', authority: 'some-authority1' }, { configId: 'configId2', authority: 'some-authority2' }, ]; - const spy = spyOn( - checkAuthService as any, - 'checkAuthWithConfig' - ).and.callThrough(); + const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); checkAuthService.checkAuthMultiple(allConfigs).subscribe((result) => { expect(Array.isArray(result)).toBe(true); - expect(spy.calls.allArgs()).toEqual([ + expect(spy).toBeCalledWith([ [ { configId: 'configId1', authority: 'some-authority1' }, allConfigs, @@ -855,43 +865,42 @@ describe('CheckAuthService', () => { ], ]); }); - })); + }); - it('runs through all configs if no parameter is passed and has no state in url', waitForAsync(() => { - spyOn(currentUrlService, 'getStateParamFromCurrentUrl').and.returnValue( - null - ); + it('runs through all configs if no parameter is passed and has no state in url', async () => { + vi.spyOn( + currentUrlService, + 'getStateParamFromCurrentUrl' + ).mockReturnValue(null); const allConfigs = [ { configId: 'configId1', authority: 'some-authority1' }, { configId: 'configId2', authority: 'some-authority2' }, ]; - const spy = spyOn( - checkAuthService as any, - 'checkAuthWithConfig' - ).and.callThrough(); + const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); checkAuthService.checkAuthMultiple(allConfigs).subscribe((result) => { expect(Array.isArray(result)).toBe(true); expect(spy).toHaveBeenCalledTimes(2); - expect(spy.calls.argsFor(0)).toEqual([ + expect(vi.mocked(spy).mock.calls[0]).toEqual([ { configId: 'configId1', authority: 'some-authority1' }, allConfigs, undefined, ]); - expect(spy.calls.argsFor(1)).toEqual([ + expect(vi.mocked(spy).mock.calls[1]).toEqual([ { configId: 'configId2', authority: 'some-authority2' }, allConfigs, undefined, ]); }); - })); + }); - it('throws error if url has state param but no config could be found', waitForAsync(() => { - spyOn(currentUrlService, 'getStateParamFromCurrentUrl').and.returnValue( - 'the-state-param' - ); + it('throws error if url has state param but no config could be found', async () => { + vi.spyOn( + currentUrlService, + 'getStateParamFromCurrentUrl' + ).mockReturnValue('the-state-param'); const allConfigs: OpenIdConfiguration[] = []; @@ -902,6 +911,6 @@ describe('CheckAuthService', () => { ); }, }); - })); + }); }); }); diff --git a/src/auth-state/check-auth.service.ts b/src/auth-state/check-auth.service.ts index eaddadc..b2d6d9c 100644 --- a/src/auth-state/check-auth.service.ts +++ b/src/auth-state/check-auth.service.ts @@ -1,15 +1,15 @@ -import { inject, Injectable } from 'injection-js'; -import { forkJoin, Observable, of, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, forkJoin, of, throwError } from 'rxjs'; import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { AutoLoginService } from '../auto-login/auto-login.service'; import { CallbackService } from '../callback/callback.service'; import { PeriodicallyTokenCheckService } from '../callback/periodically-token-check.service'; import { RefreshSessionService } from '../callback/refresh-session.service'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { CheckSessionService } from '../iframe/check-session.service'; import { SilentRenewService } from '../iframe/silent-renew.service'; import { LoggerService } from '../logging/logger.service'; -import { LoginResponse } from '../login/login-response'; +import type { LoginResponse } from '../login/login-response'; import { PopUpService } from '../login/popup/popup.service'; import { EventTypes } from '../public-events/event-types'; import { PublicEventsService } from '../public-events/public-events.service'; diff --git a/src/auth.module.spec.ts b/src/auth.module.spec.ts index 8e06b77..2507550 100644 --- a/src/auth.module.spec.ts +++ b/src/auth.module.spec.ts @@ -1,6 +1,6 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../test/auto-mock'; +import { vi } from 'vitest'; import { PASSED_CONFIG } from './auth-config'; import { AuthModule } from './auth.module'; import { ConfigurationService } from './config/config.service'; @@ -9,19 +9,20 @@ import { StsConfigLoader, StsConfigStaticLoader, } from './config/loader/config-loader'; +import { mockProvider } from './testing/mock'; describe('AuthModule', () => { describe('APP_CONFIG', () => { - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + beforeEach(async () => { + await TestBed.configureTestingModule({ imports: [AuthModule.forRoot({ config: { authority: 'something' } })], providers: [mockProvider(ConfigurationService)], }).compileComponents(); - })); + }); it('should create', () => { expect(AuthModule).toBeDefined(); - expect(AuthModule.forRoot({})).toBeDefined(); + expect(new AuthModule()).toBeDefined(); }); it('should provide config', () => { @@ -38,8 +39,8 @@ describe('AuthModule', () => { }); describe('StsConfigHttpLoader', () => { - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + beforeEach(async () => { + await TestBed.configureTestingModule({ imports: [ AuthModule.forRoot({ loader: { @@ -50,7 +51,7 @@ describe('AuthModule', () => { ], providers: [mockProvider(ConfigurationService)], }).compileComponents(); - })); + }); it('should create StsConfigStaticLoader if config is passed', () => { const configLoader = TestBed.inject(StsConfigLoader); diff --git a/src/auth.module.ts b/src/auth.module.ts index 79c734c..a3e2b6b 100644 --- a/src/auth.module.ts +++ b/src/auth.module.ts @@ -1,25 +1,41 @@ -import { CommonModule } from '@angular/common'; import { - provideHttpClient, - withInterceptorsFromDi, -} from '@ngify/http'; -import { ModuleWithProviders, NgModule } from 'injection-js'; -import { PassedInitialConfig } from './auth-config'; + type InjectionToken, + Injector, + ReflectiveInjector, + type Type, +} from 'injection-js'; +import type { PassedInitialConfig } from './auth-config'; +import type { Module } from './injection'; import { _provideAuth } from './provide-auth'; -@NgModule({ - declarations: [], - exports: [], - imports: [CommonModule], - providers: [provideHttpClient(withInterceptorsFromDi())], -}) -export class AuthModule { - static forRoot( - passedConfig: PassedInitialConfig - ): ModuleWithProviders { - return { - ngModule: AuthModule, - providers: [..._provideAuth(passedConfig)], - }; +export interface AuthModuleOptions { + passedConfig: PassedInitialConfig; + parentInjector?: ReflectiveInjector; +} + +export class AuthModule extends Injector { + passedConfig: PassedInitialConfig; + injector: ReflectiveInjector; + parentInjector?: Injector; + + constructor(passedConfig?: PassedInitialConfig, parentInjector?: Injector) { + super(); + this.passedConfig = passedConfig ?? {}; + this.parentInjector = parentInjector; + this.injector = ReflectiveInjector.resolveAndCreate( + [..._provideAuth(this.passedConfig)], + this.parentInjector + ); + } + + static forRoot(passedConfig?: PassedInitialConfig): Module { + return (parentInjector?: Injector) => + new AuthModule(passedConfig, parentInjector); + } + + get(token: Type | InjectionToken, notFoundValue?: T): T; + get(token: any, notFoundValue?: any); + get(token: unknown, notFoundValue?: unknown): any { + return this.injector.get(token, notFoundValue); } } diff --git a/src/auto-login/auto-login-partial-routes.guard.spec.ts b/src/auto-login/auto-login-partial-routes.guard.spec.ts index 672e12b..3e0b55c 100644 --- a/src/auto-login/auto-login-partial-routes.guard.spec.ts +++ b/src/auto-login/auto-login-partial-routes.guard.spec.ts @@ -1,17 +1,17 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed, mockRouterProvider } from '@/testing'; import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; + AbstractRouter, + type ActivatedRouteSnapshot, + type RouterStateSnapshot, +} from 'oidc-client-rx'; import { of } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; import { CheckAuthService } from '../auth-state/check-auth.service'; import { ConfigurationService } from '../config/config.service'; import { LoginService } from '../login/login.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockProvider } from '../testing/mock'; import { AutoLoginPartialRoutesGuard, autoLoginPartialRoutesGuard, @@ -19,11 +19,12 @@ import { } from './auto-login-partial-routes.guard'; import { AutoLoginService } from './auto-login.service'; -describe(`AutoLoginPartialRoutesGuard`, () => { +describe('AutoLoginPartialRoutesGuard', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule], + imports: [], providers: [ + mockRouterProvider(), AutoLoginService, mockProvider(AuthStateService), mockProvider(LoginService), @@ -41,7 +42,7 @@ describe(`AutoLoginPartialRoutesGuard`, () => { let storagePersistenceService: StoragePersistenceService; let configurationService: ConfigurationService; let autoLoginService: AutoLoginService; - let router: Router; + let router: AbstractRouter; beforeEach(() => { authStateService = TestBed.inject(AuthStateService); @@ -49,15 +50,16 @@ describe(`AutoLoginPartialRoutesGuard`, () => { storagePersistenceService = TestBed.inject(StoragePersistenceService); configurationService = TestBed.inject(ConfigurationService); - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of({ configId: 'configId1' }) ); guard = TestBed.inject(AutoLoginPartialRoutesGuard); autoLoginService = TestBed.inject(AutoLoginService); - router = TestBed.inject(Router); + router = TestBed.inject(AbstractRouter); }); + // biome-ignore lint/correctness/noUndeclaredVariables: afterEach(() => { storagePersistenceService.clear({}); }); @@ -67,19 +69,19 @@ describe(`AutoLoginPartialRoutesGuard`, () => { }); describe('canActivate', () => { - it('should save current route and call `login` if not authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should save current route and call `login` if not authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); guard .canActivate( @@ -87,32 +89,32 @@ describe(`AutoLoginPartialRoutesGuard`, () => { { url: 'some-url1' } as RouterStateSnapshot ) .subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'some-url1' ); - expect(loginSpy).toHaveBeenCalledOnceWith({ + expect(loginSpy).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1', }); expect( checkSavedRedirectRouteAndNavigateSpy ).not.toHaveBeenCalled(); }); - })); + }); - it('should save current route and call `login` if not authenticated already and add custom params', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should save current route and call `login` if not authenticated already and add custom params', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); guard .canActivate( @@ -120,11 +122,11 @@ describe(`AutoLoginPartialRoutesGuard`, () => { { url: 'some-url1' } as RouterStateSnapshot ) .subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'some-url1' ); - expect(loginSpy).toHaveBeenCalledOnceWith( + expect(loginSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, { customParams: { custom: 'param' } } ); @@ -132,21 +134,21 @@ describe(`AutoLoginPartialRoutesGuard`, () => { checkSavedRedirectRouteAndNavigateSpy ).not.toHaveBeenCalled(); }); - })); + }); - it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); guard .canActivate( @@ -158,25 +160,25 @@ describe(`AutoLoginPartialRoutesGuard`, () => { expect(loginSpy).not.toHaveBeenCalled(); expect( checkSavedRedirectRouteAndNavigateSpy - ).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + ).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' }); }); - })); + }); }); describe('canActivateChild', () => { - it('should save current route and call `login` if not authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should save current route and call `login` if not authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); guard .canActivateChild( @@ -184,32 +186,32 @@ describe(`AutoLoginPartialRoutesGuard`, () => { { url: 'some-url1' } as RouterStateSnapshot ) .subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'some-url1' ); - expect(loginSpy).toHaveBeenCalledOnceWith({ + expect(loginSpy).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1', }); expect( checkSavedRedirectRouteAndNavigateSpy ).not.toHaveBeenCalled(); }); - })); + }); - it('should save current route and call `login` if not authenticated already with custom params', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should save current route and call `login` if not authenticated already with custom params', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); guard .canActivateChild( @@ -217,11 +219,11 @@ describe(`AutoLoginPartialRoutesGuard`, () => { { url: 'some-url1' } as RouterStateSnapshot ) .subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'some-url1' ); - expect(loginSpy).toHaveBeenCalledOnceWith( + expect(loginSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, { customParams: { custom: 'param' } } ); @@ -229,21 +231,21 @@ describe(`AutoLoginPartialRoutesGuard`, () => { checkSavedRedirectRouteAndNavigateSpy ).not.toHaveBeenCalled(); }); - })); + }); - it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); guard .canActivateChild( @@ -255,51 +257,53 @@ describe(`AutoLoginPartialRoutesGuard`, () => { expect(loginSpy).not.toHaveBeenCalled(); expect( checkSavedRedirectRouteAndNavigateSpy - ).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + ).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' }); }); - })); + }); }); describe('canLoad', () => { - it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should save current route (empty) and call `login` if not authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); guard.canLoad().subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, '' ); - expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + expect(loginSpy).toHaveBeenCalledExactlyOnceWith({ + configId: 'configId1', + }); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); }); - })); + }); - it('should save current route (with router extractedUrl) and call `login` if not authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should save current route (with router extractedUrl) and call `login` if not authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); - spyOn(router, 'getCurrentNavigation').and.returnValue({ + vi.spyOn(router, 'getCurrentNavigation').mockReturnValue({ extractedUrl: router.parseUrl( 'some-url12/with/some-param?queryParam=true' ), @@ -311,37 +315,39 @@ describe(`AutoLoginPartialRoutesGuard`, () => { }); guard.canLoad().subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'some-url12/with/some-param?queryParam=true' ); - expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + expect(loginSpy).toHaveBeenCalledExactlyOnceWith({ + configId: 'configId1', + }); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); }); - })); + }); - it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); guard.canLoad().subscribe(() => { expect(saveRedirectRouteSpy).not.toHaveBeenCalled(); expect(loginSpy).not.toHaveBeenCalled(); expect( checkSavedRedirectRouteAndNavigateSpy - ).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + ).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' }); }); - })); + }); }); }); @@ -352,7 +358,7 @@ describe(`AutoLoginPartialRoutesGuard`, () => { let storagePersistenceService: StoragePersistenceService; let configurationService: ConfigurationService; let autoLoginService: AutoLoginService; - let router: Router; + let router: AbstractRouter; beforeEach(() => { authStateService = TestBed.inject(AuthStateService); @@ -360,48 +366,52 @@ describe(`AutoLoginPartialRoutesGuard`, () => { storagePersistenceService = TestBed.inject(StoragePersistenceService); configurationService = TestBed.inject(ConfigurationService); - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( - of({ configId: 'configId1' }) - ); + vi.spyOn( + configurationService, + 'getOpenIDConfiguration' + ).mockReturnValue(of({ configId: 'configId1' })); autoLoginService = TestBed.inject(AutoLoginService); - router = TestBed.inject(Router); + router = TestBed.inject(AbstractRouter); }); + // biome-ignore lint/correctness/noUndeclaredVariables: afterEach(() => { storagePersistenceService.clear({}); }); - it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should save current route (empty) and call `login` if not authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); const guard$ = TestBed.runInInjectionContext( autoLoginPartialRoutesGuard ); guard$.subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, '' ); - expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + expect(loginSpy).toHaveBeenCalledExactlyOnceWith({ + configId: 'configId1', + }); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); }); - })); + }); - it('should save current route (with router extractedUrl) and call `login` if not authenticated already', waitForAsync(() => { - spyOn(router, 'getCurrentNavigation').and.returnValue({ + it('should save current route (with router extractedUrl) and call `login` if not authenticated already', async () => { + vi.spyOn(router, 'getCurrentNavigation').mockReturnValue({ extractedUrl: router.parseUrl( 'some-url12/with/some-param?queryParam=true' ), @@ -412,46 +422,48 @@ describe(`AutoLoginPartialRoutesGuard`, () => { trigger: 'imperative', }); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); const guard$ = TestBed.runInInjectionContext( autoLoginPartialRoutesGuard ); guard$.subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'some-url12/with/some-param?queryParam=true' ); - expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + expect(loginSpy).toHaveBeenCalledExactlyOnceWith({ + configId: 'configId1', + }); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); }); - })); + }); - it('should save current route and call `login` if not authenticated already and add custom params', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should save current route and call `login` if not authenticated already and add custom params', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); const guard$ = TestBed.runInInjectionContext(() => autoLoginPartialRoutesGuard({ @@ -460,31 +472,31 @@ describe(`AutoLoginPartialRoutesGuard`, () => { ); guard$.subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, '' ); - expect(loginSpy).toHaveBeenCalledOnceWith( + expect(loginSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, { customParams: { custom: 'param' } } ); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); }); - })); + }); - it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); const guard$ = TestBed.runInInjectionContext( autoLoginPartialRoutesGuard @@ -495,9 +507,9 @@ describe(`AutoLoginPartialRoutesGuard`, () => { expect(loginSpy).not.toHaveBeenCalled(); expect( checkSavedRedirectRouteAndNavigateSpy - ).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + ).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' }); }); - })); + }); }); describe('autoLoginPartialRoutesGuardWithConfig', () => { @@ -513,44 +525,48 @@ describe(`AutoLoginPartialRoutesGuard`, () => { storagePersistenceService = TestBed.inject(StoragePersistenceService); configurationService = TestBed.inject(ConfigurationService); - spyOn(configurationService, 'getOpenIDConfiguration').and.callFake( - (configId) => of({ configId }) - ); + vi.spyOn( + configurationService, + 'getOpenIDConfiguration' + ).mockImplementation((configId) => of({ configId })); autoLoginService = TestBed.inject(AutoLoginService); }); + // biome-ignore lint/correctness/noUndeclaredVariables: afterEach(() => { storagePersistenceService.clear({}); }); - it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => { - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + it('should save current route (empty) and call `login` if not authenticated already', async () => { + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const checkSavedRedirectRouteAndNavigateSpy = spyOn( + const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn( autoLoginService, 'checkSavedRedirectRouteAndNavigate' ); - const saveRedirectRouteSpy = spyOn( + const saveRedirectRouteSpy = vi.spyOn( autoLoginService, 'saveRedirectRoute' ); - const loginSpy = spyOn(loginService, 'login'); + const loginSpy = vi.spyOn(loginService, 'login'); const guard$ = TestBed.runInInjectionContext( autoLoginPartialRoutesGuardWithConfig('configId1') ); guard$.subscribe(() => { - expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith( + expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, '' ); - expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' }); + expect(loginSpy).toHaveBeenCalledExactlyOnceWith({ + configId: 'configId1', + }); expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled(); }); - })); + }); }); }); }); diff --git a/src/auto-login/auto-login-partial-routes.guard.ts b/src/auto-login/auto-login-partial-routes.guard.ts index 96144f1..8c50c42 100644 --- a/src/auto-login/auto-login-partial-routes.guard.ts +++ b/src/auto-login/auto-login-partial-routes.guard.ts @@ -1,15 +1,16 @@ -import { inject, Injectable } from 'injection-js'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { Observable } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import type { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { AuthOptions } from '../auth-options'; +import type { AuthOptions } from '../auth-options'; import { AuthStateService } from '../auth-state/auth-state.service'; import { ConfigurationService } from '../config/config.service'; +import { injectAbstractType } from '../injection'; import { LoginService } from '../login/login.service'; +import { + AbstractRouter, + type ActivatedRouteSnapshot, + type RouterStateSnapshot, +} from '../router'; import { AutoLoginService } from './auto-login.service'; @Injectable() @@ -22,7 +23,7 @@ export class AutoLoginPartialRoutesGuard { private readonly configurationService = inject(ConfigurationService); - private readonly router = inject(Router); + private readonly router = injectAbstractType(AbstractRouter); canLoad(): Observable { const url = @@ -79,14 +80,14 @@ export class AutoLoginPartialRoutesGuard { export function autoLoginPartialRoutesGuard( route?: ActivatedRouteSnapshot, - state?: RouterStateSnapshot, + _state?: RouterStateSnapshot, configId?: string ): Observable { const configurationService = inject(ConfigurationService); const authStateService = inject(AuthStateService); const loginService = inject(LoginService); const autoLoginService = inject(AutoLoginService); - const router = inject(Router); + const router = injectAbstractType(AbstractRouter); const authOptions: AuthOptions | undefined = route?.data ? { customParams: route.data } : undefined; diff --git a/src/auto-login/auto-login.service.spec.ts b/src/auto-login/auto-login.service.spec.ts index b0f685a..f738c47 100644 --- a/src/auto-login/auto-login.service.spec.ts +++ b/src/auto-login/auto-login.service.spec.ts @@ -1,8 +1,9 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockProvider } from '../testing/mock'; import { AutoLoginService } from './auto-login.service'; describe('AutoLoginService ', () => { @@ -29,11 +30,11 @@ describe('AutoLoginService ', () => { describe('checkSavedRedirectRouteAndNavigate', () => { it('if not route is saved, router and delete are not called', () => { - const deleteSpy = spyOn(storagePersistenceService, 'remove'); - const routerSpy = spyOn(router, 'navigateByUrl'); - const readSpy = spyOn(storagePersistenceService, 'read').and.returnValue( - null - ); + const deleteSpy = vi.spyOn(storagePersistenceService, 'remove'); + const routerSpy = vi.spyOn(router, 'navigateByUrl'); + const readSpy = vi + .spyOn(storagePersistenceService, 'read') + .mockReturnValue(null); autoLoginService.checkSavedRedirectRouteAndNavigate({ configId: 'configId1', @@ -41,27 +42,27 @@ describe('AutoLoginService ', () => { expect(deleteSpy).not.toHaveBeenCalled(); expect(routerSpy).not.toHaveBeenCalled(); - expect(readSpy).toHaveBeenCalledOnceWith('redirect', { + expect(readSpy).toHaveBeenCalledExactlyOnceWith('redirect', { configId: 'configId1', }); }); it('if route is saved, router and delete are called', () => { - const deleteSpy = spyOn(storagePersistenceService, 'remove'); - const routerSpy = spyOn(router, 'navigateByUrl'); - const readSpy = spyOn(storagePersistenceService, 'read').and.returnValue( - 'saved-route' - ); + const deleteSpy = vi.spyOn(storagePersistenceService, 'remove'); + const routerSpy = vi.spyOn(router, 'navigateByUrl'); + const readSpy = vi + .spyOn(storagePersistenceService, 'read') + .mockReturnValue('saved-route'); autoLoginService.checkSavedRedirectRouteAndNavigate({ configId: 'configId1', }); - expect(deleteSpy).toHaveBeenCalledOnceWith('redirect', { + expect(deleteSpy).toHaveBeenCalledExactlyOnceWith('redirect', { configId: 'configId1', }); - expect(routerSpy).toHaveBeenCalledOnceWith('saved-route'); - expect(readSpy).toHaveBeenCalledOnceWith('redirect', { + expect(routerSpy).toHaveBeenCalledExactlyOnceWith('saved-route'); + expect(readSpy).toHaveBeenCalledExactlyOnceWith('redirect', { configId: 'configId1', }); }); @@ -69,16 +70,20 @@ describe('AutoLoginService ', () => { describe('saveRedirectRoute', () => { it('calls storageService with correct params', () => { - const writeSpy = spyOn(storagePersistenceService, 'write'); + const writeSpy = vi.spyOn(storagePersistenceService, 'write'); autoLoginService.saveRedirectRoute( { configId: 'configId1' }, 'some-route' ); - expect(writeSpy).toHaveBeenCalledOnceWith('redirect', 'some-route', { - configId: 'configId1', - }); + expect(writeSpy).toHaveBeenCalledExactlyOnceWith( + 'redirect', + 'some-route', + { + configId: 'configId1', + } + ); }); }); }); diff --git a/src/callback/callback.service.spec.ts b/src/callback/callback.service.spec.ts index 75f67b6..68fbda9 100644 --- a/src/callback/callback.service.spec.ts +++ b/src/callback/callback.service.spec.ts @@ -1,7 +1,8 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { Observable, of } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; -import { CallbackContext } from '../flows/callback-context'; +import { TestBed } from '@/testing'; +import { Observable, lastValueFrom, of } from 'rxjs'; +import { vi } from 'vitest'; +import type { CallbackContext } from '../flows/callback-context'; +import { mockProvider } from '../testing/mock'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { UrlService } from '../utils/url/url.service'; import { CallbackService } from './callback.service'; @@ -26,9 +27,6 @@ describe('CallbackService ', () => { mockProvider(CodeFlowCallbackService), ], }); - }); - - beforeEach(() => { callbackService = TestBed.inject(CallbackService); flowHelper = TestBed.inject(FlowHelper); implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService); @@ -38,10 +36,13 @@ describe('CallbackService ', () => { describe('isCallback', () => { it('calls urlService.isCallbackFromSts with passed url', () => { - const urlServiceSpy = spyOn(urlService, 'isCallbackFromSts'); + const urlServiceSpy = vi.spyOn(urlService, 'isCallbackFromSts'); callbackService.isCallback('anyUrl'); - expect(urlServiceSpy).toHaveBeenCalledOnceWith('anyUrl', undefined); + expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith( + 'anyUrl', + undefined + ); }); }); @@ -52,93 +53,98 @@ describe('CallbackService ', () => { }); describe('handleCallbackAndFireEvents', () => { - it('calls authorizedCallbackWithCode if current flow is code flow', waitForAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); - const authorizedCallbackWithCodeSpy = spyOn( - codeFlowCallbackService, - 'authenticatedCallbackWithCode' - ).and.returnValue(of({} as CallbackContext)); + it('calls authorizedCallbackWithCode if current flow is code flow', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); + const authorizedCallbackWithCodeSpy = vi + .spyOn(codeFlowCallbackService, 'authenticatedCallbackWithCode') + .mockReturnValue(of({} as CallbackContext)); - callbackService - .handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [ + await lastValueFrom( + callbackService.handleCallbackAndFireEvents( + 'anyUrl', { configId: 'configId1' }, - ]) - .subscribe(() => { - expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledOnceWith( - 'anyUrl', - { configId: 'configId1' }, - [{ configId: 'configId1' }] - ); - }); - })); + [{ configId: 'configId1' }] + ) + ); - it('calls authorizedImplicitFlowCallback without hash if current flow is implicit flow and callbackurl does not include a hash', waitForAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); - spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true); - const authorizedCallbackWithCodeSpy = spyOn( - implicitFlowCallbackService, - 'authenticatedImplicitFlowCallback' - ).and.returnValue(of({} as CallbackContext)); + expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledExactlyOnceWith( + 'anyUrl', + { configId: 'configId1' }, + [{ configId: 'configId1' }] + ); + }); - callbackService - .handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [ + it('calls authorizedImplicitFlowCallback without hash if current flow is implicit flow and callbackurl does not include a hash', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); + vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue( + true + ); + const authorizedCallbackWithCodeSpy = vi + .spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback') + .mockReturnValue(of({} as CallbackContext)); + + await lastValueFrom( + callbackService.handleCallbackAndFireEvents( + 'anyUrl', { configId: 'configId1' }, - ]) - .subscribe(() => { - expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith( - { configId: 'configId1' }, - [{ configId: 'configId1' }] - ); - }); - })); + [{ configId: 'configId1' }] + ) + ); + expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith( + { configId: 'configId1' }, + [{ configId: 'configId1' }] + ); + }); - it('calls authorizedImplicitFlowCallback with hash if current flow is implicit flow and callbackurl does include a hash', waitForAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); - spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true); - const authorizedCallbackWithCodeSpy = spyOn( - implicitFlowCallbackService, - 'authenticatedImplicitFlowCallback' - ).and.returnValue(of({} as CallbackContext)); + it('calls authorizedImplicitFlowCallback with hash if current flow is implicit flow and callbackurl does include a hash', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); + vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue( + true + ); + const authorizedCallbackWithCodeSpy = vi + .spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback') + .mockReturnValue(of({} as CallbackContext)); - callbackService - .handleCallbackAndFireEvents( + await lastValueFrom( + callbackService.handleCallbackAndFireEvents( 'anyUrlWithAHash#some-string', { configId: 'configId1' }, [{ configId: 'configId1' }] ) - .subscribe(() => { - expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith( - { configId: 'configId1' }, - [{ configId: 'configId1' }], - 'some-string' - ); - }); - })); + ); - it('emits callbackinternal no matter which flow it is', waitForAsync(() => { - const callbackSpy = spyOn( + expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith( + { configId: 'configId1' }, + [{ configId: 'configId1' }], + 'some-string' + ); + }); + + it('emits callbackinternal no matter which flow it is', async () => { + const callbackSpy = vi.spyOn( (callbackService as any).stsCallbackInternal$, 'next' ); - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); - const authenticatedCallbackWithCodeSpy = spyOn( - codeFlowCallbackService, - 'authenticatedCallbackWithCode' - ).and.returnValue(of({} as CallbackContext)); + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); + const authenticatedCallbackWithCodeSpy = vi + .spyOn(codeFlowCallbackService, 'authenticatedCallbackWithCode') + .mockReturnValue(of({} as CallbackContext)); - callbackService - .handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [ + await lastValueFrom( + callbackService.handleCallbackAndFireEvents( + 'anyUrl', { configId: 'configId1' }, - ]) - .subscribe(() => { - expect(authenticatedCallbackWithCodeSpy).toHaveBeenCalledOnceWith( - 'anyUrl', - { configId: 'configId1' }, - [{ configId: 'configId1' }] - ); - expect(callbackSpy).toHaveBeenCalled(); - }); - })); + [{ configId: 'configId1' }] + ) + ); + + expect(authenticatedCallbackWithCodeSpy).toHaveBeenCalledExactlyOnceWith( + 'anyUrl', + { configId: 'configId1' }, + [{ configId: 'configId1' }] + ); + expect(callbackSpy).toHaveBeenCalled(); + }); }); }); diff --git a/src/callback/callback.service.ts b/src/callback/callback.service.ts index 7af6984..f4e529d 100644 --- a/src/callback/callback.service.ts +++ b/src/callback/callback.service.ts @@ -1,8 +1,8 @@ -import { inject, Injectable } from 'injection-js'; +import { Injectable, inject } from 'injection-js'; import { Observable, Subject } from 'rxjs'; import { tap } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { UrlService } from '../utils/url/url.service'; import { CodeFlowCallbackService } from './code-flow-callback.service'; diff --git a/src/callback/code-flow-callback.service.spec.ts b/src/callback/code-flow-callback.service.spec.ts index 3e9996c..81c3438 100644 --- a/src/callback/code-flow-callback.service.spec.ts +++ b/src/callback/code-flow-callback.service.spec.ts @@ -1,11 +1,11 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { Router } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { TestBed, mockRouterProvider } from '@/testing'; +import { AbstractRouter } from 'oidc-client-rx'; import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; -import { CallbackContext } from '../flows/callback-context'; +import { vi } from 'vitest'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsService } from '../flows/flows.service'; +import { mockProvider } from '../testing/mock'; import { CodeFlowCallbackService } from './code-flow-callback.service'; import { IntervalService } from './interval.service'; @@ -14,26 +14,24 @@ describe('CodeFlowCallbackService ', () => { let intervalService: IntervalService; let flowsService: FlowsService; let flowsDataService: FlowsDataService; - let router: Router; + let router: AbstractRouter; beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule], + imports: [], providers: [ + mockRouterProvider(), CodeFlowCallbackService, mockProvider(FlowsService), mockProvider(FlowsDataService), mockProvider(IntervalService), ], }); - }); - - beforeEach(() => { codeFlowCallbackService = TestBed.inject(CodeFlowCallbackService); intervalService = TestBed.inject(IntervalService); flowsDataService = TestBed.inject(FlowsDataService); flowsService = TestBed.inject(FlowsService); - router = TestBed.inject(Router); + router = TestBed.inject(AbstractRouter); }); it('should create', () => { @@ -42,11 +40,10 @@ describe('CodeFlowCallbackService ', () => { describe('authenticatedCallbackWithCode', () => { it('calls flowsService.processCodeFlowCallback with correct url', () => { - const spy = spyOn( - flowsService, - 'processCodeFlowCallback' - ).and.returnValue(of({} as CallbackContext)); - //spyOn(configurationProvider, 'getOpenIDConfiguration').and.returnValue({ triggerAuthorizationResultEvent: true }); + const spy = vi + .spyOn(flowsService, 'processCodeFlowCallback') + .mockReturnValue(of({} as CallbackContext)); + //spyOn(configurationProvider, 'getOpenIDConfiguration').mockReturnValue({ triggerAuthorizationResultEvent: true }); const config = { configId: 'configId1', @@ -58,10 +55,12 @@ describe('CodeFlowCallbackService ', () => { config, [config] ); - expect(spy).toHaveBeenCalledOnceWith('some-url1', config, [config]); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-url1', config, [ + config, + ]); }); - it('does only call resetCodeFlowInProgress if triggerAuthorizationResultEvent is true and isRenewProcess is true', waitForAsync(() => { + it('does only call resetCodeFlowInProgress if triggerAuthorizationResultEvent is true and isRenewProcess is true', async () => { const callbackContext = { code: '', refreshToken: '', @@ -73,12 +72,14 @@ describe('CodeFlowCallbackService ', () => { validationResult: null, existingIdToken: '', }; - const spy = spyOn( - flowsService, - 'processCodeFlowCallback' - ).and.returnValue(of(callbackContext)); - const flowsDataSpy = spyOn(flowsDataService, 'resetCodeFlowInProgress'); - const routerSpy = spyOn(router, 'navigateByUrl'); + const spy = vi + .spyOn(flowsService, 'processCodeFlowCallback') + .mockReturnValue(of(callbackContext)); + const flowsDataSpy = vi.spyOn( + flowsDataService, + 'resetCodeFlowInProgress' + ); + const routerSpy = vi.spyOn(router, 'navigateByUrl'); const config = { configId: 'configId1', triggerAuthorizationResultEvent: true, @@ -87,13 +88,15 @@ describe('CodeFlowCallbackService ', () => { codeFlowCallbackService .authenticatedCallbackWithCode('some-url2', config, [config]) .subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith('some-url2', config, [config]); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-url2', config, [ + config, + ]); expect(routerSpy).not.toHaveBeenCalled(); expect(flowsDataSpy).toHaveBeenCalled(); }); - })); + }); - it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', waitForAsync(() => { + it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => { const callbackContext = { code: '', refreshToken: '', @@ -105,12 +108,14 @@ describe('CodeFlowCallbackService ', () => { validationResult: null, existingIdToken: '', }; - const spy = spyOn( - flowsService, - 'processCodeFlowCallback' - ).and.returnValue(of(callbackContext)); - const flowsDataSpy = spyOn(flowsDataService, 'resetCodeFlowInProgress'); - const routerSpy = spyOn(router, 'navigateByUrl'); + const spy = vi + .spyOn(flowsService, 'processCodeFlowCallback') + .mockReturnValue(of(callbackContext)); + const flowsDataSpy = vi.spyOn( + flowsDataService, + 'resetCodeFlowInProgress' + ); + const routerSpy = vi.spyOn(router, 'navigateByUrl'); const config = { configId: 'configId1', triggerAuthorizationResultEvent: false, @@ -120,25 +125,27 @@ describe('CodeFlowCallbackService ', () => { codeFlowCallbackService .authenticatedCallbackWithCode('some-url3', config, [config]) .subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith('some-url3', config, [config]); - expect(routerSpy).toHaveBeenCalledOnceWith('postLoginRoute'); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-url3', config, [ + config, + ]); + expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute'); expect(flowsDataSpy).toHaveBeenCalled(); }); - })); + }); - it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', waitForAsync(() => { - spyOn(flowsService, 'processCodeFlowCallback').and.returnValue( + it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', async () => { + vi.spyOn(flowsService, 'processCodeFlowCallback').mockReturnValue( throwError(() => new Error('error')) ); - const resetSilentRenewRunningSpy = spyOn( + const resetSilentRenewRunningSpy = vi.spyOn( flowsDataService, 'resetSilentRenewRunning' ); - const resetCodeFlowInProgressSpy = spyOn( + const resetCodeFlowInProgressSpy = vi.spyOn( flowsDataService, 'resetCodeFlowInProgress' ); - const stopPeriodicallTokenCheckSpy = spyOn( + const stopPeriodicallTokenCheckSpy = vi.spyOn( intervalService, 'stopPeriodicTokenCheck' ); @@ -159,23 +166,23 @@ describe('CodeFlowCallbackService ', () => { expect(err).toBeTruthy(); }, }); - })); + }); it(`navigates to unauthorizedRoute in case of error and in case of error and - triggerAuthorizationResultEvent is false`, waitForAsync(() => { - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn(flowsService, 'processCodeFlowCallback').and.returnValue( + triggerAuthorizationResultEvent is false`, async () => { + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn(flowsService, 'processCodeFlowCallback').mockReturnValue( throwError(() => new Error('error')) ); - const resetSilentRenewRunningSpy = spyOn( + const resetSilentRenewRunningSpy = vi.spyOn( flowsDataService, 'resetSilentRenewRunning' ); - const stopPeriodicallTokenCheckSpy = spyOn( + const stopPeriodicallTokenCheckSpy = vi.spyOn( intervalService, 'stopPeriodicTokenCheck' ); - const routerSpy = spyOn(router, 'navigateByUrl'); + const routerSpy = vi.spyOn(router, 'navigateByUrl'); const config = { configId: 'configId1', @@ -190,9 +197,11 @@ describe('CodeFlowCallbackService ', () => { expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); expect(err).toBeTruthy(); - expect(routerSpy).toHaveBeenCalledOnceWith('unauthorizedRoute'); + expect(routerSpy).toHaveBeenCalledExactlyOnceWith( + 'unauthorizedRoute' + ); }, }); - })); + }); }); }); diff --git a/src/callback/code-flow-callback.service.ts b/src/callback/code-flow-callback.service.ts index 21374f4..b3c52a9 100644 --- a/src/callback/code-flow-callback.service.ts +++ b/src/callback/code-flow-callback.service.ts @@ -1,9 +1,9 @@ -import { inject, Injectable } from 'injection-js'; import { Router } from '@angular/router'; -import { Observable, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsService } from '../flows/flows.service'; import { IntervalService } from './interval.service'; diff --git a/src/callback/implicit-flow-callback.service.spec.ts b/src/callback/implicit-flow-callback.service.spec.ts index 830de5e..ebe7f1c 100644 --- a/src/callback/implicit-flow-callback.service.spec.ts +++ b/src/callback/implicit-flow-callback.service.spec.ts @@ -1,11 +1,11 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { Router } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { TestBed, mockRouterProvider } from '@/testing'; +import { AbstractRouter } from 'oidc-client-rx'; import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; -import { CallbackContext } from '../flows/callback-context'; +import { vi } from 'vitest'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsService } from '../flows/flows.service'; +import { mockProvider } from '../testing/mock'; import { ImplicitFlowCallbackService } from './implicit-flow-callback.service'; import { IntervalService } from './interval.service'; @@ -14,25 +14,23 @@ describe('ImplicitFlowCallbackService ', () => { let intervalService: IntervalService; let flowsService: FlowsService; let flowsDataService: FlowsDataService; - let router: Router; + let router: AbstractRouter; beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule], + imports: [], providers: [ + mockRouterProvider(), mockProvider(FlowsService), mockProvider(FlowsDataService), mockProvider(IntervalService), ], }); - }); - - beforeEach(() => { implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService); intervalService = TestBed.inject(IntervalService); flowsDataService = TestBed.inject(FlowsDataService); flowsService = TestBed.inject(FlowsService); - router = TestBed.inject(Router); + router = TestBed.inject(AbstractRouter); }); it('should create', () => { @@ -41,10 +39,9 @@ describe('ImplicitFlowCallbackService ', () => { describe('authorizedImplicitFlowCallback', () => { it('calls flowsService.processImplicitFlowCallback with hash if given', () => { - const spy = spyOn( - flowsService, - 'processImplicitFlowCallback' - ).and.returnValue(of({} as CallbackContext)); + const spy = vi + .spyOn(flowsService, 'processImplicitFlowCallback') + .mockReturnValue(of({} as CallbackContext)); const config = { configId: 'configId1', triggerAuthorizationResultEvent: true, @@ -56,10 +53,14 @@ describe('ImplicitFlowCallbackService ', () => { 'some-hash' ); - expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash'); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + 'some-hash' + ); }); - it('does nothing if triggerAuthorizationResultEvent is true and isRenewProcess is true', waitForAsync(() => { + it('does nothing if triggerAuthorizationResultEvent is true and isRenewProcess is true', async () => { const callbackContext = { code: '', refreshToken: '', @@ -71,11 +72,10 @@ describe('ImplicitFlowCallbackService ', () => { validationResult: null, existingIdToken: '', }; - const spy = spyOn( - flowsService, - 'processImplicitFlowCallback' - ).and.returnValue(of(callbackContext)); - const routerSpy = spyOn(router, 'navigateByUrl'); + const spy = vi + .spyOn(flowsService, 'processImplicitFlowCallback') + .mockReturnValue(of(callbackContext)); + const routerSpy = vi.spyOn(router, 'navigateByUrl'); const config = { configId: 'configId1', triggerAuthorizationResultEvent: true, @@ -84,12 +84,16 @@ describe('ImplicitFlowCallbackService ', () => { implicitFlowCallbackService .authenticatedImplicitFlowCallback(config, [config], 'some-hash') .subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash'); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + 'some-hash' + ); expect(routerSpy).not.toHaveBeenCalled(); }); - })); + }); - it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', waitForAsync(() => { + it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => { const callbackContext = { code: '', refreshToken: '', @@ -101,11 +105,10 @@ describe('ImplicitFlowCallbackService ', () => { validationResult: null, existingIdToken: '', }; - const spy = spyOn( - flowsService, - 'processImplicitFlowCallback' - ).and.returnValue(of(callbackContext)); - const routerSpy = spyOn(router, 'navigateByUrl'); + const spy = vi + .spyOn(flowsService, 'processImplicitFlowCallback') + .mockReturnValue(of(callbackContext)); + const routerSpy = vi.spyOn(router, 'navigateByUrl'); const config = { configId: 'configId1', triggerAuthorizationResultEvent: false, @@ -115,20 +118,24 @@ describe('ImplicitFlowCallbackService ', () => { implicitFlowCallbackService .authenticatedImplicitFlowCallback(config, [config], 'some-hash') .subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash'); - expect(routerSpy).toHaveBeenCalledOnceWith('postLoginRoute'); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + 'some-hash' + ); + expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute'); }); - })); + }); - it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', waitForAsync(() => { - spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue( + it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', async () => { + vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue( throwError(() => new Error('error')) ); - const resetSilentRenewRunningSpy = spyOn( + const resetSilentRenewRunningSpy = vi.spyOn( flowsDataService, 'resetSilentRenewRunning' ); - const stopPeriodicallyTokenCheckSpy = spyOn( + const stopPeriodicallyTokenCheckSpy = vi.spyOn( intervalService, 'stopPeriodicTokenCheck' ); @@ -147,23 +154,23 @@ describe('ImplicitFlowCallbackService ', () => { expect(err).toBeTruthy(); }, }); - })); + }); it(`navigates to unauthorizedRoute in case of error and in case of error and - triggerAuthorizationResultEvent is false`, waitForAsync(() => { - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue( + triggerAuthorizationResultEvent is false`, async () => { + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue( throwError(() => new Error('error')) ); - const resetSilentRenewRunningSpy = spyOn( + const resetSilentRenewRunningSpy = vi.spyOn( flowsDataService, 'resetSilentRenewRunning' ); - const stopPeriodicallTokenCheckSpy = spyOn( + const stopPeriodicallTokenCheckSpy = vi.spyOn( intervalService, 'stopPeriodicTokenCheck' ); - const routerSpy = spyOn(router, 'navigateByUrl'); + const routerSpy = vi.spyOn(router, 'navigateByUrl'); const config = { configId: 'configId1', triggerAuthorizationResultEvent: false, @@ -177,9 +184,11 @@ describe('ImplicitFlowCallbackService ', () => { expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); expect(err).toBeTruthy(); - expect(routerSpy).toHaveBeenCalledOnceWith('unauthorizedRoute'); + expect(routerSpy).toHaveBeenCalledExactlyOnceWith( + 'unauthorizedRoute' + ); }, }); - })); + }); }); }); diff --git a/src/callback/implicit-flow-callback.service.ts b/src/callback/implicit-flow-callback.service.ts index f52de47..a72d87b 100644 --- a/src/callback/implicit-flow-callback.service.ts +++ b/src/callback/implicit-flow-callback.service.ts @@ -1,9 +1,9 @@ -import { inject, Injectable } from 'injection-js'; import { Router } from '@angular/router'; -import { Observable, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsService } from '../flows/flows.service'; import { IntervalService } from './interval.service'; diff --git a/src/callback/interval.service.spec.ts b/src/callback/interval.service.spec.ts index 2e124f5..25568c0 100644 --- a/src/callback/interval.service.spec.ts +++ b/src/callback/interval.service.spec.ts @@ -1,5 +1,6 @@ -import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { TestBed, fakeAsync, tick } from '@/testing'; import { Subscription } from 'rxjs'; +import { vi } from 'vitest'; import { IntervalService } from './interval.service'; describe('IntervalService', () => { @@ -31,7 +32,7 @@ describe('IntervalService', () => { describe('stopPeriodicTokenCheck', () => { it('calls unsubscribe and sets to null', () => { intervalService.runTokenValidationRunning = new Subscription(); - const spy = spyOn( + const spy = vi.spyOn( intervalService.runTokenValidationRunning, 'unsubscribe' ); @@ -44,7 +45,7 @@ describe('IntervalService', () => { it('does nothing if `runTokenValidationRunning` is null', () => { intervalService.runTokenValidationRunning = new Subscription(); - const spy = spyOn( + const spy = vi.spyOn( intervalService.runTokenValidationRunning, 'unsubscribe' ); @@ -57,7 +58,7 @@ describe('IntervalService', () => { }); describe('startPeriodicTokenCheck', () => { - it('starts check after correct milliseconds', fakeAsync(() => { + it('starts check after correct milliseconds', async () => { const periodicCheck = intervalService.startPeriodicTokenCheck(0.5); const spy = jasmine.createSpy(); const sub = periodicCheck.subscribe(() => { @@ -71,6 +72,6 @@ describe('IntervalService', () => { expect(spy).toHaveBeenCalledTimes(2); sub.unsubscribe(); - })); + }); }); }); diff --git a/src/callback/interval.service.ts b/src/callback/interval.service.ts index c8bbd6d..ee32b7c 100644 --- a/src/callback/interval.service.ts +++ b/src/callback/interval.service.ts @@ -1,11 +1,9 @@ -import { Injectable, NgZone, inject } from 'injection-js'; -import { Observable, Subscription } from 'rxjs'; -import { DOCUMENT } from '../../dom'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, type Subscription, interval } from 'rxjs'; +import { DOCUMENT } from '../dom'; @Injectable() export class IntervalService { - private readonly zone = inject(NgZone); - private readonly document = inject(DOCUMENT); runTokenValidationRunning: Subscription | null = null; @@ -24,19 +22,6 @@ export class IntervalService { startPeriodicTokenCheck(repeatAfterSeconds: number): Observable { const millisecondsDelayBetweenTokenCheck = repeatAfterSeconds * 1000; - return new Observable((subscriber) => { - let intervalId: number | undefined; - - this.zone.runOutsideAngular(() => { - intervalId = this.document?.defaultView?.setInterval( - () => this.zone.run(() => subscriber.next()), - millisecondsDelayBetweenTokenCheck - ); - }); - - return (): void => { - clearInterval(intervalId); - }; - }); + return interval(millisecondsDelayBetweenTokenCheck); } } diff --git a/src/callback/periodically-token-check.service.spec.ts b/src/callback/periodically-token-check.service.spec.ts index 93e470e..20efef3 100644 --- a/src/callback/periodically-token-check.service.spec.ts +++ b/src/callback/periodically-token-check.service.spec.ts @@ -1,10 +1,10 @@ -import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; -import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { lastValueFrom, of, throwError } from 'rxjs'; +import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; import { ConfigurationService } from '../config/config.service'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service'; @@ -12,6 +12,7 @@ import { LoggerService } from '../logging/logger.service'; import { EventTypes } from '../public-events/event-types'; import { PublicEventsService } from '../public-events/public-events.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockProvider } from '../testing/mock'; import { UserService } from '../user-data/user.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { IntervalService } from './interval.service'; @@ -49,9 +50,6 @@ describe('PeriodicallyTokenCheckService', () => { mockProvider(ConfigurationService), ], }); - }); - - beforeEach(() => { periodicallyTokenCheckService = TestBed.inject( PeriodicallyTokenCheckService ); @@ -68,11 +66,14 @@ describe('PeriodicallyTokenCheckService', () => { publicEventsService = TestBed.inject(PublicEventsService); configurationService = TestBed.inject(ConfigurationService); - spyOn(intervalService, 'startPeriodicTokenCheck').and.returnValue(of(null)); + vi.spyOn(intervalService, 'startPeriodicTokenCheck').mockReturnValue( + of(null) + ); }); + // biome-ignore lint/correctness/noUndeclaredVariables: afterEach(() => { - if (!!intervalService.runTokenValidationRunning?.unsubscribe) { + if (intervalService.runTokenValidationRunning?.unsubscribe) { intervalService.runTokenValidationRunning.unsubscribe(); intervalService.runTokenValidationRunning = null; } @@ -83,164 +84,172 @@ describe('PeriodicallyTokenCheckService', () => { }); describe('startTokenValidationPeriodically', () => { - it('returns if no config has silentrenew enabled', waitForAsync(() => { + it('returns if no config has silentrenew enabled', async () => { const configs = [ { silentRenew: false, configId: 'configId1' }, { silentRenew: false, configId: 'configId2' }, ]; - const result = + const result = await lastValueFrom( periodicallyTokenCheckService.startTokenValidationPeriodically( configs, - configs[0] - ); + configs[0]! + ) + ); expect(result).toBeUndefined(); - })); + }); - it('returns if runTokenValidationRunning', waitForAsync(() => { + it('returns if runTokenValidationRunning', async () => { const configs = [{ silentRenew: true, configId: 'configId1' }]; - spyOn(intervalService, 'isTokenValidationRunning').and.returnValue(true); + vi.spyOn(intervalService, 'isTokenValidationRunning').mockReturnValue( + true + ); - const result = + const result = await lastValueFrom( periodicallyTokenCheckService.startTokenValidationPeriodically( configs, - configs[0] - ); + configs[0]! + ) + ); expect(result).toBeUndefined(); - })); + }); - it('interval calls resetSilentRenewRunning when current flow is CodeFlowWithRefreshTokens', fakeAsync(() => { + it('interval calls resetSilentRenewRunning when current flow is CodeFlowWithRefreshTokens', async () => { const configs = [ { silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 }, ]; - spyOn( + vi.spyOn( periodicallyTokenCheckService as any, 'shouldStartPeriodicallyCheckForConfig' - ).and.returnValue(true); - const isCurrentFlowCodeFlowWithRefreshTokensSpy = spyOn( - flowHelper, - 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - const resetSilentRenewRunningSpy = spyOn( + ).mockReturnValue(true); + const isCurrentFlowCodeFlowWithRefreshTokensSpy = vi + .spyOn(flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens') + .mockReturnValue(true); + const resetSilentRenewRunningSpy = vi.spyOn( flowsDataService, 'resetSilentRenewRunning' ); - spyOn( + vi.spyOn( refreshSessionRefreshTokenService, 'refreshSessionWithRefreshTokens' - ).and.returnValue(of({} as CallbackContext)); - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( - of(configs[0]) + ).mockReturnValue(of({} as CallbackContext)); + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( + of(configs[0]!) ); periodicallyTokenCheckService.startTokenValidationPeriodically( configs, - configs[0] + configs[0]! ); - tick(1000); + await vi.advanceTimersByTimeAsync(1000); intervalService.runTokenValidationRunning?.unsubscribe(); intervalService.runTokenValidationRunning = null; expect(isCurrentFlowCodeFlowWithRefreshTokensSpy).toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); - })); + }); - it('interval calls resetSilentRenewRunning in case of error when current flow is CodeFlowWithRefreshTokens', fakeAsync(() => { + it('interval calls resetSilentRenewRunning in case of error when current flow is CodeFlowWithRefreshTokens', async () => { const configs = [ { silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 }, ]; - spyOn( + vi.spyOn( periodicallyTokenCheckService as any, 'shouldStartPeriodicallyCheckForConfig' - ).and.returnValue(true); - const resetSilentRenewRunning = spyOn( + ).mockReturnValue(true); + const resetSilentRenewRunning = vi.spyOn( flowsDataService, 'resetSilentRenewRunning' ); - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( refreshSessionRefreshTokenService, 'refreshSessionWithRefreshTokens' - ).and.returnValue(throwError(() => new Error('error'))); - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( - of(configs[0]) + ).mockReturnValue(throwError(() => new Error('error'))); + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( + of(configs[0]!) ); periodicallyTokenCheckService.startTokenValidationPeriodically( configs, - configs[0] + configs[0]! ); - tick(1000); + await vi.advanceTimersByTimeAsync(1000); expect( periodicallyTokenCheckService.startTokenValidationPeriodically ).toThrowError(); - expect(resetSilentRenewRunning).toHaveBeenCalledOnceWith(configs[0]); - })); + expect(resetSilentRenewRunning).toHaveBeenCalledExactlyOnceWith( + configs[0] + ); + }); - it('interval throws silent renew failed event with data in case of an error', fakeAsync(() => { + it('interval throws silent renew failed event with data in case of an error', async () => { const configs = [ { silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 }, ]; - spyOn( + vi.spyOn( periodicallyTokenCheckService as any, 'shouldStartPeriodicallyCheckForConfig' - ).and.returnValue(true); - spyOn(flowsDataService, 'resetSilentRenewRunning'); - const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent'); + ).mockReturnValue(true); + vi.spyOn(flowsDataService, 'resetSilentRenewRunning'); + const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent'); - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( refreshSessionRefreshTokenService, 'refreshSessionWithRefreshTokens' - ).and.returnValue(throwError(() => new Error('error'))); - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( - of(configs[0]) + ).mockReturnValue(throwError(() => new Error('error'))); + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( + of(configs[0]!) ); periodicallyTokenCheckService.startTokenValidationPeriodically( configs, - configs[0] + configs[0]! ); - tick(1000); + await vi.advanceTimersByTimeAsync(1000); expect( periodicallyTokenCheckService.startTokenValidationPeriodically ).toThrowError(); - expect(publicEventsServiceSpy.calls.allArgs()).toEqual([ + expect(publicEventsServiceSpy).toBeCalledWith([ [EventTypes.SilentRenewStarted], [EventTypes.SilentRenewFailed, new Error('error')], ]); - })); + }); - it('calls resetAuthorizationData and returns if no silent renew is configured', fakeAsync(() => { + it('calls resetAuthorizationData and returns if no silent renew is configured', async () => { const configs = [ { silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 }, ]; - spyOn( + vi.spyOn( periodicallyTokenCheckService as any, 'shouldStartPeriodicallyCheckForConfig' - ).and.returnValue(true); + ).mockReturnValue(true); - const configSpy = spyOn(configurationService, 'getOpenIDConfiguration'); + const configSpy = vi.spyOn( + configurationService, + 'getOpenIDConfiguration' + ); const configWithoutSilentRenew = { silentRenew: false, configId: 'configId1', @@ -248,68 +257,70 @@ describe('PeriodicallyTokenCheckService', () => { }; const configWithoutSilentRenew$ = of(configWithoutSilentRenew); - configSpy.and.returnValue(configWithoutSilentRenew$); + configSpy.mockReturnValue(configWithoutSilentRenew$); - const resetAuthorizationDataSpy = spyOn( + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); periodicallyTokenCheckService.startTokenValidationPeriodically( configs, - configs[0] + configs[0]! ); - tick(1000); + await vi.advanceTimersByTimeAsync(1000); intervalService.runTokenValidationRunning?.unsubscribe(); intervalService.runTokenValidationRunning = null; expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); - expect(resetAuthorizationDataSpy).toHaveBeenCalledOnceWith( + expect(resetAuthorizationDataSpy).toHaveBeenCalledExactlyOnceWith( configWithoutSilentRenew, configs ); - })); + }); - it('calls refreshSessionWithRefreshTokens if current flow is Code flow with refresh tokens', fakeAsync(() => { - spyOn( + it('calls refreshSessionWithRefreshTokens if current flow is Code flow with refresh tokens', async () => { + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( periodicallyTokenCheckService as any, 'shouldStartPeriodicallyCheckForConfig' - ).and.returnValue(true); - spyOn(storagePersistenceService, 'read').and.returnValue({}); + ).mockReturnValue(true); + vi.spyOn(storagePersistenceService, 'read').mockReturnValue({}); const configs = [ { configId: 'configId1', silentRenew: true, tokenRefreshInSeconds: 1 }, ]; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(configs[0] as OpenIdConfiguration) ); - const refreshSessionWithRefreshTokensSpy = spyOn( - refreshSessionRefreshTokenService, - 'refreshSessionWithRefreshTokens' - ).and.returnValue(of({} as CallbackContext)); + const refreshSessionWithRefreshTokensSpy = vi + .spyOn( + refreshSessionRefreshTokenService, + 'refreshSessionWithRefreshTokens' + ) + .mockReturnValue(of({} as CallbackContext)); periodicallyTokenCheckService.startTokenValidationPeriodically( configs, - configs[0] + configs[0]! ); - tick(1000); + await vi.advanceTimersByTimeAsync(1000); intervalService.runTokenValidationRunning?.unsubscribe(); intervalService.runTokenValidationRunning = null; expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled(); - })); + }); }); describe('shouldStartPeriodicallyCheckForConfig', () => { it('returns false when there is no IdToken', () => { - spyOn(authStateService, 'getIdToken').and.returnValue(''); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(authStateService, 'getIdToken').mockReturnValue(''); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( 'some-userdata' ); @@ -317,13 +328,13 @@ describe('PeriodicallyTokenCheckService', () => { periodicallyTokenCheckService as any ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('returns false when silent renew is running', () => { - spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken'); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true); + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( 'some-userdata' ); @@ -331,14 +342,14 @@ describe('PeriodicallyTokenCheckService', () => { periodicallyTokenCheckService as any ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('returns false when code flow is in progress', () => { - spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn(flowsDataService, 'isCodeFlowInProgress').and.returnValue(true); - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken'); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn(flowsDataService, 'isCodeFlowInProgress').mockReturnValue(true); + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( 'some-userdata' ); @@ -346,87 +357,87 @@ describe('PeriodicallyTokenCheckService', () => { periodicallyTokenCheckService as any ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('returns false when there is no userdata from the store', () => { - spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); - spyOn(userService, 'getUserDataFromStore').and.returnValue(null); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken'); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true); + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(null); const result = ( periodicallyTokenCheckService as any ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('returns true when there is userDataFromStore, silentrenew is not running and there is an idtoken', () => { - spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken'); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( 'some-userdata' ); - spyOn( + vi.spyOn( authStateService, 'hasIdTokenExpiredAndRenewCheckIsEnabled' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authStateService, 'hasAccessTokenExpiredIfExpiryExists' - ).and.returnValue(true); + ).mockReturnValue(true); const result = ( periodicallyTokenCheckService as any ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); it('returns false if tokens are not expired', () => { - spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken'); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( 'some-userdata' ); - spyOn( + vi.spyOn( authStateService, 'hasIdTokenExpiredAndRenewCheckIsEnabled' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( authStateService, 'hasAccessTokenExpiredIfExpiryExists' - ).and.returnValue(false); + ).mockReturnValue(false); const result = ( periodicallyTokenCheckService as any ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('returns true if tokens are expired', () => { - spyOn(authStateService, 'getIdToken').and.returnValue('idToken'); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken'); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( 'some-userdata' ); - spyOn( + vi.spyOn( authStateService, 'hasIdTokenExpiredAndRenewCheckIsEnabled' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authStateService, 'hasAccessTokenExpiredIfExpiryExists' - ).and.returnValue(true); + ).mockReturnValue(true); const result = ( periodicallyTokenCheckService as any ).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' }); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); }); }); diff --git a/src/callback/periodically-token-check.service.ts b/src/callback/periodically-token-check.service.ts index a5691da..1f30de6 100644 --- a/src/callback/periodically-token-check.service.ts +++ b/src/callback/periodically-token-check.service.ts @@ -1,10 +1,10 @@ -import { inject, Injectable } from 'injection-js'; -import { forkJoin, Observable, of, throwError } from 'rxjs'; -import { catchError, switchMap } from 'rxjs/operators'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, forkJoin, of, throwError } from 'rxjs'; +import { catchError, map, shareReplay, switchMap } from 'rxjs/operators'; import { AuthStateService } from '../auth-state/auth-state.service'; import { ConfigurationService } from '../config/config.service'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service'; @@ -52,7 +52,7 @@ export class PeriodicallyTokenCheckService { startTokenValidationPeriodically( allConfigs: OpenIdConfiguration[], currentConfig: OpenIdConfiguration - ): void { + ): Observable { const configsWithSilentRenewEnabled = this.getConfigsWithSilentRenewEnabled(allConfigs); @@ -75,46 +75,51 @@ export class PeriodicallyTokenCheckService { [id: string]: Observable; } = {}; - configsWithSilentRenewEnabled.forEach((config) => { + for (const config of configsWithSilentRenewEnabled) { const identifier = config.configId as string; const refreshEvent = this.getRefreshEvent(config, allConfigs); objectWithConfigIdsAndRefreshEvent[identifier] = refreshEvent; - }); + } return forkJoin(objectWithConfigIdsAndRefreshEvent); }) ); - this.intervalService.runTokenValidationRunning = periodicallyCheck$ - .pipe(catchError((error) => throwError(() => new Error(error)))) - .subscribe({ - next: (objectWithConfigIds) => { - for (const [configId, _] of Object.entries(objectWithConfigIds)) { - this.configurationService - .getOpenIDConfiguration(configId) - .subscribe((config) => { - this.loggerService.logDebug( - config, - 'silent renew, periodic check finished!' - ); + const o$ = periodicallyCheck$.pipe( + catchError((error) => throwError(() => new Error(error))), + map((objectWithConfigIds) => { + for (const [configId, _] of Object.entries(objectWithConfigIds)) { + this.configurationService + .getOpenIDConfiguration(configId) + .subscribe((config) => { + this.loggerService.logDebug( + config, + 'silent renew, periodic check finished!' + ); - if ( - this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config) - ) { - this.flowsDataService.resetSilentRenewRunning(config); - } - }); - } - }, - error: (error) => { - this.loggerService.logError( - currentConfig, - 'silent renew failed!', - error - ); - }, - }); + if ( + this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config) + ) { + this.flowsDataService.resetSilentRenewRunning(config); + } + }); + } + }), + catchError((error) => { + this.loggerService.logError( + currentConfig, + 'silent renew failed!', + error + ); + return throwError(() => error); + }), + shareReplay(1) + ); + + this.intervalService.runTokenValidationRunning = o$.subscribe(); + + return o$; } private getRefreshEvent( diff --git a/src/callback/refresh-session-refresh-token.service.spec.ts b/src/callback/refresh-session-refresh-token.service.spec.ts index 6aab19d..d4847ba 100644 --- a/src/callback/refresh-session-refresh-token.service.spec.ts +++ b/src/callback/refresh-session-refresh-token.service.spec.ts @@ -1,10 +1,11 @@ -import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; -import { CallbackContext } from '../flows/callback-context'; +import { vi } from 'vitest'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsService } from '../flows/flows.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { LoggerService } from '../logging/logger.service'; +import { mockProvider } from '../testing/mock'; import { IntervalService } from './interval.service'; import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service'; @@ -25,9 +26,6 @@ describe('RefreshSessionRefreshTokenService', () => { mockProvider(IntervalService), ], }); - }); - - beforeEach(() => { flowsService = TestBed.inject(FlowsService); refreshSessionRefreshTokenService = TestBed.inject( RefreshSessionRefreshTokenService @@ -41,10 +39,10 @@ describe('RefreshSessionRefreshTokenService', () => { }); describe('refreshSessionWithRefreshTokens', () => { - it('calls flowsService.processRefreshToken()', waitForAsync(() => { - const spy = spyOn(flowsService, 'processRefreshToken').and.returnValue( - of({} as CallbackContext) - ); + it('calls flowsService.processRefreshToken()', async () => { + const spy = vi + .spyOn(flowsService, 'processRefreshToken') + .mockReturnValue(of({} as CallbackContext)); refreshSessionRefreshTokenService .refreshSessionWithRefreshTokens({ configId: 'configId1' }, [ @@ -53,13 +51,13 @@ describe('RefreshSessionRefreshTokenService', () => { .subscribe(() => { expect(spy).toHaveBeenCalled(); }); - })); + }); - it('resetAuthorizationData in case of error', waitForAsync(() => { - spyOn(flowsService, 'processRefreshToken').and.returnValue( + it('resetAuthorizationData in case of error', async () => { + vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue( throwError(() => new Error('error')) ); - const resetSilentRenewRunningSpy = spyOn( + const resetSilentRenewRunningSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); @@ -74,13 +72,13 @@ describe('RefreshSessionRefreshTokenService', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('finalize with stopPeriodicTokenCheck in case of error', fakeAsync(() => { - spyOn(flowsService, 'processRefreshToken').and.returnValue( + it('finalize with stopPeriodicTokenCheck in case of error', async () => { + vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue( throwError(() => new Error('error')) ); - const stopPeriodicallyTokenCheckSpy = spyOn( + const stopPeriodicallyTokenCheckSpy = vi.spyOn( intervalService, 'stopPeriodicTokenCheck' ); @@ -94,8 +92,8 @@ describe('RefreshSessionRefreshTokenService', () => { expect(err).toBeTruthy(); }, }); - tick(); + await vi.advanceTimersByTimeAsync(0); expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled(); - })); + }); }); }); diff --git a/src/callback/refresh-session-refresh-token.service.ts b/src/callback/refresh-session-refresh-token.service.ts index 56cd31c..63114c6 100644 --- a/src/callback/refresh-session-refresh-token.service.ts +++ b/src/callback/refresh-session-refresh-token.service.ts @@ -1,8 +1,8 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsService } from '../flows/flows.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { LoggerService } from '../logging/logger.service'; diff --git a/src/callback/refresh-session.service.spec.ts b/src/callback/refresh-session.service.spec.ts index 4cc72ec..73fca25 100644 --- a/src/callback/refresh-session.service.spec.ts +++ b/src/callback/refresh-session.service.spec.ts @@ -1,17 +1,18 @@ -import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { TestBed, fakeAsync, tick } from '@/testing'; import { of, throwError } from 'rxjs'; import { delay } from 'rxjs/operators'; -import { mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service'; -import { CallbackContext } from '../flows/callback-context'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service'; import { SilentRenewService } from '../iframe/silent-renew.service'; import { LoggerService } from '../logging/logger.service'; -import { LoginResponse } from '../login/login-response'; +import type { LoginResponse } from '../login/login-response'; import { PublicEventsService } from '../public-events/public-events.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockProvider } from '../testing/mock'; import { UserService } from '../user-data/user.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service'; @@ -70,19 +71,19 @@ describe('RefreshSessionService ', () => { }); describe('userForceRefreshSession', () => { - it('should persist params refresh when extra custom params given and useRefreshToken is true', waitForAsync(() => { - spyOn( + it('should persist params refresh when extra custom params given and useRefreshToken is true', async () => { + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + ).mockReturnValue(of(null)); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - const writeSpy = spyOn(storagePersistenceService, 'write'); + const writeSpy = vi.spyOn(storagePersistenceService, 'write'); const allConfigs = [ { configId: 'configId1', @@ -94,26 +95,26 @@ describe('RefreshSessionService ', () => { const extraCustomParams = { extra: 'custom' }; refreshSessionService - .userForceRefreshSession(allConfigs[0], allConfigs, extraCustomParams) + .userForceRefreshSession(allConfigs[0]!, allConfigs, extraCustomParams) .subscribe(() => { - expect(writeSpy).toHaveBeenCalledOnceWith( + expect(writeSpy).toHaveBeenCalledExactlyOnceWith( 'storageCustomParamsRefresh', extraCustomParams, allConfigs[0] ); }); - })); + }); - it('should persist storageCustomParamsAuthRequest when extra custom params given and useRefreshToken is false', waitForAsync(() => { - spyOn( + it('should persist storageCustomParamsAuthRequest when extra custom params given and useRefreshToken is false', async () => { + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + ).mockReturnValue(of(null)); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); const allConfigs = [ @@ -123,31 +124,31 @@ describe('RefreshSessionService ', () => { silentRenewTimeoutInSeconds: 10, }, ]; - const writeSpy = spyOn(storagePersistenceService, 'write'); + const writeSpy = vi.spyOn(storagePersistenceService, 'write'); const extraCustomParams = { extra: 'custom' }; refreshSessionService - .userForceRefreshSession(allConfigs[0], allConfigs, extraCustomParams) + .userForceRefreshSession(allConfigs[0]!, allConfigs, extraCustomParams) .subscribe(() => { - expect(writeSpy).toHaveBeenCalledOnceWith( + expect(writeSpy).toHaveBeenCalledExactlyOnceWith( 'storageCustomParamsAuthRequest', extraCustomParams, allConfigs[0] ); }); - })); + }); - it('should NOT persist customparams if no customparams are given', waitForAsync(() => { - spyOn( + it('should NOT persist customparams if no customparams are given', async () => { + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + ).mockReturnValue(of(null)); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); const allConfigs = [ @@ -157,20 +158,20 @@ describe('RefreshSessionService ', () => { silentRenewTimeoutInSeconds: 10, }, ]; - const writeSpy = spyOn(storagePersistenceService, 'write'); + const writeSpy = vi.spyOn(storagePersistenceService, 'write'); refreshSessionService - .userForceRefreshSession(allConfigs[0], allConfigs) + .userForceRefreshSession(allConfigs[0]!, allConfigs) .subscribe(() => { expect(writeSpy).not.toHaveBeenCalled(); }); - })); + }); - it('should call resetSilentRenewRunning in case of an error', waitForAsync(() => { - spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue( + it('should call resetSilentRenewRunning in case of an error', async () => { + vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue( throwError(() => new Error('error')) ); - spyOn(flowsDataService, 'resetSilentRenewRunning'); + vi.spyOn(flowsDataService, 'resetSilentRenewRunning'); const allConfigs = [ { configId: 'configId1', @@ -180,7 +181,7 @@ describe('RefreshSessionService ', () => { ]; refreshSessionService - .userForceRefreshSession(allConfigs[0], allConfigs) + .userForceRefreshSession(allConfigs[0]!, allConfigs) .subscribe({ next: () => { fail('It should not return any result.'); @@ -191,16 +192,16 @@ describe('RefreshSessionService ', () => { complete: () => { expect( flowsDataService.resetSilentRenewRunning - ).toHaveBeenCalledOnceWith(allConfigs[0]); + ).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); }, }); - })); + }); - it('should call resetSilentRenewRunning in case of no error', waitForAsync(() => { - spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue( + it('should call resetSilentRenewRunning in case of no error', async () => { + vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue( of({} as LoginResponse) ); - spyOn(flowsDataService, 'resetSilentRenewRunning'); + vi.spyOn(flowsDataService, 'resetSilentRenewRunning'); const allConfigs = [ { configId: 'configId1', @@ -210,7 +211,7 @@ describe('RefreshSessionService ', () => { ]; refreshSessionService - .userForceRefreshSession(allConfigs[0], allConfigs) + .userForceRefreshSession(allConfigs[0]!, allConfigs) .subscribe({ error: () => { fail('It should not return any error.'); @@ -218,27 +219,29 @@ describe('RefreshSessionService ', () => { complete: () => { expect( flowsDataService.resetSilentRenewRunning - ).toHaveBeenCalledOnceWith(allConfigs[0]); + ).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); }, }); - })); + }); }); describe('forceRefreshSession', () => { - it('only calls start refresh session and returns idToken and accessToken if auth is true', waitForAsync(() => { - spyOn( + it('only calls start refresh session and returns idToken and accessToken if auth is true', async () => { + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + ).mockReturnValue(of(null)); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - spyOn(authStateService, 'getIdToken').and.returnValue('id-token'); - spyOn(authStateService, 'getAccessToken').and.returnValue('access-token'); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('id-token'); + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue( + 'access-token' + ); const allConfigs = [ { configId: 'configId1', @@ -247,23 +250,23 @@ describe('RefreshSessionService ', () => { ]; refreshSessionService - .forceRefreshSession(allConfigs[0], allConfigs) + .forceRefreshSession(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result.idToken).toEqual('id-token'); expect(result.accessToken).toEqual('access-token'); }); - })); + }); - it('only calls start refresh session and returns null if auth is false', waitForAsync(() => { - spyOn( + it('only calls start refresh session and returns null if auth is false', async () => { + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + ).mockReturnValue(of(null)); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); const allConfigs = [ @@ -274,7 +277,7 @@ describe('RefreshSessionService ', () => { ]; refreshSessionService - .forceRefreshSession(allConfigs[0], allConfigs) + .forceRefreshSession(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: false, @@ -285,24 +288,24 @@ describe('RefreshSessionService ', () => { configId: 'configId1', }); }); - })); + }); - it('calls start refresh session and waits for completed, returns idtoken and accesstoken if auth is true', waitForAsync(() => { - spyOn( + it('calls start refresh session and waits for completed, returns idtoken and accesstoken if auth is true', async () => { + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + ).mockReturnValue(of(null)); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - spyOnProperty( + vi.spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' - ).and.returnValue( + ).mockReturnValue( of({ authResult: { id_token: 'some-id_token', @@ -318,29 +321,29 @@ describe('RefreshSessionService ', () => { ]; refreshSessionService - .forceRefreshSession(allConfigs[0], allConfigs) + .forceRefreshSession(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result.idToken).toBeDefined(); expect(result.accessToken).toBeDefined(); }); - })); + }); - it('calls start refresh session and waits for completed, returns LoginResponse if auth is false', waitForAsync(() => { - spyOn( + it('calls start refresh session and waits for completed, returns LoginResponse if auth is false', async () => { + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + ).mockReturnValue(of(null)); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - spyOnProperty( + vi.spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' - ).and.returnValue(of(null)); + ).mockReturnValue(of(null)); const allConfigs = [ { configId: 'configId1', @@ -349,7 +352,7 @@ describe('RefreshSessionService ', () => { ]; refreshSessionService - .forceRefreshSession(allConfigs[0], allConfigs) + .forceRefreshSession(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: false, @@ -360,23 +363,23 @@ describe('RefreshSessionService ', () => { configId: 'configId1', }); }); - })); + }); - it('occurs timeout error and retry mechanism exhausted max retry count throws error', fakeAsync(() => { - spyOn( + it('occurs timeout error and retry mechanism exhausted max retry count throws error', async () => { + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOnProperty( + ).mockReturnValue(of(null)); + vi.spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' - ).and.returnValue(of(null).pipe(delay(11000))); + ).mockReturnValue(of(null).pipe(delay(11000))); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); const allConfigs = [ @@ -386,14 +389,14 @@ describe('RefreshSessionService ', () => { }, ]; - const resetSilentRenewRunningSpy = spyOn( + const resetSilentRenewRunningSpy = vi.spyOn( flowsDataService, 'resetSilentRenewRunning' ); const expectedInvokeCount = MAX_RETRY_ATTEMPTS; refreshSessionService - .forceRefreshSession(allConfigs[0], allConfigs) + .forceRefreshSession(allConfigs[0]!, allConfigs) .subscribe({ next: () => { fail('It should not return any result.'); @@ -407,9 +410,9 @@ describe('RefreshSessionService ', () => { }); tick(allConfigs[0].silentRenewTimeoutInSeconds * 10000); - })); + }); - it('occurs unknown error throws it to subscriber', fakeAsync(() => { + it('occurs unknown error throws it to subscriber', async () => { const allConfigs = [ { configId: 'configId1', @@ -419,29 +422,29 @@ describe('RefreshSessionService ', () => { const expectedErrorMessage = 'Test error message'; - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(false); - spyOnProperty( + ).mockReturnValue(false); + vi.spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' - ).and.returnValue(of(null)); - spyOn( + ).mockReturnValue(of(null)); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(throwError(() => new Error(expectedErrorMessage))); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + ).mockReturnValue(throwError(() => new Error(expectedErrorMessage))); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - const resetSilentRenewRunningSpy = spyOn( + const resetSilentRenewRunningSpy = vi.spyOn( flowsDataService, 'resetSilentRenewRunning' ); refreshSessionService - .forceRefreshSession(allConfigs[0], allConfigs) + .forceRefreshSession(allConfigs[0]!, allConfigs) .subscribe({ next: () => { fail('It should not return any result.'); @@ -452,10 +455,10 @@ describe('RefreshSessionService ', () => { expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled(); }, }); - })); + }); describe('NOT isCurrentFlowCodeFlowWithRefreshTokens', () => { - it('does return null when not authenticated', waitForAsync(() => { + it('does return null when not authenticated', async () => { const allConfigs = [ { configId: 'configId1', @@ -463,24 +466,24 @@ describe('RefreshSessionService ', () => { }, ]; - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue( + ).mockReturnValue(of(null)); + vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - spyOnProperty( + vi.spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' - ).and.returnValue(of(null)); + ).mockReturnValue(of(null)); refreshSessionService - .forceRefreshSession(allConfigs[0], allConfigs) + .forceRefreshSession(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ isAuthenticated: false, @@ -491,9 +494,9 @@ describe('RefreshSessionService ', () => { configId: 'configId1', }); }); - })); + }); - it('return value only returns once', waitForAsync(() => { + it('return value only returns once', async () => { const allConfigs = [ { configId: 'configId1', @@ -501,18 +504,18 @@ describe('RefreshSessionService ', () => { }, ]; - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( refreshSessionService as any, 'startRefreshSession' - ).and.returnValue(of(null)); - spyOnProperty( + ).mockReturnValue(of(null)); + vi.spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' - ).and.returnValue( + ).mockReturnValue( of({ authResult: { id_token: 'some-id_token', @@ -520,13 +523,12 @@ describe('RefreshSessionService ', () => { }, } as CallbackContext) ); - const spyInsideMap = spyOn( - authStateService, - 'areAuthStorageTokensValid' - ).and.returnValue(true); + const spyInsideMap = vi + .spyOn(authStateService, 'areAuthStorageTokensValid') + .mockReturnValue(true); refreshSessionService - .forceRefreshSession(allConfigs[0], allConfigs) + .forceRefreshSession(allConfigs[0]!, allConfigs) .subscribe((result) => { expect(result).toEqual({ idToken: 'some-id_token', @@ -537,33 +539,33 @@ describe('RefreshSessionService ', () => { }); expect(spyInsideMap).toHaveBeenCalledTimes(1); }); - })); + }); }); }); describe('startRefreshSession', () => { - it('returns null if no auth well known endpoint defined', waitForAsync(() => { - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); + it('returns null if no auth well known endpoint defined', async () => { + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true); (refreshSessionService as any) .startRefreshSession() .subscribe((result: any) => { expect(result).toBe(null); }); - })); + }); - it('returns null if silent renew Is running', waitForAsync(() => { - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); + it('returns null if silent renew Is running', async () => { + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true); (refreshSessionService as any) .startRefreshSession() .subscribe((result: any) => { expect(result).toBe(null); }); - })); + }); - it('calls `setSilentRenewRunning` when should be executed', waitForAsync(() => { - const setSilentRenewRunningSpy = spyOn( + it('calls `setSilentRenewRunning` when should be executed', async () => { + const setSilentRenewRunningSpy = vi.spyOn( flowsDataService, 'setSilentRenewRunning' ); @@ -574,30 +576,30 @@ describe('RefreshSessionService ', () => { }, ]; - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn( + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( refreshSessionRefreshTokenService, 'refreshSessionWithRefreshTokens' - ).and.returnValue(of({} as CallbackContext)); + ).mockReturnValue(of({} as CallbackContext)); (refreshSessionService as any) - .startRefreshSession(allConfigs[0], allConfigs) + .startRefreshSession(allConfigs[0]!, allConfigs) .subscribe(() => { expect(setSilentRenewRunningSpy).toHaveBeenCalled(); }); - })); + }); - it('calls refreshSessionWithRefreshTokens when current flow is codeflow with refresh tokens', waitForAsync(() => { - spyOn(flowsDataService, 'setSilentRenewRunning'); + it('calls refreshSessionWithRefreshTokens when current flow is codeflow with refresh tokens', async () => { + vi.spyOn(flowsDataService, 'setSilentRenewRunning'); const allConfigs = [ { configId: 'configId1', @@ -605,30 +607,32 @@ describe('RefreshSessionService ', () => { }, ]; - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn( + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(true); - const refreshSessionWithRefreshTokensSpy = spyOn( - refreshSessionRefreshTokenService, - 'refreshSessionWithRefreshTokens' - ).and.returnValue(of({} as CallbackContext)); + ).mockReturnValue(true); + const refreshSessionWithRefreshTokensSpy = vi + .spyOn( + refreshSessionRefreshTokenService, + 'refreshSessionWithRefreshTokens' + ) + .mockReturnValue(of({} as CallbackContext)); (refreshSessionService as any) - .startRefreshSession(allConfigs[0], allConfigs) + .startRefreshSession(allConfigs[0]!, allConfigs) .subscribe(() => { expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled(); }); - })); + }); - it('calls refreshSessionWithIframe when current flow is NOT codeflow with refresh tokens', waitForAsync(() => { - spyOn(flowsDataService, 'setSilentRenewRunning'); + it('calls refreshSessionWithIframe when current flow is NOT codeflow with refresh tokens', async () => { + vi.spyOn(flowsDataService, 'setSilentRenewRunning'); const allConfigs = [ { configId: 'configId1', @@ -636,32 +640,33 @@ describe('RefreshSessionService ', () => { }, ]; - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - spyOn( + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' - ).and.returnValue(false); - const refreshSessionWithRefreshTokensSpy = spyOn( - refreshSessionRefreshTokenService, - 'refreshSessionWithRefreshTokens' - ).and.returnValue(of({} as CallbackContext)); + ).mockReturnValue(false); + const refreshSessionWithRefreshTokensSpy = vi + .spyOn( + refreshSessionRefreshTokenService, + 'refreshSessionWithRefreshTokens' + ) + .mockReturnValue(of({} as CallbackContext)); - const refreshSessionWithIframeSpy = spyOn( - refreshSessionIframeService, - 'refreshSessionWithIframe' - ).and.returnValue(of(false)); + const refreshSessionWithIframeSpy = vi + .spyOn(refreshSessionIframeService, 'refreshSessionWithIframe') + .mockReturnValue(of(false)); (refreshSessionService as any) - .startRefreshSession(allConfigs[0], allConfigs) + .startRefreshSession(allConfigs[0]!, allConfigs) .subscribe(() => { expect(refreshSessionWithRefreshTokensSpy).not.toHaveBeenCalled(); expect(refreshSessionWithIframeSpy).toHaveBeenCalled(); }); - })); + }); }); }); diff --git a/src/callback/refresh-session.service.ts b/src/callback/refresh-session.service.ts index bea2425..496da4a 100644 --- a/src/callback/refresh-session.service.ts +++ b/src/callback/refresh-session.service.ts @@ -1,10 +1,10 @@ -import { inject, Injectable } from 'injection-js'; +import { Injectable, inject } from 'injection-js'; import { + type Observable, + TimeoutError, forkJoin, - Observable, of, throwError, - TimeoutError, timer, } from 'rxjs'; import { @@ -18,13 +18,13 @@ import { } from 'rxjs/operators'; import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service'; import { SilentRenewService } from '../iframe/silent-renew.service'; import { LoggerService } from '../logging/logger.service'; -import { LoginResponse } from '../login/login-response'; +import type { LoginResponse } from '../login/login-response'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { UserService } from '../user-data/user.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; diff --git a/src/config/auth-well-known/auth-well-known-data.service.spec.ts b/src/config/auth-well-known/auth-well-known-data.service.spec.ts index 383f906..7fe6736 100644 --- a/src/config/auth-well-known/auth-well-known-data.service.spec.ts +++ b/src/config/auth-well-known/auth-well-known-data.service.spec.ts @@ -1,11 +1,12 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; -import { createRetriableStream } from '../../../test/create-retriable-stream.helper'; +import { vi } from 'vitest'; import { DataService } from '../../api/data.service'; import { LoggerService } from '../../logging/logger.service'; +import { createRetriableStream } from '../../testing/create-retriable-stream.helper'; +import { mockProvider } from '../../testing/mock'; import { AuthWellKnownDataService } from './auth-well-known-data.service'; -import { AuthWellKnownEndpoints } from './auth-well-known-endpoints'; +import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints'; const DUMMY_WELL_KNOWN_DOCUMENT = { issuer: 'https://identity-server.test/realms/main', @@ -51,56 +52,65 @@ describe('AuthWellKnownDataService', () => { }); describe('getWellKnownDocument', () => { - it('should add suffix if it does not exist on current URL', waitForAsync(() => { - const dataServiceSpy = spyOn(dataService, 'get').and.returnValue( - of(null) - ); + it('should add suffix if it does not exist on current URL', async () => { + const dataServiceSpy = vi + .spyOn(dataService, 'get') + .mockReturnValue(of(null)); const urlWithoutSuffix = 'myUrl'; const urlWithSuffix = `${urlWithoutSuffix}/.well-known/openid-configuration`; (service as any) .getWellKnownDocument(urlWithoutSuffix, { configId: 'configId1' }) .subscribe(() => { - expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, { - configId: 'configId1', - }); + expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith( + urlWithSuffix, + { + configId: 'configId1', + } + ); }); - })); + }); - it('should not add suffix if it does exist on current url', waitForAsync(() => { - const dataServiceSpy = spyOn(dataService, 'get').and.returnValue( - of(null) - ); + it('should not add suffix if it does exist on current url', async () => { + const dataServiceSpy = vi + .spyOn(dataService, 'get') + .mockReturnValue(of(null)); const urlWithSuffix = `myUrl/.well-known/openid-configuration`; (service as any) .getWellKnownDocument(urlWithSuffix, { configId: 'configId1' }) .subscribe(() => { - expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, { - configId: 'configId1', - }); + expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith( + urlWithSuffix, + { + configId: 'configId1', + } + ); }); - })); + }); - it('should not add suffix if it does exist in the middle of current url', waitForAsync(() => { - const dataServiceSpy = spyOn(dataService, 'get').and.returnValue( - of(null) - ); + it('should not add suffix if it does exist in the middle of current url', async () => { + const dataServiceSpy = vi + .spyOn(dataService, 'get') + .mockReturnValue(of(null)); const urlWithSuffix = `myUrl/.well-known/openid-configuration/and/some/more/stuff`; (service as any) .getWellKnownDocument(urlWithSuffix, { configId: 'configId1' }) .subscribe(() => { - expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, { - configId: 'configId1', - }); + expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith( + urlWithSuffix, + { + configId: 'configId1', + } + ); }); - })); + }); - it('should use the custom suffix provided in the config', waitForAsync(() => { - const dataServiceSpy = spyOn(dataService, 'get').and.returnValue( - of(null) - ); + it('should use the custom suffix provided in the config', async () => { + const dataServiceSpy = vi + .spyOn(dataService, 'get') + .mockReturnValue(of(null)); const urlWithoutSuffix = `myUrl`; const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`; @@ -110,15 +120,18 @@ describe('AuthWellKnownDataService', () => { authWellknownUrlSuffix: '/.well-known/test-openid-configuration', }) .subscribe(() => { - expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, { - configId: 'configId1', - authWellknownUrlSuffix: '/.well-known/test-openid-configuration', - }); + expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith( + urlWithSuffix, + { + configId: 'configId1', + authWellknownUrlSuffix: '/.well-known/test-openid-configuration', + } + ); }); - })); + }); - it('should retry once', waitForAsync(() => { - spyOn(dataService, 'get').and.returnValue( + it('should retry once', async () => { + vi.spyOn(dataService, 'get').mockReturnValue( createRetriableStream( throwError(() => new Error('one')), of(DUMMY_WELL_KNOWN_DOCUMENT) @@ -133,10 +146,10 @@ describe('AuthWellKnownDataService', () => { expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); }, }); - })); + }); - it('should retry twice', waitForAsync(() => { - spyOn(dataService, 'get').and.returnValue( + it('should retry twice', async () => { + vi.spyOn(dataService, 'get').mockReturnValue( createRetriableStream( throwError(() => new Error('one')), throwError(() => new Error('two')), @@ -152,10 +165,10 @@ describe('AuthWellKnownDataService', () => { expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); }, }); - })); + }); - it('should fail after three tries', waitForAsync(() => { - spyOn(dataService, 'get').and.returnValue( + it('should fail after three tries', async () => { + vi.spyOn(dataService, 'get').mockReturnValue( createRetriableStream( throwError(() => new Error('one')), throwError(() => new Error('two')), @@ -169,17 +182,16 @@ describe('AuthWellKnownDataService', () => { expect(err).toBeTruthy(); }, }); - })); + }); }); describe('getWellKnownEndPointsForConfig', () => { - it('calling internal getWellKnownDocument and maps', waitForAsync(() => { - spyOn(dataService, 'get').and.returnValue(of({ jwks_uri: 'jwks_uri' })); + it('calling internal getWellKnownDocument and maps', async () => { + vi.spyOn(dataService, 'get').mockReturnValue( + of({ jwks_uri: 'jwks_uri' }) + ); - const spy = spyOn( - service as any, - 'getWellKnownDocument' - ).and.callThrough(); + const spy = vi.spyOn(service as any, 'getWellKnownDocument')(); service .getWellKnownEndPointsForConfig({ @@ -191,10 +203,10 @@ describe('AuthWellKnownDataService', () => { expect((result as any).jwks_uri).toBeUndefined(); expect(result.jwksUri).toBe('jwks_uri'); }); - })); + }); - it('throws error and logs if no authwellknownUrl is given', waitForAsync(() => { - const loggerSpy = spyOn(loggerService, 'logError'); + it('throws error and logs if no authwellknownUrl is given', async () => { + const loggerSpy = vi.spyOn(loggerService, 'logError'); const config = { configId: 'configId1', authWellknownEndpointUrl: undefined, @@ -202,17 +214,19 @@ describe('AuthWellKnownDataService', () => { service.getWellKnownEndPointsForConfig(config).subscribe({ error: (error) => { - expect(loggerSpy).toHaveBeenCalledOnceWith( + expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( config, 'no authWellknownEndpoint given!' ); expect(error.message).toEqual('no authWellknownEndpoint given!'); }, }); - })); + }); - it('should merge the mapped endpoints with the provided endpoints', waitForAsync(() => { - spyOn(dataService, 'get').and.returnValue(of(DUMMY_WELL_KNOWN_DOCUMENT)); + it('should merge the mapped endpoints with the provided endpoints', async () => { + vi.spyOn(dataService, 'get').mockReturnValue( + of(DUMMY_WELL_KNOWN_DOCUMENT) + ); const expected: AuthWellKnownEndpoints = { endSessionEndpoint: 'config-endSessionEndpoint', @@ -232,6 +246,6 @@ describe('AuthWellKnownDataService', () => { .subscribe((result) => { expect(result).toEqual(jasmine.objectContaining(expected)); }); - })); + }); }); }); diff --git a/src/config/auth-well-known/auth-well-known.service.spec.ts b/src/config/auth-well-known/auth-well-known.service.spec.ts index 099d93e..b35130e 100644 --- a/src/config/auth-well-known/auth-well-known.service.spec.ts +++ b/src/config/auth-well-known/auth-well-known.service.spec.ts @@ -1,9 +1,10 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; +import { vi } from 'vitest'; import { EventTypes } from '../../public-events/event-types'; import { PublicEventsService } from '../../public-events/public-events.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; +import { mockProvider } from '../../testing/mock'; import { AuthWellKnownDataService } from './auth-well-known-data.service'; import { AuthWellKnownService } from './auth-well-known.service'; @@ -22,9 +23,6 @@ describe('AuthWellKnownService', () => { mockProvider(StoragePersistenceService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(AuthWellKnownService); dataService = TestBed.inject(AuthWellKnownDataService); storagePersistenceService = TestBed.inject(StoragePersistenceService); @@ -36,7 +34,7 @@ describe('AuthWellKnownService', () => { }); describe('getAuthWellKnownEndPoints', () => { - it('getAuthWellKnownEndPoints throws an error if not config provided', waitForAsync(() => { + it('getAuthWellKnownEndPoints throws an error if not config provided', async () => { service.queryAndStoreAuthWellKnownEndPoints(null).subscribe({ error: (error) => { expect(error).toEqual( @@ -46,17 +44,18 @@ describe('AuthWellKnownService', () => { ); }, }); - })); + }); - it('getAuthWellKnownEndPoints calls always dataservice', waitForAsync(() => { - const dataServiceSpy = spyOn( - dataService, - 'getWellKnownEndPointsForConfig' - ).and.returnValue(of({ issuer: 'anything' })); + it('getAuthWellKnownEndPoints calls always dataservice', async () => { + const dataServiceSpy = vi + .spyOn(dataService, 'getWellKnownEndPointsForConfig') + .mockReturnValue(of({ issuer: 'anything' })); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ issuer: 'anything' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ issuer: 'anything' }) + ); service .queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' }) @@ -65,18 +64,19 @@ describe('AuthWellKnownService', () => { expect(dataServiceSpy).toHaveBeenCalled(); expect(result).toEqual({ issuer: 'anything' }); }); - })); + }); - it('getAuthWellKnownEndPoints stored the result if http call is made', waitForAsync(() => { - const dataServiceSpy = spyOn( - dataService, - 'getWellKnownEndPointsForConfig' - ).and.returnValue(of({ issuer: 'anything' })); + it('getAuthWellKnownEndPoints stored the result if http call is made', async () => { + const dataServiceSpy = vi + .spyOn(dataService, 'getWellKnownEndPointsForConfig') + .mockReturnValue(of({ issuer: 'anything' })); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue(null); - const storeSpy = spyOn(service, 'storeWellKnownEndpoints'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => null + ); + const storeSpy = vi.spyOn(service, 'storeWellKnownEndpoints'); service .queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' }) @@ -85,13 +85,13 @@ describe('AuthWellKnownService', () => { expect(storeSpy).toHaveBeenCalled(); expect(result).toEqual({ issuer: 'anything' }); }); - })); + }); - it('throws `ConfigLoadingFailed` event when error happens from http', waitForAsync(() => { - spyOn(dataService, 'getWellKnownEndPointsForConfig').and.returnValue( + it('throws `ConfigLoadingFailed` event when error happens from http', async () => { + vi.spyOn(dataService, 'getWellKnownEndPointsForConfig').mockReturnValue( throwError(() => new Error('error')) ); - const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent'); + const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent'); service .queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' }) @@ -99,12 +99,12 @@ describe('AuthWellKnownService', () => { error: (err) => { expect(err).toBeTruthy(); expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1); - expect(publicEventsServiceSpy).toHaveBeenCalledOnceWith( + expect(publicEventsServiceSpy).toHaveBeenCalledExactlyOnceWith( EventTypes.ConfigLoadingFailed, null ); }, }); - })); + }); }); }); diff --git a/src/config/config.service.spec.ts b/src/config/config.service.spec.ts index 609b314..8862ba9 100644 --- a/src/config/config.service.spec.ts +++ b/src/config/config.service.spec.ts @@ -1,15 +1,16 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { mockAbstractProvider, mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; import { EventTypes } from '../public-events/event-types'; import { PublicEventsService } from '../public-events/public-events.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockAbstractProvider, mockProvider } from '../testing/mock'; import { PlatformProvider } from '../utils/platform-provider/platform.provider'; import { AuthWellKnownService } from './auth-well-known/auth-well-known.service'; import { ConfigurationService } from './config.service'; import { StsConfigLoader, StsConfigStaticLoader } from './loader/config-loader'; -import { OpenIdConfiguration } from './openid-configuration'; +import type { OpenIdConfiguration } from './openid-configuration'; import { ConfigValidationService } from './validation/config-validation.service'; describe('Configuration Service', () => { @@ -34,9 +35,6 @@ describe('Configuration Service', () => { mockAbstractProvider(StsConfigLoader, StsConfigStaticLoader), ], }); - }); - - beforeEach(() => { configService = TestBed.inject(ConfigurationService); publicEventsService = TestBed.inject(PublicEventsService); authWellKnownService = TestBed.inject(AuthWellKnownService); @@ -88,47 +86,53 @@ describe('Configuration Service', () => { }); describe('getOpenIDConfiguration', () => { - it(`if config is already saved 'loadConfigs' is not called`, waitForAsync(() => { + it(`if config is already saved 'loadConfigs' is not called`, async () => { (configService as any).configsInternal = { configId1: { configId: 'configId1' }, configId2: { configId: 'configId2' }, }; - const spy = spyOn(configService as any, 'loadConfigs'); + const spy = vi.spyOn(configService as any, 'loadConfigs'); configService.getOpenIDConfiguration('configId1').subscribe((config) => { expect(config).toBeTruthy(); expect(spy).not.toHaveBeenCalled(); }); - })); + }); - it(`if config is NOT already saved 'loadConfigs' is called`, waitForAsync(() => { + it(`if config is NOT already saved 'loadConfigs' is called`, async () => { const configs = [{ configId: 'configId1' }, { configId: 'configId2' }]; - const spy = spyOn(configService as any, 'loadConfigs').and.returnValue( - of(configs) - ); + const spy = vi + .spyOn(configService as any, 'loadConfigs') + .mockReturnValue(of(configs)); - spyOn(configValidationService, 'validateConfig').and.returnValue(true); + vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true); configService.getOpenIDConfiguration('configId1').subscribe((config) => { expect(config).toBeTruthy(); expect(spy).toHaveBeenCalled(); }); - })); + }); - it(`returns null if config is not valid`, waitForAsync(() => { + it('returns null if config is not valid', async () => { const configs = [{ configId: 'configId1' }]; - spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs)); - spyOn(configValidationService, 'validateConfig').and.returnValue(false); - const consoleSpy = spyOn(console, 'warn'); + vi.spyOn(configService as any, 'loadConfigs').mockReturnValue( + of(configs) + ); + vi.spyOn(configValidationService, 'validateConfig').mockReturnValue( + false + ); + const consoleSpy = vi.spyOn(console, 'warn'); configService.getOpenIDConfiguration('configId1').subscribe((config) => { expect(config).toBeNull(); - expect(consoleSpy).toHaveBeenCalledOnceWith(`[oidc-client-rx] No configuration found for config id 'configId1'.`) + expect(consoleSpy).toHaveBeenCalledExactlyOnceWith( + `[oidc-client-rx] No configuration found for config id 'configId1'.` + ); }); - })); + }); - it(`returns null if configs are stored but not existing ID is passed`, waitForAsync(() => { + it('returns null if configs are stored but not existing ID is passed', async () => { (configService as any).configsInternal = { configId1: { configId: 'configId1' }, configId2: { configId: 'configId2' }, @@ -139,16 +143,18 @@ describe('Configuration Service', () => { .subscribe((config) => { expect(config).toBeNull(); }); - })); + }); - it(`sets authWellKnownEndPoints on config if authWellKnownEndPoints is stored`, waitForAsync(() => { + it('sets authWellKnownEndPoints on config if authWellKnownEndPoints is stored', async () => { const configs = [{ configId: 'configId1' }]; - spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs)); - spyOn(configValidationService, 'validateConfig').and.returnValue(true); - const consoleSpy = spyOn(console, 'warn'); + vi.spyOn(configService as any, 'loadConfigs').mockReturnValue( + of(configs) + ); + vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true); + const consoleSpy = vi.spyOn(console, 'warn'); - spyOn(storagePersistenceService, 'read').and.returnValue({ + vi.spyOn(storagePersistenceService, 'read').mockReturnValue({ issuer: 'auth-well-known', }); @@ -156,30 +162,32 @@ describe('Configuration Service', () => { expect(config?.authWellknownEndpoints).toEqual({ issuer: 'auth-well-known', }); - expect(consoleSpy).not.toHaveBeenCalled() + expect(consoleSpy).not.toHaveBeenCalled(); }); - })); + }); - it(`fires ConfigLoaded if authWellKnownEndPoints is stored`, waitForAsync(() => { + it('fires ConfigLoaded if authWellKnownEndPoints is stored', async () => { const configs = [{ configId: 'configId1' }]; - spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs)); - spyOn(configValidationService, 'validateConfig').and.returnValue(true); - spyOn(storagePersistenceService, 'read').and.returnValue({ + vi.spyOn(configService as any, 'loadConfigs').mockReturnValue( + of(configs) + ); + vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true); + vi.spyOn(storagePersistenceService, 'read').mockReturnValue({ issuer: 'auth-well-known', }); - const spy = spyOn(publicEventsService, 'fireEvent'); + const spy = vi.spyOn(publicEventsService, 'fireEvent'); configService.getOpenIDConfiguration('configId1').subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( EventTypes.ConfigLoaded, - jasmine.anything() + expect.anything() ); }); - })); + }); - it(`stores, uses and fires event when authwellknownendpoints are passed`, waitForAsync(() => { + it('stores, uses and fires event when authwellknownendpoints are passed', async () => { const configs = [ { configId: 'configId1', @@ -187,58 +195,60 @@ describe('Configuration Service', () => { }, ]; - spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs)); - spyOn(configValidationService, 'validateConfig').and.returnValue(true); - spyOn(storagePersistenceService, 'read').and.returnValue(null); + vi.spyOn(configService as any, 'loadConfigs').mockReturnValue( + of(configs) + ); + vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true); + vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null); - const fireEventSpy = spyOn(publicEventsService, 'fireEvent'); - const storeWellKnownEndpointsSpy = spyOn( + const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent'); + const storeWellKnownEndpointsSpy = vi.spyOn( authWellKnownService, 'storeWellKnownEndpoints' ); configService.getOpenIDConfiguration('configId1').subscribe((config) => { expect(config).toBeTruthy(); - expect(fireEventSpy).toHaveBeenCalledOnceWith( + expect(fireEventSpy).toHaveBeenCalledExactlyOnceWith( EventTypes.ConfigLoaded, - jasmine.anything() + expect.anything() ); - expect(storeWellKnownEndpointsSpy).toHaveBeenCalledOnceWith( + expect(storeWellKnownEndpointsSpy).toHaveBeenCalledExactlyOnceWith( config as OpenIdConfiguration, { issuer: 'auth-well-known', } ); }); - })); + }); }); describe('getOpenIDConfigurations', () => { - it(`returns correct result`, waitForAsync(() => { - spyOn(stsConfigLoader, 'loadConfigs').and.returnValue( + it('returns correct result', async () => { + vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue( of([ { configId: 'configId1' } as OpenIdConfiguration, { configId: 'configId2' } as OpenIdConfiguration, ]) ); - spyOn(configValidationService, 'validateConfig').and.returnValue(true); + vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true); configService.getOpenIDConfigurations('configId1').subscribe((result) => { expect(result.allConfigs.length).toEqual(2); expect(result.currentConfig).toBeTruthy(); }); - })); + }); - it(`created configId when configId is not set`, waitForAsync(() => { - spyOn(stsConfigLoader, 'loadConfigs').and.returnValue( + it('created configId when configId is not set', async () => { + vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue( of([ { clientId: 'clientId1' } as OpenIdConfiguration, { clientId: 'clientId2' } as OpenIdConfiguration, ]) ); - spyOn(configValidationService, 'validateConfig').and.returnValue(true); + vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true); configService.getOpenIDConfigurations().subscribe((result) => { expect(result.allConfigs.length).toEqual(2); @@ -249,17 +259,19 @@ describe('Configuration Service', () => { expect(result.currentConfig).toBeTruthy(); expect(result.currentConfig?.configId).toBeTruthy(); }); - })); + }); - it(`returns empty array if config is not valid`, waitForAsync(() => { - spyOn(stsConfigLoader, 'loadConfigs').and.returnValue( + it('returns empty array if config is not valid', async () => { + vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue( of([ { configId: 'configId1' } as OpenIdConfiguration, { configId: 'configId2' } as OpenIdConfiguration, ]) ); - spyOn(configValidationService, 'validateConfigs').and.returnValue(false); + vi.spyOn(configValidationService, 'validateConfigs').mockReturnValue( + false + ); configService .getOpenIDConfigurations() @@ -267,12 +279,12 @@ describe('Configuration Service', () => { expect(allConfigs).toEqual([]); expect(currentConfig).toBeNull(); }); - })); + }); }); describe('setSpecialCases', () => { - it(`should set special cases when current platform is browser`, () => { - spyOn(platformProvider, 'isBrowser').and.returnValue(false); + it('should set special cases when current platform is browser', () => { + vi.spyOn(platformProvider, 'isBrowser').mockReturnValue(false); const config = { configId: 'configId1' } as OpenIdConfiguration; diff --git a/src/config/config.service.ts b/src/config/config.service.ts index c7526c4..80ce2ab 100644 --- a/src/config/config.service.ts +++ b/src/config/config.service.ts @@ -1,6 +1,7 @@ -import {inject, Injectable, isDevMode} from 'injection-js'; -import { forkJoin, Observable, of } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, forkJoin, of } from 'rxjs'; import { concatMap, map } from 'rxjs/operators'; +import { injectAbstractType } from '../injection/inject'; import { LoggerService } from '../logging/logger.service'; import { EventTypes } from '../public-events/event-types'; import { PublicEventsService } from '../public-events/public-events.service'; @@ -9,7 +10,7 @@ import { PlatformProvider } from '../utils/platform-provider/platform.provider'; import { AuthWellKnownService } from './auth-well-known/auth-well-known.service'; import { DEFAULT_CONFIG } from './default-config'; import { StsConfigLoader } from './loader/config-loader'; -import { OpenIdConfiguration } from './openid-configuration'; +import type { OpenIdConfiguration } from './openid-configuration'; import { ConfigValidationService } from './validation/config-validation.service'; @Injectable() @@ -26,7 +27,7 @@ export class ConfigurationService { private readonly authWellKnownService = inject(AuthWellKnownService); - private readonly loader = inject(StsConfigLoader); + private readonly loader = injectAbstractType(StsConfigLoader); private readonly configValidationService = inject(ConfigValidationService); @@ -84,11 +85,14 @@ export class ConfigurationService { } private getConfig(configId?: string): OpenIdConfiguration | null { - if (Boolean(configId)) { + if (configId) { const config = this.configsInternal[configId!]; - if(!config && isDevMode()) { - console.warn(`[oidc-client-rx] No configuration found for config id '${configId}'.`); + if (!config) { + // biome-ignore lint/suspicious/noConsole: + console.warn( + `[oidc-client-rx] No configuration found for config id '${configId}'.` + ); } return config || null; @@ -165,7 +169,7 @@ export class ConfigurationService { configuration ); - if (!!alreadyExistingAuthWellKnownEndpoints) { + if (alreadyExistingAuthWellKnownEndpoints) { configuration.authWellknownEndpoints = alreadyExistingAuthWellKnownEndpoints; @@ -174,7 +178,7 @@ export class ConfigurationService { const passedAuthWellKnownEndpoints = configuration.authWellknownEndpoints; - if (!!passedAuthWellKnownEndpoints) { + if (passedAuthWellKnownEndpoints) { this.authWellKnownService.storeWellKnownEndpoints( configuration, passedAuthWellKnownEndpoints diff --git a/src/config/loader/config-loader.spec.ts b/src/config/loader/config-loader.spec.ts index 3ee4514..dc1b069 100644 --- a/src/config/loader/config-loader.spec.ts +++ b/src/config/loader/config-loader.spec.ts @@ -1,12 +1,12 @@ -import { waitForAsync } from '@angular/core/testing'; +import { waitForAsync } from '@/testing'; import { of } from 'rxjs'; -import { OpenIdConfiguration } from '../openid-configuration'; +import type { OpenIdConfiguration } from '../openid-configuration'; import { StsConfigHttpLoader, StsConfigStaticLoader } from './config-loader'; describe('ConfigLoader', () => { describe('StsConfigStaticLoader', () => { describe('loadConfigs', () => { - it('returns an array if an array is passed', waitForAsync(() => { + it('returns an array if an array is passed', async () => { const toPass = [ { configId: 'configId1' } as OpenIdConfiguration, { configId: 'configId2' } as OpenIdConfiguration, @@ -17,11 +17,11 @@ describe('ConfigLoader', () => { const result$ = loader.loadConfigs(); result$.subscribe((result) => { - expect(Array.isArray(result)).toBeTrue(); + expect(Array.isArray(result)).toBeTruthy(); }); - })); + }); - it('returns an array if only one config is passed', waitForAsync(() => { + it('returns an array if only one config is passed', async () => { const loader = new StsConfigStaticLoader({ configId: 'configId1', } as OpenIdConfiguration); @@ -29,15 +29,15 @@ describe('ConfigLoader', () => { const result$ = loader.loadConfigs(); result$.subscribe((result) => { - expect(Array.isArray(result)).toBeTrue(); + expect(Array.isArray(result)).toBeTruthy(); }); - })); + }); }); }); describe('StsConfigHttpLoader', () => { describe('loadConfigs', () => { - it('returns an array if an array of observables is passed', waitForAsync(() => { + it('returns an array if an array of observables is passed', async () => { const toPass = [ of({ configId: 'configId1' } as OpenIdConfiguration), of({ configId: 'configId2' } as OpenIdConfiguration), @@ -47,13 +47,13 @@ describe('ConfigLoader', () => { const result$ = loader.loadConfigs(); result$.subscribe((result) => { - expect(Array.isArray(result)).toBeTrue(); + expect(Array.isArray(result)).toBeTruthy(); expect(result[0].configId).toBe('configId1'); expect(result[1].configId).toBe('configId2'); }); - })); + }); - it('returns an array if an observable with a config array is passed', waitForAsync(() => { + it('returns an array if an observable with a config array is passed', async () => { const toPass = of([ { configId: 'configId1' } as OpenIdConfiguration, { configId: 'configId2' } as OpenIdConfiguration, @@ -63,13 +63,13 @@ describe('ConfigLoader', () => { const result$ = loader.loadConfigs(); result$.subscribe((result) => { - expect(Array.isArray(result)).toBeTrue(); + expect(Array.isArray(result)).toBeTruthy(); expect(result[0].configId).toBe('configId1'); expect(result[1].configId).toBe('configId2'); }); - })); + }); - it('returns an array if only one config is passed', waitForAsync(() => { + it('returns an array if only one config is passed', async () => { const loader = new StsConfigHttpLoader( of({ configId: 'configId1' } as OpenIdConfiguration) ); @@ -77,10 +77,10 @@ describe('ConfigLoader', () => { const result$ = loader.loadConfigs(); result$.subscribe((result) => { - expect(Array.isArray(result)).toBeTrue(); + expect(Array.isArray(result)).toBeTruthy(); expect(result[0].configId).toBe('configId1'); }); - })); + }); }); }); }); diff --git a/src/config/loader/config-loader.ts b/src/config/loader/config-loader.ts index 0dba6b9..6e4d190 100644 --- a/src/config/loader/config-loader.ts +++ b/src/config/loader/config-loader.ts @@ -1,7 +1,7 @@ -import { Provider } from 'injection-js'; -import { forkJoin, Observable, of } from 'rxjs'; +import type { Provider } from 'injection-js'; +import { type Observable, forkJoin, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../openid-configuration'; +import type { OpenIdConfiguration } from '../openid-configuration'; export class OpenIdConfigLoader { loader?: Provider; @@ -13,6 +13,7 @@ export abstract class StsConfigLoader { export class StsConfigStaticLoader implements StsConfigLoader { constructor( + // biome-ignore lint/style/noParameterProperties: private readonly passedConfigs: OpenIdConfiguration | OpenIdConfiguration[] ) {} @@ -27,6 +28,7 @@ export class StsConfigStaticLoader implements StsConfigLoader { export class StsConfigHttpLoader implements StsConfigLoader { constructor( + // biome-ignore lint/style/noParameterProperties: private readonly configs$: | Observable | Observable[] diff --git a/src/config/openid-configuration.ts b/src/config/openid-configuration.ts index 3dd95ab..b1bb650 100644 --- a/src/config/openid-configuration.ts +++ b/src/config/openid-configuration.ts @@ -1,5 +1,5 @@ -import { LogLevel } from '../logging/log-level'; -import { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints'; +import type { LogLevel } from '../logging/log-level'; +import type { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints'; export interface OpenIdConfiguration { /** @@ -207,5 +207,5 @@ export interface OpenIdConfiguration { /** * Disable cleaning up the popup when receiving invalid messages */ - disableCleaningPopupOnInvalidMessage?: boolean + disableCleaningPopupOnInvalidMessage?: boolean; } diff --git a/src/config/validation/config-validation.service.spec.ts b/src/config/validation/config-validation.service.spec.ts index 7bf529f..a6e2953 100644 --- a/src/config/validation/config-validation.service.spec.ts +++ b/src/config/validation/config-validation.service.spec.ts @@ -1,8 +1,9 @@ -import { TestBed } from '@angular/core/testing'; -import { mockProvider } from '../../../test/auto-mock'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; +import { vi } from 'vitest'; import { LogLevel } from '../../logging/log-level'; import { LoggerService } from '../../logging/logger.service'; -import { OpenIdConfiguration } from '../openid-configuration'; +import { mockProvider } from '../../testing/mock'; +import type { OpenIdConfiguration } from '../openid-configuration'; import { ConfigValidationService } from './config-validation.service'; import { allMultipleConfigRules } from './rules'; @@ -14,6 +15,8 @@ describe('Config Validation Service', () => { TestBed.configureTestingModule({ providers: [ConfigValidationService, mockProvider(LoggerService)], }); + configValidationService = TestBed.inject(ConfigValidationService); + loggerService = TestBed.inject(LoggerService); }); const VALID_CONFIG = { @@ -29,11 +32,6 @@ describe('Config Validation Service', () => { logLevel: LogLevel.Debug, }; - beforeEach(() => { - configValidationService = TestBed.inject(ConfigValidationService); - loggerService = TestBed.inject(LoggerService); - }); - it('should create', () => { expect(configValidationService).toBeTruthy(); }); @@ -42,26 +40,27 @@ describe('Config Validation Service', () => { const config = {}; const result = configValidationService.validateConfig(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('should return true for valid config', () => { const result = configValidationService.validateConfig(VALID_CONFIG); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); it('calls `logWarning` if one rule has warning level', () => { - const loggerWarningSpy = spyOn(loggerService, 'logWarning'); - const messageTypeSpy = spyOn( + const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning'); + const messageTypeSpy = vi.spyOn( configValidationService as any, 'getAllMessagesOfType' ); - messageTypeSpy - .withArgs('warning', jasmine.any(Array)) - .and.returnValue(['A warning message']); - messageTypeSpy.withArgs('error', jasmine.any(Array)).and.callThrough(); + mockImplementationWhenArgsEqual( + messageTypeSpy, + (arg1: any, arg2: any) => arg1 === 'warning' && Array.isArray(arg2), + () => ['A warning message'] + ); configValidationService.validateConfig(VALID_CONFIG); expect(loggerWarningSpy).toHaveBeenCalled(); @@ -72,7 +71,7 @@ describe('Config Validation Service', () => { const config = { ...VALID_CONFIG, clientId: '' } as OpenIdConfiguration; const result = configValidationService.validateConfig(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); }); @@ -84,7 +83,7 @@ describe('Config Validation Service', () => { } as OpenIdConfiguration; const result = configValidationService.validateConfig(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); }); @@ -93,7 +92,7 @@ describe('Config Validation Service', () => { const config = { ...VALID_CONFIG, redirectUrl: '' }; const result = configValidationService.validateConfig(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); }); @@ -107,7 +106,7 @@ describe('Config Validation Service', () => { } as OpenIdConfiguration; const result = configValidationService.validateConfig(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); }); @@ -120,12 +119,12 @@ describe('Config Validation Service', () => { scopes: 'scope1 scope2 but_no_offline_access', }; - const loggerSpy = spyOn(loggerService, 'logError'); - const loggerWarningSpy = spyOn(loggerService, 'logWarning'); + const loggerSpy = vi.spyOn(loggerService, 'logError'); + const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning'); const result = configValidationService.validateConfig(config); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); expect(loggerSpy).not.toHaveBeenCalled(); expect(loggerWarningSpy).toHaveBeenCalled(); }); @@ -146,47 +145,47 @@ describe('Config Validation Service', () => { scopes: 'scope1 scope2 but_no_offline_access', }; - const loggerErrorSpy = spyOn(loggerService, 'logError'); - const loggerWarningSpy = spyOn(loggerService, 'logWarning'); + const loggerErrorSpy = vi.spyOn(loggerService, 'logError'); + const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning'); const result = configValidationService.validateConfigs([ config1, config2, ]); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); expect(loggerErrorSpy).not.toHaveBeenCalled(); - expect(loggerWarningSpy.calls.argsFor(0)).toEqual([ + expect(vi.mocked(loggerWarningSpy).mock.calls[0]).toEqual([ config1, 'You added multiple configs with the same authority, clientId and scope', ]); - expect(loggerWarningSpy.calls.argsFor(1)).toEqual([ + expect(vi.mocked(loggerWarningSpy).mock.calls[1]).toEqual([ config2, 'You added multiple configs with the same authority, clientId and scope', ]); }); it('should return false and a better error message when config is not passed as object with config property', () => { - const loggerWarningSpy = spyOn(loggerService, 'logWarning'); + const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning'); const result = configValidationService.validateConfigs([]); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); expect(loggerWarningSpy).not.toHaveBeenCalled(); }); }); describe('validateConfigs', () => { it('calls internal method with empty array if something falsy is passed', () => { - const spy = spyOn( + const spy = vi.spyOn( configValidationService as any, 'validateConfigsInternal' - ).and.callThrough(); + ); const result = configValidationService.validateConfigs([]); - expect(result).toBeFalse(); - expect(spy).toHaveBeenCalledOnceWith([], allMultipleConfigRules); + expect(result).toBeFalsy(); + expect(spy).toHaveBeenCalledExactlyOnceWith([], allMultipleConfigRules); }); }); }); diff --git a/src/extractors/jwk.extractor.spec.ts b/src/extractors/jwk.extractor.spec.ts index 31fbdc1..e4e9db6 100644 --- a/src/extractors/jwk.extractor.spec.ts +++ b/src/extractors/jwk.extractor.spec.ts @@ -1,4 +1,5 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { CryptoService } from '../utils/crypto/crypto.service'; import { JwkExtractor, diff --git a/src/flows/callback-handling/code-flow-callback-handler.service.spec.ts b/src/flows/callback-handling/code-flow-callback-handler.service.spec.ts index d8a8108..2c5f362 100644 --- a/src/flows/callback-handling/code-flow-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/code-flow-callback-handler.service.spec.ts @@ -1,14 +1,15 @@ -import { HttpErrorResponse, HttpHeaders } from '@angular/common/http'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; +import { HttpErrorResponse, HttpHeaders } from '@ngify/http'; import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; -import { createRetriableStream } from '../../../test/create-retriable-stream.helper'; +import { vi } from 'vitest'; import { DataService } from '../../api/data.service'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; +import { createRetriableStream } from '../../testing/create-retriable-stream.helper'; +import { mockProvider } from '../../testing/mock'; import { UrlService } from '../../utils/url/url.service'; import { TokenValidationService } from '../../validation/token-validation.service'; -import { CallbackContext } from '../callback-context'; +import type { CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; import { CodeFlowCallbackHandlerService } from './code-flow-callback-handler.service'; @@ -46,13 +47,16 @@ describe('CodeFlowCallbackHandlerService', () => { }); describe('codeFlowCallback', () => { - it('throws error if no state is given', waitForAsync(() => { - const getUrlParameterSpy = spyOn( - urlService, - 'getUrlParameter' - ).and.returnValue('params'); + it('throws error if no state is given', async () => { + const getUrlParameterSpy = vi + .spyOn(urlService, 'getUrlParameter') + .mockReturnValue('params'); - getUrlParameterSpy.withArgs('test-url', 'state').and.returnValue(''); + mockImplementationWhenArgsEqual( + getUrlParameterSpy, + ['test-url', 'state'], + () => '' + ); service .codeFlowCallback('test-url', { configId: 'configId1' }) @@ -61,15 +65,14 @@ describe('CodeFlowCallbackHandlerService', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('throws error if no code is given', waitForAsync(() => { - const getUrlParameterSpy = spyOn( - urlService, - 'getUrlParameter' - ).and.returnValue('params'); + it('throws error if no code is given', async () => { + const getUrlParameterSpy = vi + .spyOn(urlService, 'getUrlParameter') + .mockReturnValue('params'); - getUrlParameterSpy.withArgs('test-url', 'code').and.returnValue(''); + getUrlParameterSpy.withArgs('test-url', 'code').mockReturnValue(''); service .codeFlowCallback('test-url', { configId: 'configId1' }) @@ -78,10 +81,10 @@ describe('CodeFlowCallbackHandlerService', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('returns callbackContext if all params are good', waitForAsync(() => { - spyOn(urlService, 'getUrlParameter').and.returnValue('params'); + it('returns callbackContext if all params are good', async () => { + vi.spyOn(urlService, 'getUrlParameter').mockReturnValue('params'); const expectedCallbackContext = { code: 'params', @@ -100,7 +103,7 @@ describe('CodeFlowCallbackHandlerService', () => { .subscribe((callbackContext) => { expect(callbackContext).toEqual(expectedCallbackContext); }); - })); + }); }); describe('codeFlowCodeRequest ', () => { @@ -112,11 +115,11 @@ describe('CodeFlowCallbackHandlerService', () => { url: 'https://identity-server.test/openid-connect/token', }); - it('throws error if state is not correct', waitForAsync(() => { - spyOn( + it('throws error if state is not correct', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); service .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) @@ -125,16 +128,18 @@ describe('CodeFlowCallbackHandlerService', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('throws error if authWellknownEndpoints is null is given', waitForAsync(() => { - spyOn( + it('throws error if authWellknownEndpoints is null is given', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue(null); + ).mockReturnValue(true); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => null + ); service .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) @@ -143,16 +148,18 @@ describe('CodeFlowCallbackHandlerService', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('throws error if tokenendpoint is null is given', waitForAsync(() => { - spyOn( + it('throws error if tokenendpoint is null is given', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ tokenEndpoint: null }); + ).mockReturnValue(true); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ tokenEndpoint: null }) + ); service .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) @@ -161,34 +168,36 @@ describe('CodeFlowCallbackHandlerService', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('calls dataService if all params are good', waitForAsync(() => { - const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); + it('calls dataService if all params are good', async () => { + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({})); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); service .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) .subscribe(() => { - expect(postSpy).toHaveBeenCalledOnceWith( + expect(postSpy).toHaveBeenCalledExactlyOnceWith( 'tokenEndpoint', undefined, { configId: 'configId1' }, - jasmine.any(HttpHeaders) + expect.any(HttpHeaders) ); }); - })); + }); - it('calls url service with custom token params', waitForAsync(() => { - const urlServiceSpy = spyOn( + it('calls url service with custom token params', async () => { + const urlServiceSpy = vi.spyOn( urlService, 'createBodyForCodeFlowCodeRequest' ); @@ -197,76 +206,84 @@ describe('CodeFlowCallbackHandlerService', () => { customParamsCodeRequest: { foo: 'bar' }, }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); - const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({})); service .codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config) .subscribe(() => { - expect(urlServiceSpy).toHaveBeenCalledOnceWith('foo', config, { + expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith('foo', config, { foo: 'bar', }); expect(postSpy).toHaveBeenCalledTimes(1); }); - })); + }); - it('calls dataService with correct headers if all params are good', waitForAsync(() => { - const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); + it('calls dataService with correct headers if all params are good', async () => { + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({})); const config = { configId: 'configId1', customParamsCodeRequest: { foo: 'bar' }, }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); service .codeFlowCodeRequest({} as CallbackContext, config) .subscribe(() => { const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; - expect(httpHeaders.has('Content-Type')).toBeTrue(); + expect(httpHeaders.has('Content-Type')).toBeTruthy(); expect(httpHeaders.get('Content-Type')).toBe( 'application/x-www-form-urlencoded' ); }); - })); + }); - it('returns error in case of http error', waitForAsync(() => { - spyOn(dataService, 'post').and.returnValue(throwError(() => HTTP_ERROR)); + it('returns error in case of http error', async () => { + vi.spyOn(dataService, 'post').mockReturnValue( + throwError(() => HTTP_ERROR) + ); const config = { configId: 'configId1', customParamsCodeRequest: { foo: 'bar' }, authority: 'authority', }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ error: (err) => { expect(err).toBeTruthy(); }, }); - })); + }); - it('retries request in case of no connection http error and succeeds', waitForAsync(() => { - const postSpy = spyOn(dataService, 'post').and.returnValue( + it('retries request in case of no connection http error and succeeds', async () => { + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => CONNECTION_ERROR), of({}) @@ -278,14 +295,16 @@ describe('CodeFlowCallbackHandlerService', () => { authority: 'authority', }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ next: (res) => { @@ -297,10 +316,10 @@ describe('CodeFlowCallbackHandlerService', () => { expect(err).toBeFalsy(); }, }); - })); + }); - it('retries request in case of no connection http error and fails because of http error afterwards', waitForAsync(() => { - const postSpy = spyOn(dataService, 'post').and.returnValue( + it('retries request in case of no connection http error and fails because of http error afterwards', async () => { + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => CONNECTION_ERROR), throwError(() => HTTP_ERROR) @@ -312,14 +331,16 @@ describe('CodeFlowCallbackHandlerService', () => { authority: 'authority', }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ next: (res) => { @@ -331,6 +352,6 @@ describe('CodeFlowCallbackHandlerService', () => { expect(postSpy).toHaveBeenCalledTimes(1); }, }); - })); + }); }); }); diff --git a/src/flows/callback-handling/error-helper.spec.ts b/src/flows/callback-handling/error-helper.spec.ts index 7c7f16d..54f2409 100644 --- a/src/flows/callback-handling/error-helper.spec.ts +++ b/src/flows/callback-handling/error-helper.spec.ts @@ -1,4 +1,4 @@ -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpErrorResponse } from '@ngify/http'; import { isNetworkError } from './error-helper'; describe('error helper', () => { @@ -27,31 +27,31 @@ describe('error helper', () => { }); it('returns true on http error with status = 0', () => { - expect(isNetworkError(CONNECTION_ERROR)).toBeTrue(); + expect(isNetworkError(CONNECTION_ERROR)).toBeTruthy(); }); it('returns true on http error with status = 0 and unknown error', () => { - expect(isNetworkError(UNKNOWN_CONNECTION_ERROR)).toBeTrue(); + expect(isNetworkError(UNKNOWN_CONNECTION_ERROR)).toBeTruthy(); }); it('returns true on http error with status <> 0 and error ProgressEvent', () => { - expect(isNetworkError(PARTIAL_CONNECTION_ERROR)).toBeTrue(); + expect(isNetworkError(PARTIAL_CONNECTION_ERROR)).toBeTruthy(); }); it('returns false on non http error', () => { - expect(isNetworkError(new Error('not a HttpErrorResponse'))).toBeFalse(); + expect(isNetworkError(new Error('not a HttpErrorResponse'))).toBeFalsy(); }); it('returns false on string error', () => { - expect(isNetworkError('not a HttpErrorResponse')).toBeFalse(); + expect(isNetworkError('not a HttpErrorResponse')).toBeFalsy(); }); it('returns false on undefined', () => { - expect(isNetworkError(undefined)).toBeFalse(); + expect(isNetworkError(undefined)).toBeFalsy(); }); it('returns false on empty http error', () => { - expect(isNetworkError(HTTP_ERROR)).toBeFalse(); + expect(isNetworkError(HTTP_ERROR)).toBeFalsy(); }); }); }); diff --git a/src/flows/callback-handling/history-jwt-keys-callback-handler.service.spec.ts b/src/flows/callback-handling/history-jwt-keys-callback-handler.service.spec.ts index 754420c..dd65080 100644 --- a/src/flows/callback-handling/history-jwt-keys-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/history-jwt-keys-callback-handler.service.spec.ts @@ -1,12 +1,13 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; +import { vi } from 'vitest'; import { AuthStateService } from '../../auth-state/auth-state.service'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; -import { JwtKey, JwtKeys } from '../../validation/jwtkeys'; +import { mockProvider } from '../../testing/mock'; +import type { JwtKey, JwtKeys } from '../../validation/jwtkeys'; import { ValidationResult } from '../../validation/validation-result'; -import { AuthResult, CallbackContext } from '../callback-context'; +import type { AuthResult, CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service'; import { SigninKeyDataService } from '../signin-key-data.service'; @@ -46,9 +47,6 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { mockProvider(ResetAuthDataService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(HistoryJwtKeysCallbackHandlerService); storagePersistenceService = TestBed.inject(StoragePersistenceService); resetAuthDataService = TestBed.inject(ResetAuthDataService); @@ -62,8 +60,8 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { }); describe('callbackHistoryAndResetJwtKeys', () => { - it('writes authResult into the storage', waitForAsync(() => { - const storagePersistenceServiceSpy = spyOn( + it('writes authResult into the storage', async () => { + const storagePersistenceServiceSpy = vi.spyOn( storagePersistenceService, 'write' ); @@ -75,86 +73,86 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { const callbackContext = { authResult: DUMMY_AUTH_RESULT, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, }, ]; - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({ keys: [] } as JwtKeys) ); service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe(() => { - expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([ - ['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]], - ['jwtKeys', { keys: [] }, allconfigs[0]], + expect(storagePersistenceServiceSpy).toBeCalledWith([ + ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], + ['jwtKeys', { keys: [] }, allConfigs[0]], ]); // write authnResult & jwtKeys expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); }); - })); + }); - it('writes refresh_token into the storage without reuse (refresh token rotation)', waitForAsync(() => { + it('writes refresh_token into the storage without reuse (refresh token rotation)', async () => { const DUMMY_AUTH_RESULT = { refresh_token: 'dummy_refresh_token', id_token: 'some-id-token', }; - const storagePersistenceServiceSpy = spyOn( + const storagePersistenceServiceSpy = vi.spyOn( storagePersistenceService, 'write' ); const callbackContext = { authResult: DUMMY_AUTH_RESULT, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, }, ]; - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({ keys: [] } as JwtKeys) ); service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe(() => { - expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([ - ['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]], - ['jwtKeys', { keys: [] }, allconfigs[0]], + expect(storagePersistenceServiceSpy).toBeCalledWith([ + ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], + ['jwtKeys', { keys: [] }, allConfigs[0]], ]); // write authnResult & refresh_token & jwtKeys expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); }); - })); + }); - it('writes refresh_token into the storage with reuse (without refresh token rotation)', waitForAsync(() => { + it('writes refresh_token into the storage with reuse (without refresh token rotation)', async () => { const DUMMY_AUTH_RESULT = { refresh_token: 'dummy_refresh_token', id_token: 'some-id-token', }; - const storagePersistenceServiceSpy = spyOn( + const storagePersistenceServiceSpy = vi.spyOn( storagePersistenceService, 'write' ); const callbackContext = { authResult: DUMMY_AUTH_RESULT, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, @@ -162,27 +160,27 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { }, ]; - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({ keys: [] } as JwtKeys) ); service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe(() => { - expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([ - ['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]], - ['reusable_refresh_token', 'dummy_refresh_token', allconfigs[0]], - ['jwtKeys', { keys: [] }, allconfigs[0]], + expect(storagePersistenceServiceSpy).toBeCalledWith([ + ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], + ['reusable_refresh_token', 'dummy_refresh_token', allConfigs[0]], + ['jwtKeys', { keys: [] }, allConfigs[0]], ]); // write authnResult & refresh_token & jwtKeys expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3); }); - })); + }); - it('resetBrowserHistory if historyCleanup is turned on and is not in a renewProcess', waitForAsync(() => { + it('resetBrowserHistory if historyCleanup is turned on and is not in a renewProcess', async () => { const DUMMY_AUTH_RESULT = { id_token: 'some-id-token', }; @@ -190,30 +188,30 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { isRenewProcess: false, authResult: DUMMY_AUTH_RESULT, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: false, }, ]; - const windowSpy = spyOn(window.history, 'replaceState'); + const windowSpy = vi.spyOn(window.history, 'replaceState'); - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({ keys: [] } as JwtKeys) ); service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe(() => { expect(windowSpy).toHaveBeenCalledTimes(1); }); - })); + }); - it('returns callbackContext with jwtkeys filled if everything works fine', waitForAsync(() => { + it('returns callbackContext with jwtkeys filled if everything works fine', async () => { const DUMMY_AUTH_RESULT = { id_token: 'some-id-token', }; @@ -222,21 +220,21 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { isRenewProcess: false, authResult: DUMMY_AUTH_RESULT, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: false, }, ]; - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({ keys: [{ kty: 'henlo' } as JwtKey] } as JwtKeys) ); service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe((result) => { expect(result).toEqual({ @@ -245,9 +243,9 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { jwtKeys: { keys: [{ kty: 'henlo' }] }, } as CallbackContext); }); - })); + }); - it('returns error if no jwtKeys have been in the call --> keys are null', waitForAsync(() => { + it('returns error if no jwtKeys have been in the call --> keys are null', async () => { const DUMMY_AUTH_RESULT = { id_token: 'some-id-token', }; @@ -256,32 +254,32 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { isRenewProcess: false, authResult: DUMMY_AUTH_RESULT, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: false, }, ]; - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({} as JwtKeys) ); service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe({ error: (err) => { expect(err.message).toEqual( - `Failed to retrieve signing key with error: Error: Failed to retrieve signing key` + 'Failed to retrieve signing key with error: Error: Failed to retrieve signing key' ); }, }); - })); + }); - it('returns error if no jwtKeys have been in the call --> keys throw an error', waitForAsync(() => { + it('returns error if no jwtKeys have been in the call --> keys throw an error', async () => { const DUMMY_AUTH_RESULT = { id_token: 'some-id-token', }; @@ -289,36 +287,36 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { isRenewProcess: false, authResult: DUMMY_AUTH_RESULT, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: false, }, ]; - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( throwError(() => new Error('error')) ); service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe({ error: (err) => { expect(err.message).toEqual( - `Failed to retrieve signing key with error: Error: Error: error` + 'Failed to retrieve signing key with error: Error: Error: error' ); }, }); - })); + }); - it('returns error if callbackContext.authresult has an error property filled', waitForAsync(() => { + it('returns error if callbackContext.authresult has an error property filled', async () => { const callbackContext = { authResult: { error: 'someError' }, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, @@ -328,36 +326,36 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe({ error: (err) => { expect(err.message).toEqual( - `AuthCallback AuthResult came with error: someError` + 'AuthCallback AuthResult came with error: someError' ); }, }); - })); + }); - it('calls resetAuthorizationData, resets nonce and authStateService in case of an error', waitForAsync(() => { + it('calls resetAuthorizationData, resets nonce and authStateService in case of an error', async () => { const callbackContext = { authResult: { error: 'someError' }, isRenewProcess: false, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, }, ]; - const resetAuthorizationDataSpy = spyOn( + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); - const setNonceSpy = spyOn(flowsDataService, 'setNonce'); - const updateAndPublishAuthStateSpy = spyOn( + const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce'); + const updateAndPublishAuthStateSpy = vi.spyOn( authStateService, 'updateAndPublishAuthState' ); @@ -365,40 +363,42 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe({ error: () => { expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(setNonceSpy).toHaveBeenCalledTimes(1); - expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ + expect( + updateAndPublishAuthStateSpy + ).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, validationResult: ValidationResult.SecureTokenServerError, isRenewProcess: false, }); }, }); - })); + }); - it('calls authStateService.updateAndPublishAuthState with login required if the error is `login_required`', waitForAsync(() => { + it('calls authStateService.updateAndPublishAuthState with login required if the error is `login_required`', async () => { const callbackContext = { authResult: { error: 'login_required' }, isRenewProcess: false, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, }, ]; - const resetAuthorizationDataSpy = spyOn( + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); - const setNonceSpy = spyOn(flowsDataService, 'setNonce'); - const updateAndPublishAuthStateSpy = spyOn( + const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce'); + const updateAndPublishAuthStateSpy = vi.spyOn( authStateService, 'updateAndPublishAuthState' ); @@ -406,23 +406,25 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { service .callbackHistoryAndResetJwtKeys( callbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe({ error: () => { expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(setNonceSpy).toHaveBeenCalledTimes(1); - expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ + expect( + updateAndPublishAuthStateSpy + ).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, validationResult: ValidationResult.LoginRequired, isRenewProcess: false, }); }, }); - })); + }); - it('should store jwtKeys', waitForAsync(() => { + it('should store jwtKeys', async () => { const DUMMY_AUTH_RESULT = { id_token: 'some-id-token', }; @@ -430,33 +432,33 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { const initialCallbackContext = { authResult: DUMMY_AUTH_RESULT, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, }, ]; - const storagePersistenceServiceSpy = spyOn( + const storagePersistenceServiceSpy = vi.spyOn( storagePersistenceService, 'write' ); - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of(DUMMY_JWT_KEYS) ); service .callbackHistoryAndResetJwtKeys( initialCallbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe({ next: (callbackContext: CallbackContext) => { expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); - expect(storagePersistenceServiceSpy.calls.allArgs()).toEqual([ - ['authnResult', DUMMY_AUTH_RESULT, allconfigs[0]], - ['jwtKeys', DUMMY_JWT_KEYS, allconfigs[0]], + expect(storagePersistenceServiceSpy).toBeCalledWith([ + ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], + ['jwtKeys', DUMMY_JWT_KEYS, allConfigs[0]], ]); expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); @@ -465,9 +467,9 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { expect(err).toBeFalsy(); }, }); - })); + }); - it('should not store jwtKeys on error', waitForAsync(() => { + it('should not store jwtKeys on error', async () => { const authResult = { id_token: 'some-id-token', access_token: 'some-access-token', @@ -476,26 +478,26 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { authResult, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, }, ]; - const storagePersistenceServiceSpy = spyOn( + const storagePersistenceServiceSpy = vi.spyOn( storagePersistenceService, 'write' ); - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( throwError(() => new Error('Error')) ); service .callbackHistoryAndResetJwtKeys( initialCallbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe({ next: (callbackContext: CallbackContext) => { @@ -505,16 +507,18 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { expect(err).toBeTruthy(); // storagePersistenceService.write() should not have been called with jwtKeys - expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith( + expect( + storagePersistenceServiceSpy + ).toHaveBeenCalledExactlyOnceWith( 'authnResult', authResult, - allconfigs[0] + allConfigs[0] ); }, }); - })); + }); - it('should fallback to stored jwtKeys on error', waitForAsync(() => { + it('should fallback to stored jwtKeys on error', async () => { const authResult = { id_token: 'some-id-token', access_token: 'some-access-token', @@ -523,66 +527,65 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { authResult, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, }, ]; - const storagePersistenceServiceSpy = spyOn( + const storagePersistenceServiceSpy = vi.spyOn( storagePersistenceService, 'read' ); - storagePersistenceServiceSpy.and.returnValue(DUMMY_JWT_KEYS); - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + storagePersistenceServiceSpy.mockReturnValue(DUMMY_JWT_KEYS); + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( throwError(() => new Error('Error')) ); service .callbackHistoryAndResetJwtKeys( initialCallbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe({ next: (callbackContext: CallbackContext) => { - expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith( - 'jwtKeys', - allconfigs[0] - ); + expect( + storagePersistenceServiceSpy + ).toHaveBeenCalledExactlyOnceWith('jwtKeys', allConfigs[0]); expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); }, error: (err) => { expect(err).toBeFalsy(); }, }); - })); + }); - it('should throw error if no jwtKeys are stored', waitForAsync(() => { + it('should throw error if no jwtKeys are stored', async () => { const authResult = { id_token: 'some-id-token', access_token: 'some-access-token', } as AuthResult; const initialCallbackContext = { authResult } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', historyCleanupOff: true, }, ]; - spyOn(storagePersistenceService, 'read').and.returnValue(null); - spyOn(signInKeyDataService, 'getSigningKeys').and.returnValue( + vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null); + vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( throwError(() => new Error('Error')) ); service .callbackHistoryAndResetJwtKeys( initialCallbackContext, - allconfigs[0], - allconfigs + allConfigs[0]!, + allConfigs ) .subscribe({ next: (callbackContext: CallbackContext) => { @@ -592,7 +595,7 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { expect(err).toBeTruthy(); }, }); - })); + }); }); describe('historyCleanUpTurnedOn ', () => { diff --git a/src/flows/callback-handling/history-jwt-keys-callback-handler.service.ts b/src/flows/callback-handling/history-jwt-keys-callback-handler.service.ts index 5dba20f..3e826df 100644 --- a/src/flows/callback-handling/history-jwt-keys-callback-handler.service.ts +++ b/src/flows/callback-handling/history-jwt-keys-callback-handler.service.ts @@ -1,14 +1,14 @@ -import { DOCUMENT } from '../../dom'; -import { inject, Injectable } from 'injection-js'; -import { Observable, of, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, of, throwError } from 'rxjs'; import { catchError, switchMap, tap } from 'rxjs/operators'; import { AuthStateService } from '../../auth-state/auth-state.service'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; +import { DOCUMENT } from '../../dom'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; -import { JwtKeys } from '../../validation/jwtkeys'; +import type { JwtKeys } from '../../validation/jwtkeys'; import { ValidationResult } from '../../validation/validation-result'; -import { CallbackContext } from '../callback-context'; +import type { CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service'; import { SigninKeyDataService } from '../signin-key-data.service'; diff --git a/src/flows/callback-handling/implicit-flow-callback-handler.service.spec.ts b/src/flows/callback-handling/implicit-flow-callback-handler.service.spec.ts index 1feec31..8120d0f 100644 --- a/src/flows/callback-handling/implicit-flow-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/implicit-flow-callback-handler.service.spec.ts @@ -1,8 +1,9 @@ +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { DOCUMENT } from '../../dom'; -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { mockProvider } from '../../../test/auto-mock'; import { LoggerService } from '../../logging/logger.service'; -import { CallbackContext } from '../callback-context'; +import { mockProvider } from '../../testing/mock'; +import type { CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service'; import { ImplicitFlowCallbackHandlerService } from './implicit-flow-callback-handler.service'; @@ -34,9 +35,6 @@ describe('ImplicitFlowCallbackHandlerService', () => { }, ], }); - }); - - beforeEach(() => { service = TestBed.inject(ImplicitFlowCallbackHandlerService); flowsDataService = TestBed.inject(FlowsDataService); resetAuthDataService = TestBed.inject(ResetAuthDataService); @@ -47,46 +45,46 @@ describe('ImplicitFlowCallbackHandlerService', () => { }); describe('implicitFlowCallback', () => { - it('calls "resetAuthorizationData" if silent renew is not running', waitForAsync(() => { - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); - const resetAuthorizationDataSpy = spyOn( + it('calls "resetAuthorizationData" if silent renew is not running', async () => { + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', }, ]; service - .implicitFlowCallback(allconfigs[0], allconfigs, 'any-hash') + .implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash') .subscribe(() => { expect(resetAuthorizationDataSpy).toHaveBeenCalled(); }); - })); + }); - it('does NOT calls "resetAuthorizationData" if silent renew is running', waitForAsync(() => { - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); - const resetAuthorizationDataSpy = spyOn( + it('does NOT calls "resetAuthorizationData" if silent renew is running', async () => { + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true); + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', }, ]; service - .implicitFlowCallback(allconfigs[0], allconfigs, 'any-hash') + .implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash') .subscribe(() => { expect(resetAuthorizationDataSpy).not.toHaveBeenCalled(); }); - })); + }); - it('returns callbackContext if all params are good', waitForAsync(() => { - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); + it('returns callbackContext if all params are good', async () => { + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true); const expectedCallbackContext = { code: '', refreshToken: '', @@ -99,21 +97,21 @@ describe('ImplicitFlowCallbackHandlerService', () => { existingIdToken: null, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', }, ]; service - .implicitFlowCallback(allconfigs[0], allconfigs, 'anyHash') + .implicitFlowCallback(allConfigs[0]!, allConfigs, 'anyHash') .subscribe((callbackContext) => { expect(callbackContext).toEqual(expectedCallbackContext); }); - })); + }); - it('uses window location hash if no hash is passed', waitForAsync(() => { - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); + it('uses window location hash if no hash is passed', async () => { + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true); const expectedCallbackContext = { code: '', refreshToken: '', @@ -126,17 +124,17 @@ describe('ImplicitFlowCallbackHandlerService', () => { existingIdToken: null, } as CallbackContext; - const allconfigs = [ + const allConfigs = [ { configId: 'configId1', }, ]; service - .implicitFlowCallback(allconfigs[0], allconfigs) + .implicitFlowCallback(allConfigs[0]!, allConfigs) .subscribe((callbackContext) => { expect(callbackContext).toEqual(expectedCallbackContext); }); - })); + }); }); }); diff --git a/src/flows/callback-handling/implicit-flow-callback-handler.service.ts b/src/flows/callback-handling/implicit-flow-callback-handler.service.ts index a676f0d..16097d1 100644 --- a/src/flows/callback-handling/implicit-flow-callback-handler.service.ts +++ b/src/flows/callback-handling/implicit-flow-callback-handler.service.ts @@ -1,9 +1,9 @@ +import { Injectable, inject } from 'injection-js'; +import { type Observable, of } from 'rxjs'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { DOCUMENT } from '../../dom'; -import { inject, Injectable } from 'injection-js'; -import { Observable, of } from 'rxjs'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; -import { AuthResult, CallbackContext } from '../callback-context'; +import type { AuthResult, CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service'; diff --git a/src/flows/callback-handling/refresh-session-callback-handler.service.spec.ts b/src/flows/callback-handling/refresh-session-callback-handler.service.spec.ts index 9973dc3..3c7553e 100644 --- a/src/flows/callback-handling/refresh-session-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/refresh-session-callback-handler.service.spec.ts @@ -1,8 +1,9 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { mockProvider } from '../../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { AuthStateService } from '../../auth-state/auth-state.service'; import { LoggerService } from '../../logging/logger.service'; -import { CallbackContext } from '../callback-context'; +import { mockProvider } from '../../testing/mock'; +import type { CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; import { RefreshSessionCallbackHandlerService } from './refresh-session-callback-handler.service'; @@ -33,15 +34,15 @@ describe('RefreshSessionCallbackHandlerService', () => { }); describe('refreshSessionWithRefreshTokens', () => { - it('returns callbackContext if all params are good', waitForAsync(() => { - spyOn( + it('returns callbackContext if all params are good', async () => { + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue('state-data'); - spyOn(authStateService, 'getRefreshToken').and.returnValue( + ).mockReturnValue('state-data'); + vi.spyOn(authStateService, 'getRefreshToken').mockReturnValue( 'henlo-furiend' ); - spyOn(authStateService, 'getIdToken').and.returnValue('henlo-legger'); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('henlo-legger'); const expectedCallbackContext = { code: '', @@ -60,15 +61,15 @@ describe('RefreshSessionCallbackHandlerService', () => { .subscribe((callbackContext) => { expect(callbackContext).toEqual(expectedCallbackContext); }); - })); + }); - it('throws error if no refresh token is given', waitForAsync(() => { - spyOn( + it('throws error if no refresh token is given', async () => { + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue('state-data'); - spyOn(authStateService, 'getRefreshToken').and.returnValue(''); - spyOn(authStateService, 'getIdToken').and.returnValue('henlo-legger'); + ).mockReturnValue('state-data'); + vi.spyOn(authStateService, 'getRefreshToken').mockReturnValue(''); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('henlo-legger'); service .refreshSessionWithRefreshTokens({ configId: 'configId1' }) @@ -77,6 +78,6 @@ describe('RefreshSessionCallbackHandlerService', () => { expect(err).toBeTruthy(); }, }); - })); + }); }); }); diff --git a/src/flows/callback-handling/refresh-token-callback-handler.service.spec.ts b/src/flows/callback-handling/refresh-token-callback-handler.service.spec.ts index dff9ccf..dda12bd 100644 --- a/src/flows/callback-handling/refresh-token-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/refresh-token-callback-handler.service.spec.ts @@ -1,13 +1,14 @@ -import { HttpErrorResponse, HttpHeaders } from '@angular/common/http'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; +import { HttpErrorResponse, HttpHeaders } from '@ngify/http'; import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; -import { createRetriableStream } from '../../../test/create-retriable-stream.helper'; +import { vi } from 'vitest'; import { DataService } from '../../api/data.service'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; +import { createRetriableStream } from '../../testing/create-retriable-stream.helper'; +import { mockProvider } from '../../testing/mock'; import { UrlService } from '../../utils/url/url.service'; -import { CallbackContext } from '../callback-context'; +import type { CallbackContext } from '../callback-context'; import { RefreshTokenCallbackHandlerService } from './refresh-token-callback-handler.service'; describe('RefreshTokenCallbackHandlerService', () => { @@ -46,7 +47,7 @@ describe('RefreshTokenCallbackHandlerService', () => { url: 'https://identity-server.test/openid-connect/token', }); - it('throws error if no tokenEndpoint is given', waitForAsync(() => { + it('throws error if no tokenEndpoint is given', async () => { (service as any) .refreshTokensRequestTokens({} as CallbackContext) .subscribe({ @@ -54,41 +55,45 @@ describe('RefreshTokenCallbackHandlerService', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('calls data service if all params are good', waitForAsync(() => { - const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); + it('calls data service if all params are good', async () => { + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({})); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); service .refreshTokensRequestTokens({} as CallbackContext, { configId: 'configId1', }) .subscribe(() => { - expect(postSpy).toHaveBeenCalledOnceWith( + expect(postSpy).toHaveBeenCalledExactlyOnceWith( 'tokenEndpoint', undefined, { configId: 'configId1' }, - jasmine.any(HttpHeaders) + expect.any(HttpHeaders) ); const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; - expect(httpHeaders.has('Content-Type')).toBeTrue(); + expect(httpHeaders.has('Content-Type')).toBeTruthy(); expect(httpHeaders.get('Content-Type')).toBe( 'application/x-www-form-urlencoded' ); }); - })); + }); - it('calls data service with correct headers if all params are good', waitForAsync(() => { - const postSpy = spyOn(dataService, 'post').and.returnValue(of({})); + it('calls data service with correct headers if all params are good', async () => { + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({})); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); service .refreshTokensRequestTokens({} as CallbackContext, { @@ -97,20 +102,24 @@ describe('RefreshTokenCallbackHandlerService', () => { .subscribe(() => { const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; - expect(httpHeaders.has('Content-Type')).toBeTrue(); + expect(httpHeaders.has('Content-Type')).toBeTruthy(); expect(httpHeaders.get('Content-Type')).toBe( 'application/x-www-form-urlencoded' ); }); - })); + }); - it('returns error in case of http error', waitForAsync(() => { - spyOn(dataService, 'post').and.returnValue(throwError(() => HTTP_ERROR)); + it('returns error in case of http error', async () => { + vi.spyOn(dataService, 'post').mockReturnValue( + throwError(() => HTTP_ERROR) + ); const config = { configId: 'configId1', authority: 'authority' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); service .refreshTokensRequestTokens({} as CallbackContext, config) @@ -119,10 +128,10 @@ describe('RefreshTokenCallbackHandlerService', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('retries request in case of no connection http error and succeeds', waitForAsync(() => { - const postSpy = spyOn(dataService, 'post').and.returnValue( + it('retries request in case of no connection http error and succeeds', async () => { + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => CONNECTION_ERROR), of({}) @@ -130,9 +139,11 @@ describe('RefreshTokenCallbackHandlerService', () => { ); const config = { configId: 'configId1', authority: 'authority' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); service .refreshTokensRequestTokens({} as CallbackContext, config) @@ -146,10 +157,10 @@ describe('RefreshTokenCallbackHandlerService', () => { expect(err).toBeFalsy(); }, }); - })); + }); - it('retries request in case of no connection http error and fails because of http error afterwards', waitForAsync(() => { - const postSpy = spyOn(dataService, 'post').and.returnValue( + it('retries request in case of no connection http error and fails because of http error afterwards', async () => { + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => CONNECTION_ERROR), throwError(() => HTTP_ERROR) @@ -157,9 +168,11 @@ describe('RefreshTokenCallbackHandlerService', () => { ); const config = { configId: 'configId1', authority: 'authority' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ tokenEndpoint: 'tokenEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ tokenEndpoint: 'tokenEndpoint' }) + ); service .refreshTokensRequestTokens({} as CallbackContext, config) @@ -173,6 +186,6 @@ describe('RefreshTokenCallbackHandlerService', () => { expect(postSpy).toHaveBeenCalledTimes(1); }, }); - })); + }); }); }); diff --git a/src/flows/callback-handling/state-validation-callback-handler.service.spec.ts b/src/flows/callback-handling/state-validation-callback-handler.service.spec.ts index 2bd4077..2be057d 100644 --- a/src/flows/callback-handling/state-validation-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/state-validation-callback-handler.service.spec.ts @@ -1,13 +1,14 @@ -import { DOCUMENT } from '../../dom'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; +import { vi } from 'vitest'; import { AuthStateService } from '../../auth-state/auth-state.service'; +import { DOCUMENT } from '../../dom'; import { LoggerService } from '../../logging/logger.service'; -import { StateValidationResult } from '../../validation/state-validation-result'; +import { mockProvider } from '../../testing/mock'; +import type { StateValidationResult } from '../../validation/state-validation-result'; import { StateValidationService } from '../../validation/state-validation.service'; import { ValidationResult } from '../../validation/validation-result'; -import { CallbackContext } from '../callback-context'; +import type { CallbackContext } from '../callback-context'; import { ResetAuthDataService } from '../reset-auth-data.service'; import { StateValidationCallbackHandlerService } from './state-validation-callback-handler.service'; @@ -56,8 +57,11 @@ describe('StateValidationCallbackHandlerService', () => { }); describe('callbackStateValidation', () => { - it('returns callbackContext with validationResult if validationResult is valid', waitForAsync(() => { - spyOn(stateValidationService, 'getValidatedStateResult').and.returnValue( + it('returns callbackContext with validationResult if validationResult is valid', async () => { + vi.spyOn( + stateValidationService, + 'getValidatedStateResult' + ).mockReturnValue( of({ idToken: 'idTokenJustForTesting', authResponseIsValid: true, @@ -68,7 +72,7 @@ describe('StateValidationCallbackHandlerService', () => { service .callbackStateValidation( {} as CallbackContext, - allConfigs[0], + allConfigs[0]!, allConfigs ) .subscribe((newCallbackContext) => { @@ -79,47 +83,53 @@ describe('StateValidationCallbackHandlerService', () => { }, } as CallbackContext); }); - })); + }); - it('logs error in case of an error', waitForAsync(() => { - spyOn(stateValidationService, 'getValidatedStateResult').and.returnValue( + it('logs error in case of an error', async () => { + vi.spyOn( + stateValidationService, + 'getValidatedStateResult' + ).mockReturnValue( of({ authResponseIsValid: false, } as StateValidationResult) ); - const loggerSpy = spyOn(loggerService, 'logWarning'); + const loggerSpy = vi.spyOn(loggerService, 'logWarning'); const allConfigs = [{ configId: 'configId1' }]; service .callbackStateValidation( {} as CallbackContext, - allConfigs[0], + allConfigs[0]!, allConfigs ) .subscribe({ error: () => { - expect(loggerSpy).toHaveBeenCalledOnceWith( - allConfigs[0], + expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( + allConfigs[0]!, 'authorizedCallback, token(s) validation failed, resetting. Hash: &anyFakeHash' ); }, }); - })); + }); - it('calls resetAuthDataService.resetAuthorizationData and authStateService.updateAndPublishAuthState in case of an error', waitForAsync(() => { - spyOn(stateValidationService, 'getValidatedStateResult').and.returnValue( + it('calls resetAuthDataService.resetAuthorizationData and authStateService.updateAndPublishAuthState in case of an error', async () => { + vi.spyOn( + stateValidationService, + 'getValidatedStateResult' + ).mockReturnValue( of({ authResponseIsValid: false, state: ValidationResult.LoginRequired, } as StateValidationResult) ); - const resetAuthorizationDataSpy = spyOn( + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); - const updateAndPublishAuthStateSpy = spyOn( + const updateAndPublishAuthStateSpy = vi.spyOn( authStateService, 'updateAndPublishAuthState' ); @@ -128,19 +138,21 @@ describe('StateValidationCallbackHandlerService', () => { service .callbackStateValidation( { isRenewProcess: true } as CallbackContext, - allConfigs[0], + allConfigs[0]!, allConfigs ) .subscribe({ error: () => { expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); - expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ + expect( + updateAndPublishAuthStateSpy + ).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, validationResult: ValidationResult.LoginRequired, isRenewProcess: true, }); }, }); - })); + }); }); }); diff --git a/src/flows/callback-handling/state-validation-callback-handler.service.ts b/src/flows/callback-handling/state-validation-callback-handler.service.ts index bc4c828..8498455 100644 --- a/src/flows/callback-handling/state-validation-callback-handler.service.ts +++ b/src/flows/callback-handling/state-validation-callback-handler.service.ts @@ -1,13 +1,13 @@ -import { DOCUMENT } from '../../dom'; -import { inject, Injectable } from 'injection-js'; -import { Observable } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import type { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { AuthStateService } from '../../auth-state/auth-state.service'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; +import { DOCUMENT } from '../../dom'; import { LoggerService } from '../../logging/logger.service'; -import { StateValidationResult } from '../../validation/state-validation-result'; +import type { StateValidationResult } from '../../validation/state-validation-result'; import { StateValidationService } from '../../validation/state-validation.service'; -import { CallbackContext } from '../callback-context'; +import type { CallbackContext } from '../callback-context'; import { ResetAuthDataService } from '../reset-auth-data.service'; @Injectable() diff --git a/src/flows/callback-handling/user-callback-handler.service.spec.ts b/src/flows/callback-handling/user-callback-handler.service.spec.ts index 5696a6c..b8b464d 100644 --- a/src/flows/callback-handling/user-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/user-callback-handler.service.spec.ts @@ -1,12 +1,13 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; +import { vi } from 'vitest'; import { AuthStateService } from '../../auth-state/auth-state.service'; import { LoggerService } from '../../logging/logger.service'; +import { mockProvider } from '../../testing/mock'; import { UserService } from '../../user-data/user.service'; import { StateValidationResult } from '../../validation/state-validation-result'; import { ValidationResult } from '../../validation/validation-result'; -import { CallbackContext } from '../callback-context'; +import type { CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service'; import { UserCallbackHandlerService } from './user-callback-handler.service'; @@ -44,7 +45,7 @@ describe('UserCallbackHandlerService', () => { }); describe('callbackUser', () => { - it('calls flowsDataService.setSessionState with correct params if autoUserInfo is false, isRenewProcess is false and refreshToken is null', waitForAsync(() => { + it('calls flowsDataService.setSessionState with correct params if autoUserInfo is false, isRenewProcess is false and refreshToken is null', async () => { const svr = new StateValidationResult( 'accesstoken', 'idtoken', @@ -70,17 +71,17 @@ describe('UserCallbackHandlerService', () => { }, ]; - const spy = spyOn(flowsDataService, 'setSessionState'); + const spy = vi.spyOn(flowsDataService, 'setSessionState'); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe((resultCallbackContext) => { - expect(spy).toHaveBeenCalledOnceWith('mystate', allConfigs[0]); + expect(spy).toHaveBeenCalledExactlyOnceWith('mystate', allConfigs[0]); expect(resultCallbackContext).toEqual(callbackContext); }); - })); + }); - it('does NOT call flowsDataService.setSessionState if autoUserInfo is false, isRenewProcess is true and refreshToken is null', waitForAsync(() => { + it('does NOT call flowsDataService.setSessionState if autoUserInfo is false, isRenewProcess is true and refreshToken is null', async () => { const svr = new StateValidationResult( 'accesstoken', 'idtoken', @@ -104,17 +105,17 @@ describe('UserCallbackHandlerService', () => { autoUserInfo: false, }, ]; - const spy = spyOn(flowsDataService, 'setSessionState'); + const spy = vi.spyOn(flowsDataService, 'setSessionState'); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe((resultCallbackContext) => { expect(spy).not.toHaveBeenCalled(); expect(resultCallbackContext).toEqual(callbackContext); }); - })); + }); - it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value', waitForAsync(() => { + it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value', async () => { const svr = new StateValidationResult( 'accesstoken', 'idtoken', @@ -138,17 +139,17 @@ describe('UserCallbackHandlerService', () => { autoUserInfo: false, }, ]; - const spy = spyOn(flowsDataService, 'setSessionState'); + const spy = vi.spyOn(flowsDataService, 'setSessionState'); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe((resultCallbackContext) => { expect(spy).not.toHaveBeenCalled(); expect(resultCallbackContext).toEqual(callbackContext); }); - })); + }); - it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value, id_token is false', waitForAsync(() => { + it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value, id_token is false', async () => { const svr = new StateValidationResult('accesstoken', '', true, ''); const callbackContext = { code: '', @@ -168,17 +169,17 @@ describe('UserCallbackHandlerService', () => { }, ]; - const spy = spyOn(flowsDataService, 'setSessionState'); + const spy = vi.spyOn(flowsDataService, 'setSessionState'); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe((resultCallbackContext) => { expect(spy).not.toHaveBeenCalled(); expect(resultCallbackContext).toEqual(callbackContext); }); - })); + }); - it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is false', waitForAsync(() => { + it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is false', async () => { const svr = new StateValidationResult( 'accesstoken', 'idtoken', @@ -204,24 +205,24 @@ describe('UserCallbackHandlerService', () => { }, ]; - const updateAndPublishAuthStateSpy = spyOn( + const updateAndPublishAuthStateSpy = vi.spyOn( authStateService, 'updateAndPublishAuthState' ); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe((resultCallbackContext) => { - expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ + expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: true, validationResult: ValidationResult.NotSet, isRenewProcess: false, }); expect(resultCallbackContext).toEqual(callbackContext); }); - })); + }); - it('calls userService.getAndPersistUserDataInStore with correct params if autoUserInfo is true', waitForAsync(() => { + it('calls userService.getAndPersistUserDataInStore with correct params if autoUserInfo is true', async () => { const svr = new StateValidationResult( 'accesstoken', 'idtoken', @@ -247,16 +248,17 @@ describe('UserCallbackHandlerService', () => { }, ]; - const getAndPersistUserDataInStoreSpy = spyOn( - userService, - 'getAndPersistUserDataInStore' - ).and.returnValue(of({ user: 'some_data' })); + const getAndPersistUserDataInStoreSpy = vi + .spyOn(userService, 'getAndPersistUserDataInStore') + .mockReturnValue(of({ user: 'some_data' })); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe((resultCallbackContext) => { - expect(getAndPersistUserDataInStoreSpy).toHaveBeenCalledOnceWith( - allConfigs[0], + expect( + getAndPersistUserDataInStoreSpy + ).toHaveBeenCalledExactlyOnceWith( + allConfigs[0]!, allConfigs, false, 'idtoken', @@ -264,9 +266,9 @@ describe('UserCallbackHandlerService', () => { ); expect(resultCallbackContext).toEqual(callbackContext); }); - })); + }); - it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is true', waitForAsync(() => { + it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is true', async () => { const svr = new StateValidationResult( 'accesstoken', 'idtoken', @@ -293,27 +295,27 @@ describe('UserCallbackHandlerService', () => { }, ]; - spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue( + vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue( of({ user: 'some_data' }) ); - const updateAndPublishAuthStateSpy = spyOn( + const updateAndPublishAuthStateSpy = vi.spyOn( authStateService, 'updateAndPublishAuthState' ); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe((resultCallbackContext) => { - expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ + expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: true, validationResult: ValidationResult.MaxOffsetExpired, isRenewProcess: false, }); expect(resultCallbackContext).toEqual(callbackContext); }); - })); + }); - it('calls flowsDataService.setSessionState with correct params if user data is present and NOT refresh token', waitForAsync(() => { + it('calls flowsDataService.setSessionState with correct params if user data is present and NOT refresh token', async () => { const svr = new StateValidationResult( 'accesstoken', 'idtoken', @@ -340,23 +342,23 @@ describe('UserCallbackHandlerService', () => { }, ]; - spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue( + vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue( of({ user: 'some_data' }) ); - const setSessionStateSpy = spyOn(flowsDataService, 'setSessionState'); + const setSessionStateSpy = vi.spyOn(flowsDataService, 'setSessionState'); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe((resultCallbackContext) => { - expect(setSessionStateSpy).toHaveBeenCalledOnceWith( + expect(setSessionStateSpy).toHaveBeenCalledExactlyOnceWith( 'mystate', allConfigs[0] ); expect(resultCallbackContext).toEqual(callbackContext); }); - })); + }); - it('calls authStateService.publishUnauthorizedState with correct params if user info which are coming back are null', waitForAsync(() => { + it('calls authStateService.publishUnauthorizedState with correct params if user info which are coming back are null', async () => { const svr = new StateValidationResult( 'accesstoken', 'idtoken', @@ -383,19 +385,21 @@ describe('UserCallbackHandlerService', () => { }, ]; - spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue( + vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue( of(null) ); - const updateAndPublishAuthStateSpy = spyOn( + const updateAndPublishAuthStateSpy = vi.spyOn( authStateService, 'updateAndPublishAuthState' ); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe({ error: (err) => { - expect(updateAndPublishAuthStateSpy).toHaveBeenCalledOnceWith({ + expect( + updateAndPublishAuthStateSpy + ).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, validationResult: ValidationResult.MaxOffsetExpired, isRenewProcess: false, @@ -405,9 +409,9 @@ describe('UserCallbackHandlerService', () => { ); }, }); - })); + }); - it('calls resetAuthDataService.resetAuthorizationData if user info which are coming back are null', waitForAsync(() => { + it('calls resetAuthDataService.resetAuthorizationData if user info which are coming back are null', async () => { const svr = new StateValidationResult( 'accesstoken', 'idtoken', @@ -434,16 +438,16 @@ describe('UserCallbackHandlerService', () => { }, ]; - spyOn(userService, 'getAndPersistUserDataInStore').and.returnValue( + vi.spyOn(userService, 'getAndPersistUserDataInStore').mockReturnValue( of(null) ); - const resetAuthorizationDataSpy = spyOn( + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); service - .callbackUser(callbackContext, allConfigs[0], allConfigs) + .callbackUser(callbackContext, allConfigs[0]!, allConfigs) .subscribe({ error: (err) => { expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); @@ -452,6 +456,6 @@ describe('UserCallbackHandlerService', () => { ); }, }); - })); + }); }); }); diff --git a/src/flows/callback-handling/user-callback-handler.service.ts b/src/flows/callback-handling/user-callback-handler.service.ts index ea0fa10..09730d1 100644 --- a/src/flows/callback-handling/user-callback-handler.service.ts +++ b/src/flows/callback-handling/user-callback-handler.service.ts @@ -1,12 +1,12 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable, of, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, of, throwError } from 'rxjs'; import { catchError, switchMap } from 'rxjs/operators'; import { AuthStateService } from '../../auth-state/auth-state.service'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { UserService } from '../../user-data/user.service'; -import { StateValidationResult } from '../../validation/state-validation-result'; -import { CallbackContext } from '../callback-context'; +import type { StateValidationResult } from '../../validation/state-validation-result'; +import type { CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; import { ResetAuthDataService } from '../reset-auth-data.service'; diff --git a/src/flows/flows-data.service.spec.ts b/src/flows/flows-data.service.spec.ts index dc8e258..e5aa45f 100644 --- a/src/flows/flows-data.service.spec.ts +++ b/src/flows/flows-data.service.spec.ts @@ -1,7 +1,8 @@ -import { TestBed } from '@angular/core/testing'; -import { mockProvider } from '../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockProvider } from '../testing/mock'; import { CryptoService } from '../utils/crypto/crypto.service'; import { FlowsDataService } from './flows-data.service'; import { RandomService } from './random/random.service'; @@ -37,12 +38,12 @@ describe('Flows Data Service', () => { describe('createNonce', () => { it('createNonce returns nonce and stores it', () => { - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); const result = service.createNonce({ configId: 'configId1' }); expect(result).toBeTruthy(); - expect(spy).toHaveBeenCalledOnceWith('authNonce', result, { + expect(spy).toHaveBeenCalledExactlyOnceWith('authNonce', result, { configId: 'configId1', }); }); @@ -50,32 +51,38 @@ describe('Flows Data Service', () => { describe('AuthStateControl', () => { it('getAuthStateControl returns property from store', () => { - const spy = spyOn(storagePersistenceService, 'read'); + const spy = vi.spyOn(storagePersistenceService, 'read'); service.getAuthStateControl({ configId: 'configId1' }); - expect(spy).toHaveBeenCalledOnceWith('authStateControl', { + expect(spy).toHaveBeenCalledExactlyOnceWith('authStateControl', { configId: 'configId1', }); }); it('setAuthStateControl saves property in store', () => { - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); service.setAuthStateControl('ToSave', { configId: 'configId1' }); - expect(spy).toHaveBeenCalledOnceWith('authStateControl', 'ToSave', { - configId: 'configId1', - }); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'authStateControl', + 'ToSave', + { + configId: 'configId1', + } + ); }); }); describe('getExistingOrCreateAuthStateControl', () => { it('if nothing stored it creates a 40 char one and saves the authStateControl', () => { - spyOn(storagePersistenceService, 'read') - .withArgs('authStateControl', { configId: 'configId1' }) - .and.returnValue(null); - const setSpy = spyOn(storagePersistenceService, 'write'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authStateControl', { configId: 'configId1' }], + () => null + ); + const setSpy = vi.spyOn(storagePersistenceService, 'write'); const result = service.getExistingOrCreateAuthStateControl({ configId: 'configId1', @@ -83,16 +90,22 @@ describe('Flows Data Service', () => { expect(result).toBeTruthy(); expect(result.length).toBe(41); - expect(setSpy).toHaveBeenCalledOnceWith('authStateControl', result, { - configId: 'configId1', - }); + expect(setSpy).toHaveBeenCalledExactlyOnceWith( + 'authStateControl', + result, + { + configId: 'configId1', + } + ); }); it('if stored it returns the value and does NOT Store the value again', () => { - spyOn(storagePersistenceService, 'read') - .withArgs('authStateControl', { configId: 'configId1' }) - .and.returnValue('someAuthStateControl'); - const setSpy = spyOn(storagePersistenceService, 'write'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authStateControl', { configId: 'configId1' }], + () => 'someAuthStateControl' + ); + const setSpy = vi.spyOn(storagePersistenceService, 'write'); const result = service.getExistingOrCreateAuthStateControl({ configId: 'configId1', @@ -106,11 +119,11 @@ describe('Flows Data Service', () => { describe('setSessionState', () => { it('setSessionState saves the value in the storage', () => { - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); service.setSessionState('Genesis', { configId: 'configId1' }); - expect(spy).toHaveBeenCalledOnceWith('session_state', 'Genesis', { + expect(spy).toHaveBeenCalledExactlyOnceWith('session_state', 'Genesis', { configId: 'configId1', }); }); @@ -118,7 +131,7 @@ describe('Flows Data Service', () => { describe('resetStorageFlowData', () => { it('resetStorageFlowData calls correct method on storagePersistenceService', () => { - const spy = spyOn(storagePersistenceService, 'resetStorageFlowData'); + const spy = vi.spyOn(storagePersistenceService, 'resetStorageFlowData'); service.resetStorageFlowData({ configId: 'configId1' }); @@ -128,26 +141,27 @@ describe('Flows Data Service', () => { describe('codeVerifier', () => { it('getCodeVerifier returns value from the store', () => { - const spy = spyOn(storagePersistenceService, 'read') + const spy = vi + .spyOn(storagePersistenceService, 'read') .withArgs('codeVerifier', { configId: 'configId1' }) - .and.returnValue('Genesis'); + .mockReturnValue('Genesis'); const result = service.getCodeVerifier({ configId: 'configId1' }); expect(result).toBe('Genesis'); - expect(spy).toHaveBeenCalledOnceWith('codeVerifier', { + expect(spy).toHaveBeenCalledExactlyOnceWith('codeVerifier', { configId: 'configId1', }); }); it('createCodeVerifier returns random createCodeVerifier and stores it', () => { - const setSpy = spyOn(storagePersistenceService, 'write'); + const setSpy = vi.spyOn(storagePersistenceService, 'write'); const result = service.createCodeVerifier({ configId: 'configId1' }); expect(result).toBeTruthy(); expect(result.length).toBe(67); - expect(setSpy).toHaveBeenCalledOnceWith('codeVerifier', result, { + expect(setSpy).toHaveBeenCalledExactlyOnceWith('codeVerifier', result, { configId: 'configId1', }); }); @@ -165,22 +179,26 @@ describe('Flows Data Service', () => { jasmine.clock().mockDate(baseTime); - spyOn(storagePersistenceService, 'read') - .withArgs('storageCodeFlowInProgress', config) - .and.returnValue(true); - const spyWrite = spyOn(storagePersistenceService, 'write'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['storageCodeFlowInProgress', config], + () => true + ); + const spyWrite = vi.spyOn(storagePersistenceService, 'write'); const isCodeFlowInProgressResult = service.isCodeFlowInProgress(config); expect(spyWrite).not.toHaveBeenCalled(); - expect(isCodeFlowInProgressResult).toBeTrue(); + expect(isCodeFlowInProgressResult).toBeTruthy(); }); it('state object does not exist returns false result', () => { // arrange - spyOn(storagePersistenceService, 'read') - .withArgs('storageCodeFlowInProgress', { configId: 'configId1' }) - .and.returnValue(null); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['storageCodeFlowInProgress', { configId: 'configId1' }], + () => null + ); // act const isCodeFlowInProgressResult = service.isCodeFlowInProgress({ @@ -188,7 +206,7 @@ describe('Flows Data Service', () => { }); // assert - expect(isCodeFlowInProgressResult).toBeFalse(); + expect(isCodeFlowInProgressResult).toBeFalsy(); }); }); @@ -200,23 +218,31 @@ describe('Flows Data Service', () => { jasmine.clock().mockDate(baseTime); - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); service.setCodeFlowInProgress({ configId: 'configId1' }); - expect(spy).toHaveBeenCalledOnceWith('storageCodeFlowInProgress', true, { - configId: 'configId1', - }); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'storageCodeFlowInProgress', + true, + { + configId: 'configId1', + } + ); }); }); describe('resetCodeFlowInProgress', () => { it('set resetCodeFlowInProgress to false when called', () => { - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); service.resetCodeFlowInProgress({ configId: 'configId1' }); - expect(spy).toHaveBeenCalledOnceWith('storageCodeFlowInProgress', false, { - configId: 'configId1', - }); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'storageCodeFlowInProgress', + false, + { + configId: 'configId1', + } + ); }); }); @@ -238,21 +264,21 @@ describe('Flows Data Service', () => { dateOfLaunchedProcessUtc: baseTime.toISOString(), }; - spyOn(storagePersistenceService, 'read') + vi.spyOn(storagePersistenceService, 'read') .withArgs('storageSilentRenewRunning', config) - .and.returnValue(JSON.stringify(storageObject)); - const spyWrite = spyOn(storagePersistenceService, 'write'); + .mockReturnValue(JSON.stringify(storageObject)); + const spyWrite = vi.spyOn(storagePersistenceService, 'write'); jasmine.clock().tick((config.silentRenewTimeoutInSeconds + 1) * 1000); const isSilentRenewRunningResult = service.isSilentRenewRunning(config); - expect(spyWrite).toHaveBeenCalledOnceWith( + expect(spyWrite).toHaveBeenCalledExactlyOnceWith( 'storageSilentRenewRunning', '', config ); - expect(isSilentRenewRunningResult).toBeFalse(); + expect(isSilentRenewRunningResult).toBeFalsy(); }); it('checks silent renew process and returns result', () => { @@ -272,27 +298,29 @@ describe('Flows Data Service', () => { dateOfLaunchedProcessUtc: baseTime.toISOString(), }; - spyOn(storagePersistenceService, 'read') + vi.spyOn(storagePersistenceService, 'read') .withArgs('storageSilentRenewRunning', config) - .and.returnValue(JSON.stringify(storageObject)); - const spyWrite = spyOn(storagePersistenceService, 'write'); + .mockReturnValue(JSON.stringify(storageObject)); + const spyWrite = vi.spyOn(storagePersistenceService, 'write'); const isSilentRenewRunningResult = service.isSilentRenewRunning(config); expect(spyWrite).not.toHaveBeenCalled(); - expect(isSilentRenewRunningResult).toBeTrue(); + expect(isSilentRenewRunningResult).toBeTruthy(); }); it('state object does not exist returns false result', () => { - spyOn(storagePersistenceService, 'read') - .withArgs('storageSilentRenewRunning', { configId: 'configId1' }) - .and.returnValue(null); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['storageSilentRenewRunning', { configId: 'configId1' }], + () => null + ); const isSilentRenewRunningResult = service.isSilentRenewRunning({ configId: 'configId1', }); - expect(isSilentRenewRunningResult).toBeFalse(); + expect(isSilentRenewRunningResult).toBeFalsy(); }); }); @@ -309,10 +337,10 @@ describe('Flows Data Service', () => { dateOfLaunchedProcessUtc: baseTime.toISOString(), }; - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); service.setSilentRenewRunning({ configId: 'configId1' }); - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( 'storageSilentRenewRunning', JSON.stringify(storageObject), { configId: 'configId1' } @@ -322,12 +350,16 @@ describe('Flows Data Service', () => { describe('resetSilentRenewRunning', () => { it('set resetSilentRenewRunning to empty string when called', () => { - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); service.resetSilentRenewRunning({ configId: 'configId1' }); - expect(spy).toHaveBeenCalledOnceWith('storageSilentRenewRunning', '', { - configId: 'configId1', - }); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'storageSilentRenewRunning', + '', + { + configId: 'configId1', + } + ); }); }); }); diff --git a/src/flows/flows-data.service.ts b/src/flows/flows-data.service.ts index 488ef37..42d55be 100644 --- a/src/flows/flows-data.service.ts +++ b/src/flows/flows-data.service.ts @@ -1,8 +1,8 @@ -import { inject, Injectable } from 'injection-js'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import { Injectable, inject } from 'injection-js'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; -import { SilentRenewRunning } from './flows.models'; +import type { SilentRenewRunning } from './flows.models'; import { RandomService } from './random/random.service'; @Injectable() @@ -18,7 +18,7 @@ export class FlowsDataService { createNonce(configuration: OpenIdConfiguration): string { const nonce = this.randomService.createRandom(40, configuration); - this.loggerService.logDebug(configuration, 'Nonce created. nonce:' + nonce); + this.loggerService.logDebug(configuration, `Nonce created. nonce:${nonce}`); this.setNonce(nonce, configuration); return nonce; diff --git a/src/flows/flows.service.spec.ts b/src/flows/flows.service.spec.ts index 34c33ce..cd36d28 100644 --- a/src/flows/flows.service.spec.ts +++ b/src/flows/flows.service.spec.ts @@ -1,7 +1,8 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; -import { CallbackContext } from './callback-context'; +import { vi } from 'vitest'; +import { mockProvider } from '../testing/mock'; +import type { CallbackContext } from './callback-context'; import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service'; import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service'; import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service'; @@ -64,27 +65,25 @@ describe('Flows Service', () => { }); describe('processCodeFlowCallback', () => { - it('calls all methods correctly', waitForAsync(() => { - const codeFlowCallbackSpy = spyOn( - codeFlowCallbackHandlerService, - 'codeFlowCallback' - ).and.returnValue(of({} as CallbackContext)); - const codeFlowCodeRequestSpy = spyOn( - codeFlowCallbackHandlerService, - 'codeFlowCodeRequest' - ).and.returnValue(of({} as CallbackContext)); - const callbackHistoryAndResetJwtKeysSpy = spyOn( - historyJwtKeysCallbackHandlerService, - 'callbackHistoryAndResetJwtKeys' - ).and.returnValue(of({} as CallbackContext)); - const callbackStateValidationSpy = spyOn( - stateValidationCallbackHandlerService, - 'callbackStateValidation' - ).and.returnValue(of({} as CallbackContext)); - const callbackUserSpy = spyOn( - userCallbackHandlerService, - 'callbackUser' - ).and.returnValue(of({} as CallbackContext)); + it('calls all methods correctly', async () => { + const codeFlowCallbackSpy = vi + .spyOn(codeFlowCallbackHandlerService, 'codeFlowCallback') + .mockReturnValue(of({} as CallbackContext)); + const codeFlowCodeRequestSpy = vi + .spyOn(codeFlowCallbackHandlerService, 'codeFlowCodeRequest') + .mockReturnValue(of({} as CallbackContext)); + const callbackHistoryAndResetJwtKeysSpy = vi + .spyOn( + historyJwtKeysCallbackHandlerService, + 'callbackHistoryAndResetJwtKeys' + ) + .mockReturnValue(of({} as CallbackContext)); + const callbackStateValidationSpy = vi + .spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation') + .mockReturnValue(of({} as CallbackContext)); + const callbackUserSpy = vi + .spyOn(userCallbackHandlerService, 'callbackUser') + .mockReturnValue(of({} as CallbackContext)); const allConfigs = [ { configId: 'configId1', @@ -92,10 +91,10 @@ describe('Flows Service', () => { ]; service - .processCodeFlowCallback('some-url1234', allConfigs[0], allConfigs) + .processCodeFlowCallback('some-url1234', allConfigs[0]!, allConfigs) .subscribe((value) => { expect(value).toEqual({} as CallbackContext); - expect(codeFlowCallbackSpy).toHaveBeenCalledOnceWith( + expect(codeFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith( 'some-url1234', allConfigs[0] ); @@ -104,27 +103,26 @@ describe('Flows Service', () => { expect(callbackStateValidationSpy).toHaveBeenCalledTimes(1); expect(callbackUserSpy).toHaveBeenCalledTimes(1); }); - })); + }); }); describe('processSilentRenewCodeFlowCallback', () => { - it('calls all methods correctly', waitForAsync(() => { - const codeFlowCodeRequestSpy = spyOn( - codeFlowCallbackHandlerService, - 'codeFlowCodeRequest' - ).and.returnValue(of({} as CallbackContext)); - const callbackHistoryAndResetJwtKeysSpy = spyOn( - historyJwtKeysCallbackHandlerService, - 'callbackHistoryAndResetJwtKeys' - ).and.returnValue(of({} as CallbackContext)); - const callbackStateValidationSpy = spyOn( - stateValidationCallbackHandlerService, - 'callbackStateValidation' - ).and.returnValue(of({} as CallbackContext)); - const callbackUserSpy = spyOn( - userCallbackHandlerService, - 'callbackUser' - ).and.returnValue(of({} as CallbackContext)); + it('calls all methods correctly', async () => { + const codeFlowCodeRequestSpy = vi + .spyOn(codeFlowCallbackHandlerService, 'codeFlowCodeRequest') + .mockReturnValue(of({} as CallbackContext)); + const callbackHistoryAndResetJwtKeysSpy = vi + .spyOn( + historyJwtKeysCallbackHandlerService, + 'callbackHistoryAndResetJwtKeys' + ) + .mockReturnValue(of({} as CallbackContext)); + const callbackStateValidationSpy = vi + .spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation') + .mockReturnValue(of({} as CallbackContext)); + const callbackUserSpy = vi + .spyOn(userCallbackHandlerService, 'callbackUser') + .mockReturnValue(of({} as CallbackContext)); const allConfigs = [ { configId: 'configId1', @@ -134,7 +132,7 @@ describe('Flows Service', () => { service .processSilentRenewCodeFlowCallback( {} as CallbackContext, - allConfigs[0], + allConfigs[0]!, allConfigs ) .subscribe((value) => { @@ -144,27 +142,26 @@ describe('Flows Service', () => { expect(callbackStateValidationSpy).toHaveBeenCalled(); expect(callbackUserSpy).toHaveBeenCalled(); }); - })); + }); }); describe('processImplicitFlowCallback', () => { - it('calls all methods correctly', waitForAsync(() => { - const implicitFlowCallbackSpy = spyOn( - implicitFlowCallbackHandlerService, - 'implicitFlowCallback' - ).and.returnValue(of({} as CallbackContext)); - const callbackHistoryAndResetJwtKeysSpy = spyOn( - historyJwtKeysCallbackHandlerService, - 'callbackHistoryAndResetJwtKeys' - ).and.returnValue(of({} as CallbackContext)); - const callbackStateValidationSpy = spyOn( - stateValidationCallbackHandlerService, - 'callbackStateValidation' - ).and.returnValue(of({} as CallbackContext)); - const callbackUserSpy = spyOn( - userCallbackHandlerService, - 'callbackUser' - ).and.returnValue(of({} as CallbackContext)); + it('calls all methods correctly', async () => { + const implicitFlowCallbackSpy = vi + .spyOn(implicitFlowCallbackHandlerService, 'implicitFlowCallback') + .mockReturnValue(of({} as CallbackContext)); + const callbackHistoryAndResetJwtKeysSpy = vi + .spyOn( + historyJwtKeysCallbackHandlerService, + 'callbackHistoryAndResetJwtKeys' + ) + .mockReturnValue(of({} as CallbackContext)); + const callbackStateValidationSpy = vi + .spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation') + .mockReturnValue(of({} as CallbackContext)); + const callbackUserSpy = vi + .spyOn(userCallbackHandlerService, 'callbackUser') + .mockReturnValue(of({} as CallbackContext)); const allConfigs = [ { configId: 'configId1', @@ -172,7 +169,7 @@ describe('Flows Service', () => { ]; service - .processImplicitFlowCallback(allConfigs[0], allConfigs, 'any-hash') + .processImplicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash') .subscribe((value) => { expect(value).toEqual({} as CallbackContext); expect(implicitFlowCallbackSpy).toHaveBeenCalled(); @@ -180,31 +177,32 @@ describe('Flows Service', () => { expect(callbackStateValidationSpy).toHaveBeenCalled(); expect(callbackUserSpy).toHaveBeenCalled(); }); - })); + }); }); describe('processRefreshToken', () => { - it('calls all methods correctly', waitForAsync(() => { - const refreshSessionWithRefreshTokensSpy = spyOn( - refreshSessionCallbackHandlerService, - 'refreshSessionWithRefreshTokens' - ).and.returnValue(of({} as CallbackContext)); - const refreshTokensRequestTokensSpy = spyOn( - refreshTokenCallbackHandlerService, - 'refreshTokensRequestTokens' - ).and.returnValue(of({} as CallbackContext)); - const callbackHistoryAndResetJwtKeysSpy = spyOn( - historyJwtKeysCallbackHandlerService, - 'callbackHistoryAndResetJwtKeys' - ).and.returnValue(of({} as CallbackContext)); - const callbackStateValidationSpy = spyOn( - stateValidationCallbackHandlerService, - 'callbackStateValidation' - ).and.returnValue(of({} as CallbackContext)); - const callbackUserSpy = spyOn( - userCallbackHandlerService, - 'callbackUser' - ).and.returnValue(of({} as CallbackContext)); + it('calls all methods correctly', async () => { + const refreshSessionWithRefreshTokensSpy = vi + .spyOn( + refreshSessionCallbackHandlerService, + 'refreshSessionWithRefreshTokens' + ) + .mockReturnValue(of({} as CallbackContext)); + const refreshTokensRequestTokensSpy = vi + .spyOn(refreshTokenCallbackHandlerService, 'refreshTokensRequestTokens') + .mockReturnValue(of({} as CallbackContext)); + const callbackHistoryAndResetJwtKeysSpy = vi + .spyOn( + historyJwtKeysCallbackHandlerService, + 'callbackHistoryAndResetJwtKeys' + ) + .mockReturnValue(of({} as CallbackContext)); + const callbackStateValidationSpy = vi + .spyOn(stateValidationCallbackHandlerService, 'callbackStateValidation') + .mockReturnValue(of({} as CallbackContext)); + const callbackUserSpy = vi + .spyOn(userCallbackHandlerService, 'callbackUser') + .mockReturnValue(of({} as CallbackContext)); const allConfigs = [ { configId: 'configId1', @@ -212,7 +210,7 @@ describe('Flows Service', () => { ]; service - .processRefreshToken(allConfigs[0], allConfigs) + .processRefreshToken(allConfigs[0]!, allConfigs) .subscribe((value) => { expect(value).toEqual({} as CallbackContext); expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled(); @@ -221,6 +219,6 @@ describe('Flows Service', () => { expect(callbackStateValidationSpy).toHaveBeenCalled(); expect(callbackUserSpy).toHaveBeenCalled(); }); - })); + }); }); }); diff --git a/src/flows/flows.service.ts b/src/flows/flows.service.ts index c06024d..9026b6e 100644 --- a/src/flows/flows.service.ts +++ b/src/flows/flows.service.ts @@ -1,8 +1,8 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import type { Observable } from 'rxjs'; import { concatMap } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from './callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from './callback-context'; import { CodeFlowCallbackHandlerService } from './callback-handling/code-flow-callback-handler.service'; import { HistoryJwtKeysCallbackHandlerService } from './callback-handling/history-jwt-keys-callback-handler.service'; import { ImplicitFlowCallbackHandlerService } from './callback-handling/implicit-flow-callback-handler.service'; diff --git a/src/flows/random/random.service.spec.ts b/src/flows/random/random.service.spec.ts index 44f6aa6..1660b4e 100644 --- a/src/flows/random/random.service.spec.ts +++ b/src/flows/random/random.service.spec.ts @@ -1,6 +1,7 @@ -import { TestBed } from '@angular/core/testing'; -import { mockProvider } from '../../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { LoggerService } from '../../logging/logger.service'; +import { mockProvider } from '../../testing/mock'; import { CryptoService } from '../../utils/crypto/crypto.service'; import { RandomService } from './random.service'; diff --git a/src/flows/reset-auth-data.service.spec.ts b/src/flows/reset-auth-data.service.spec.ts index b19b2cf..7de2d26 100644 --- a/src/flows/reset-auth-data.service.spec.ts +++ b/src/flows/reset-auth-data.service.spec.ts @@ -1,7 +1,8 @@ -import { TestBed } from '@angular/core/testing'; -import { mockProvider } from '../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; import { LoggerService } from '../logging/logger.service'; +import { mockProvider } from '../testing/mock'; import { UserService } from '../user-data/user.service'; import { FlowsDataService } from './flows-data.service'; import { ResetAuthDataService } from './reset-auth-data.service'; @@ -37,7 +38,7 @@ describe('ResetAuthDataService', () => { describe('resetAuthorizationData', () => { it('calls resetUserDataInStore when autoUserInfo is true', () => { - const resetUserDataInStoreSpy = spyOn( + const resetUserDataInStoreSpy = vi.spyOn( userService, 'resetUserDataInStore' ); @@ -47,16 +48,16 @@ describe('ResetAuthDataService', () => { }, ]; - service.resetAuthorizationData(allConfigs[0], allConfigs); + service.resetAuthorizationData(allConfigs[0]!, allConfigs); expect(resetUserDataInStoreSpy).toHaveBeenCalled(); }); it('calls correct methods', () => { - const resetStorageFlowDataSpy = spyOn( + const resetStorageFlowDataSpy = vi.spyOn( flowsDataService, 'resetStorageFlowData' ); - const setUnauthorizedAndFireEventSpy = spyOn( + const setUnauthorizedAndFireEventSpy = vi.spyOn( authStateService, 'setUnauthenticatedAndFireEvent' ); @@ -66,7 +67,7 @@ describe('ResetAuthDataService', () => { }, ]; - service.resetAuthorizationData(allConfigs[0], allConfigs); + service.resetAuthorizationData(allConfigs[0]!, allConfigs); expect(resetStorageFlowDataSpy).toHaveBeenCalled(); expect(setUnauthorizedAndFireEventSpy).toHaveBeenCalled(); diff --git a/src/flows/reset-auth-data.service.ts b/src/flows/reset-auth-data.service.ts index 1b327c5..1caa4e1 100644 --- a/src/flows/reset-auth-data.service.ts +++ b/src/flows/reset-auth-data.service.ts @@ -1,6 +1,6 @@ -import { inject, Injectable } from 'injection-js'; +import { Injectable, inject } from 'injection-js'; import { AuthStateService } from '../auth-state/auth-state.service'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { LoggerService } from '../logging/logger.service'; import { UserService } from '../user-data/user.service'; import { FlowsDataService } from './flows-data.service'; diff --git a/src/flows/signin-key-data.service.spec.ts b/src/flows/signin-key-data.service.spec.ts index 08fb0f4..9193722 100644 --- a/src/flows/signin-key-data.service.spec.ts +++ b/src/flows/signin-key-data.service.spec.ts @@ -1,11 +1,12 @@ -import { HttpResponse } from '@angular/common/http'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; +import { HttpResponse } from '@ngify/http'; import { isObservable, of, throwError } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; -import { createRetriableStream } from '../../test/create-retriable-stream.helper'; +import { vi } from 'vitest'; import { DataService } from '../api/data.service'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { createRetriableStream } from '../testing/create-retriable-stream.helper'; +import { mockProvider } from '../testing/mock'; import { SigninKeyDataService } from './signin-key-data.service'; const DUMMY_JWKS = { @@ -53,10 +54,12 @@ describe('Signin Key Data Service', () => { }); describe('getSigningKeys', () => { - it('throws error when no wellKnownEndpoints given', waitForAsync(() => { - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue(null); + it('throws error when no wellKnownEndpoints given', async () => { + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => null + ); const result = service.getSigningKeys({ configId: 'configId1' }); result.subscribe({ @@ -64,12 +67,14 @@ describe('Signin Key Data Service', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('throws error when no jwksUri given', waitForAsync(() => { - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ jwksUri: null }); + it('throws error when no jwksUri given', async () => { + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ jwksUri: null }) + ); const result = service.getSigningKeys({ configId: 'configId1' }); result.subscribe({ @@ -77,30 +82,34 @@ describe('Signin Key Data Service', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('calls dataservice if jwksurl is given', waitForAsync(() => { - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ jwksUri: 'someUrl' }); - const spy = spyOn(dataService, 'get').and.callFake(() => of()); + it('calls dataservice if jwksurl is given', async () => { + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ jwksUri: 'someUrl' }) + ); + const spy = vi.spyOn(dataService, 'get').mockImplementation(() => of()); const result = service.getSigningKeys({ configId: 'configId1' }); result.subscribe({ complete: () => { - expect(spy).toHaveBeenCalledOnceWith('someUrl', { + expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl', { configId: 'configId1', }); }, }); - })); + }); - it('should retry once', waitForAsync(() => { - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ jwksUri: 'someUrl' }); - spyOn(dataService, 'get').and.returnValue( + it('should retry once', async () => { + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ jwksUri: 'someUrl' }) + ); + vi.spyOn(dataService, 'get').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), of(DUMMY_JWKS) @@ -113,13 +122,15 @@ describe('Signin Key Data Service', () => { expect(res).toEqual(DUMMY_JWKS); }, }); - })); + }); - it('should retry twice', waitForAsync(() => { - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ jwksUri: 'someUrl' }); - spyOn(dataService, 'get').and.returnValue( + it('should retry twice', async () => { + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ jwksUri: 'someUrl' }) + ); + vi.spyOn(dataService, 'get').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), throwError(() => new Error('Error')), @@ -133,13 +144,15 @@ describe('Signin Key Data Service', () => { expect(res).toEqual(DUMMY_JWKS); }, }); - })); + }); - it('should fail after three tries', waitForAsync(() => { - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ jwksUri: 'someUrl' }); - spyOn(dataService, 'get').and.returnValue( + it('should fail after three tries', async () => { + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ jwksUri: 'someUrl' }) + ); + vi.spyOn(dataService, 'get').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), throwError(() => new Error('Error')), @@ -153,21 +166,21 @@ describe('Signin Key Data Service', () => { expect(err).toBeTruthy(); }, }); - })); + }); }); describe('handleErrorGetSigningKeys', () => { - it('keeps observable if error is catched', waitForAsync(() => { + it('keeps observable if error is catched', async () => { const result = (service as any).handleErrorGetSigningKeys( new HttpResponse() ); const hasTypeObservable = isObservable(result); - expect(hasTypeObservable).toBeTrue(); - })); + expect(hasTypeObservable).toBeTruthy(); + }); - it('logs error if error is response', waitForAsync(() => { - const logSpy = spyOn(loggerService, 'logError'); + it('logs error if error is response', async () => { + const logSpy = vi.spyOn(loggerService, 'logError'); (service as any) .handleErrorGetSigningKeys( @@ -176,31 +189,31 @@ describe('Signin Key Data Service', () => { ) .subscribe({ error: () => { - expect(logSpy).toHaveBeenCalledOnceWith( + expect(logSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, '400 - nono {}' ); }, }); - })); + }); - it('logs error if error is not a response', waitForAsync(() => { - const logSpy = spyOn(loggerService, 'logError'); + it('logs error if error is not a response', async () => { + const logSpy = vi.spyOn(loggerService, 'logError'); (service as any) .handleErrorGetSigningKeys('Just some Error', { configId: 'configId1' }) .subscribe({ error: () => { - expect(logSpy).toHaveBeenCalledOnceWith( + expect(logSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'Just some Error' ); }, }); - })); + }); - it('logs error if error with message property is not a response', waitForAsync(() => { - const logSpy = spyOn(loggerService, 'logError'); + it('logs error if error with message property is not a response', async () => { + const logSpy = vi.spyOn(loggerService, 'logError'); (service as any) .handleErrorGetSigningKeys( @@ -209,12 +222,12 @@ describe('Signin Key Data Service', () => { ) .subscribe({ error: () => { - expect(logSpy).toHaveBeenCalledOnceWith( + expect(logSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'Just some Error' ); }, }); - })); + }); }); }); diff --git a/src/http/index.ts b/src/http/index.ts new file mode 100644 index 0000000..ad689bb --- /dev/null +++ b/src/http/index.ts @@ -0,0 +1,11 @@ +import type { HttpFeature } from '@ngify/http'; + +export function provideHttpClient() { + // todo + throw new Error('todo!'); +} + +export function withInterceptorsFromDi(): HttpFeature { + // todo + throw new Error('todo!'); +} diff --git a/src/iframe/check-session.service.spec.ts b/src/iframe/check-session.service.spec.ts index db4db75..7e957c4 100644 --- a/src/iframe/check-session.service.spec.ts +++ b/src/iframe/check-session.service.spec.ts @@ -1,13 +1,14 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { of } from 'rxjs'; import { skip } from 'rxjs/operators'; -import { mockAbstractProvider, mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; import { OidcSecurityService } from '../oidc.security.service'; import { PublicEventsService } from '../public-events/public-events.service'; import { AbstractSecurityStorage } from '../storage/abstract-security-storage'; import { DefaultSessionStorageService } from '../storage/default-sessionstorage.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockAbstractProvider, mockProvider } from '../testing/mock'; import { PlatformProvider } from '../utils/platform-provider/platform.provider'; import { CheckSessionService } from './check-session.service'; import { IFrameService } from './existing-iframe.service'; @@ -67,7 +68,7 @@ describe('CheckSessionService', () => { }); it('getOrCreateIframe calls iFrameService.addIFrameToWindowBody if no Iframe exists', () => { - spyOn(iFrameService, 'addIFrameToWindowBody').and.callThrough(); + vi.spyOn(iFrameService, 'addIFrameToWindowBody')(); const result = (checkSessionService as any).getOrCreateIframe({ configId: 'configId1', @@ -89,7 +90,7 @@ describe('CheckSessionService', () => { it('init appends iframe on body with correct values', () => { expect((checkSessionService as any).sessionIframe).toBeFalsy(); - spyOn(loggerService, 'logDebug').and.callFake(() => undefined); + vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined); (checkSessionService as any).init(); const iframe = (checkSessionService as any).getOrCreateIframe({ @@ -105,29 +106,34 @@ describe('CheckSessionService', () => { }); it('log warning if authWellKnownEndpoints.check_session_iframe is not existing', () => { - const spyLogWarning = spyOn(loggerService, 'logWarning'); + const spyLogWarning = vi.spyOn(loggerService, 'logWarning'); const config = { configId: 'configId1' }; - spyOn(loggerService, 'logDebug').and.callFake(() => undefined); - spyOn(storagePersistenceService, 'read') + vi.spyOn(loggerService, 'logDebug').mockImplementation( + () => undefined + ); + vi.spyOn(storagePersistenceService, 'read') .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ checkSessionIframe: undefined }); + .mockReturnValue({ checkSessionIframe: undefined }); (checkSessionService as any).init(config); - expect(spyLogWarning).toHaveBeenCalledOnceWith(config, jasmine.any(String)); + expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith( + config, + expect.any(String) + ); }); it('start() calls pollserversession() with clientId if no scheduledheartbeat is set', () => { - const spy = spyOn(checkSessionService, 'pollServerSession'); + const spy = vi.spyOn(checkSessionService, 'pollServerSession'); const config = { clientId: 'clientId', configId: 'configId1' }; checkSessionService.start(config); - expect(spy).toHaveBeenCalledOnceWith('clientId', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('clientId', config); }); it('start() does not call pollServerSession() if scheduledHeartBeatRunning is set', () => { const config = { configId: 'configId1' }; - const spy = spyOn(checkSessionService, 'pollServerSession'); + const spy = vi.spyOn(checkSessionService, 'pollServerSession'); (checkSessionService as any).scheduledHeartBeatRunning = (): void => undefined; @@ -148,10 +154,10 @@ describe('CheckSessionService', () => { it('stopCheckingSession does nothing if scheduledHeartBeatRunning is not set', () => { (checkSessionService as any).scheduledHeartBeatRunning = null; - const spy = spyOn(checkSessionService, 'clearScheduledHeartBeat'); + const spy = vi.spyOn(checkSessionService, 'clearScheduledHeartBeat'); checkSessionService.stop(); - expect(spy).not.toHaveBeenCalledOnceWith(); + expect(spy).not.toHaveBeenCalledExactlyOnceWith(); }); describe('serverStateChanged', () => { @@ -167,7 +173,7 @@ describe('CheckSessionService', () => { const config = { startCheckSession: true, configId: 'configId1' }; const result = checkSessionService.serverStateChanged(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('returns true if startCheckSession is configured and checkSessionReceived is true', () => { @@ -175,17 +181,17 @@ describe('CheckSessionService', () => { const config = { startCheckSession: true, configId: 'configId1' }; const result = checkSessionService.serverStateChanged(config); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); }); describe('pollServerSession', () => { beforeEach(() => { - spyOn(checkSessionService, 'init').and.returnValue(of(undefined)); + vi.spyOn(checkSessionService, 'init').mockReturnValue(of(undefined)); }); it('increases outstandingMessages', () => { - spyOn(checkSessionService, 'getExistingIframe').and.returnValue({ + vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue({ contentWindow: { postMessage: () => undefined }, }); const authWellKnownEndpoints = { @@ -193,18 +199,20 @@ describe('CheckSessionService', () => { }; const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints) + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => authWellKnownEndpoints + ) .withArgs('session_state', config) - .and.returnValue('session_state'); - spyOn(loggerService, 'logDebug').and.callFake(() => undefined); + .mockReturnValue('session_state'); + vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined); (checkSessionService as any).pollServerSession('clientId', config); expect((checkSessionService as any).outstandingMessages).toBe(1); }); it('logs warning if iframe does not exist', () => { - spyOn(checkSessionService, 'getExistingIframe').and.returnValue( + vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue( null ); const authWellKnownEndpoints = { @@ -212,77 +220,91 @@ describe('CheckSessionService', () => { }; const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); - const spyLogWarning = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => authWellKnownEndpoints ); + const spyLogWarning = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); - spyOn(loggerService, 'logDebug').and.callFake(() => undefined); + vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined); (checkSessionService as any).pollServerSession('clientId', config); - expect(spyLogWarning).toHaveBeenCalledOnceWith( + expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith( config, - jasmine.any(String) + expect.any(String) ); }); it('logs warning if clientId is not set', () => { - spyOn(checkSessionService, 'getExistingIframe').and.returnValue({}); + vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue( + {} + ); const authWellKnownEndpoints = { checkSessionIframe: 'https://some-testing-url.com', }; const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); - const spyLogWarning = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => authWellKnownEndpoints ); + const spyLogWarning = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); - spyOn(loggerService, 'logDebug').and.callFake(() => undefined); + vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined); (checkSessionService as any).pollServerSession('', config); - expect(spyLogWarning).toHaveBeenCalledOnceWith( + expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith( config, - jasmine.any(String) + expect.any(String) ); }); it('logs debug if session_state is not set', () => { - spyOn(checkSessionService, 'getExistingIframe').and.returnValue({}); + vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue( + {} + ); const authWellKnownEndpoints = { checkSessionIframe: 'https://some-testing-url.com', }; const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints) + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => authWellKnownEndpoints + ) .withArgs('session_state', config) - .and.returnValue(null); + .mockReturnValue(null); - const spyLogDebug = spyOn(loggerService, 'logDebug').and.callFake( - () => undefined - ); + const spyLogDebug = vi + .spyOn(loggerService, 'logDebug') + .mockImplementation(() => undefined); (checkSessionService as any).pollServerSession('clientId', config); expect(spyLogDebug).toHaveBeenCalledTimes(2); }); it('logs debug if session_state is set but authWellKnownEndpoints are not set', () => { - spyOn(checkSessionService, 'getExistingIframe').and.returnValue({}); + vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue( + {} + ); const authWellKnownEndpoints = null; const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints) + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => authWellKnownEndpoints + ) .withArgs('session_state', config) - .and.returnValue('some_session_state'); - const spyLogDebug = spyOn(loggerService, 'logDebug').and.callFake( - () => undefined - ); + .mockReturnValue('some_session_state'); + const spyLogDebug = vi + .spyOn(loggerService, 'logDebug') + .mockImplementation(() => undefined); (checkSessionService as any).pollServerSession('clientId', config); expect(spyLogDebug).toHaveBeenCalledTimes(2); @@ -290,7 +312,7 @@ describe('CheckSessionService', () => { }); describe('init', () => { - it('returns falsy observable when lastIframerefresh and iframeRefreshInterval are bigger than now', waitForAsync(() => { + it('returns falsy observable when lastIframerefresh and iframeRefreshInterval are bigger than now', async () => { const serviceAsAny = checkSessionService as any; const dateNow = new Date(); const lastRefresh = dateNow.setMinutes(dateNow.getMinutes() + 30); @@ -301,7 +323,7 @@ describe('CheckSessionService', () => { serviceAsAny.init().subscribe((result: any) => { expect(result).toBeUndefined(); }); - })); + }); }); describe('isCheckSessionConfigured', () => { @@ -323,7 +345,7 @@ describe('CheckSessionService', () => { }); describe('checkSessionChanged$', () => { - it('emits when internal event is thrown', waitForAsync(() => { + it('emits when internal event is thrown', async () => { checkSessionService.checkSessionChanged$ .pipe(skip(1)) .subscribe((result) => { @@ -333,15 +355,15 @@ describe('CheckSessionService', () => { const serviceAsAny = checkSessionService as any; serviceAsAny.checkSessionChangedInternal$.next(true); - })); + }); - it('emits false initially', waitForAsync(() => { + it('emits false initially', async () => { checkSessionService.checkSessionChanged$.subscribe((result) => { expect(result).toBe(false); }); - })); + }); - it('emits false then true when emitted', waitForAsync(() => { + it('emits false then true when emitted', async () => { const expectedResultsInOrder = [false, true]; let counter = 0; @@ -351,6 +373,6 @@ describe('CheckSessionService', () => { }); (checkSessionService as any).checkSessionChangedInternal$.next(true); - })); + }); }); }); diff --git a/src/iframe/refresh-session-iframe.service.spec.ts b/src/iframe/refresh-session-iframe.service.spec.ts index 1dd2fca..0bd4c4a 100644 --- a/src/iframe/refresh-session-iframe.service.spec.ts +++ b/src/iframe/refresh-session-iframe.service.spec.ts @@ -1,7 +1,8 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; +import { mockProvider } from '../testing/mock'; import { UrlService } from '../utils/url/url.service'; import { RefreshSessionIframeService } from './refresh-session-iframe.service'; import { SilentRenewService } from './silent-renew.service'; @@ -31,37 +32,43 @@ describe('RefreshSessionIframeService ', () => { }); describe('refreshSessionWithIframe', () => { - it('calls sendAuthorizeRequestUsingSilentRenew with created url', waitForAsync(() => { - spyOn(urlService, 'getRefreshSessionSilentRenewUrl').and.returnValue( + it('calls sendAuthorizeRequestUsingSilentRenew with created url', async () => { + vi.spyOn(urlService, 'getRefreshSessionSilentRenewUrl').mockReturnValue( of('a-url') ); - const sendAuthorizeRequestUsingSilentRenewSpy = spyOn( - refreshSessionIframeService as any, - 'sendAuthorizeRequestUsingSilentRenew' - ).and.returnValue(of(null)); + const sendAuthorizeRequestUsingSilentRenewSpy = vi + .spyOn( + refreshSessionIframeService as any, + 'sendAuthorizeRequestUsingSilentRenew' + ) + .mockReturnValue(of(null)); const allConfigs = [{ configId: 'configId1' }]; refreshSessionIframeService - .refreshSessionWithIframe(allConfigs[0], allConfigs) + .refreshSessionWithIframe(allConfigs[0]!, allConfigs) .subscribe(() => { expect( sendAuthorizeRequestUsingSilentRenewSpy - ).toHaveBeenCalledOnceWith('a-url', allConfigs[0], allConfigs); + ).toHaveBeenCalledExactlyOnceWith( + 'a-url', + allConfigs[0]!, + allConfigs + ); }); - })); + }); }); describe('initSilentRenewRequest', () => { - it('dispatches customevent to window object', waitForAsync(() => { - const dispatchEventSpy = spyOn(window, 'dispatchEvent'); + it('dispatches customevent to window object', async () => { + const dispatchEventSpy = vi.spyOn(window, 'dispatchEvent'); (refreshSessionIframeService as any).initSilentRenewRequest(); - expect(dispatchEventSpy).toHaveBeenCalledOnceWith( + expect(dispatchEventSpy).toHaveBeenCalledExactlyOnceWith( new CustomEvent('oidc-silent-renew-init', { - detail: jasmine.any(Number), + detail: expect.any(Number), }) ); - })); + }); }); }); diff --git a/src/iframe/refresh-session-iframe.service.ts b/src/iframe/refresh-session-iframe.service.ts index db9ed49..345c61a 100644 --- a/src/iframe/refresh-session-iframe.service.ts +++ b/src/iframe/refresh-session-iframe.service.ts @@ -1,8 +1,8 @@ -import { DOCUMENT } from '../dom'; import { Injectable, RendererFactory2, inject } from 'injection-js'; import { Observable } from 'rxjs'; import { switchMap } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import { DOCUMENT } from '../dom'; import { LoggerService } from '../logging/logger.service'; import { UrlService } from '../utils/url/url.service'; import { SilentRenewService } from './silent-renew.service'; diff --git a/src/iframe/silent-renew.service.spec.ts b/src/iframe/silent-renew.service.spec.ts index da0d92f..53719fb 100644 --- a/src/iframe/silent-renew.service.spec.ts +++ b/src/iframe/silent-renew.service.spec.ts @@ -1,14 +1,15 @@ -import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { TestBed, fakeAsync, tick } from '@/testing'; import { Observable, of, throwError } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service'; import { IntervalService } from '../callback/interval.service'; -import { CallbackContext } from '../flows/callback-context'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsService } from '../flows/flows.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { LoggerService } from '../logging/logger.service'; +import { mockProvider } from '../testing/mock'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { ValidationResult } from '../validation/validation-result'; import { IFrameService } from './existing-iframe.service'; @@ -63,7 +64,7 @@ describe('SilentRenewService ', () => { describe('refreshSessionWithIFrameCompleted', () => { it('is of type observable', () => { expect(silentRenewService.refreshSessionWithIFrameCompleted$).toEqual( - jasmine.any(Observable) + expect.any(Observable) ); }); }); @@ -95,7 +96,7 @@ describe('SilentRenewService ', () => { describe('getOrCreateIframe', () => { it('returns iframe if iframe is truthy', () => { - spyOn(silentRenewService as any, 'getExistingIframe').and.returnValue({ + vi.spyOn(silentRenewService as any, 'getExistingIframe').mockReturnValue({ name: 'anything', }); @@ -109,31 +110,33 @@ describe('SilentRenewService ', () => { it('adds iframe to body if existing iframe is falsy', () => { const config = { configId: 'configId1' }; - spyOn(silentRenewService as any, 'getExistingIframe').and.returnValue( + vi.spyOn(silentRenewService as any, 'getExistingIframe').mockReturnValue( null ); - const spy = spyOn(iFrameService, 'addIFrameToWindowBody').and.returnValue( - { name: 'anything' } as HTMLIFrameElement - ); + const spy = vi + .spyOn(iFrameService, 'addIFrameToWindowBody') + .mockReturnValue({ name: 'anything' } as HTMLIFrameElement); const result = silentRenewService.getOrCreateIframe(config); expect(result).toEqual({ name: 'anything' } as HTMLIFrameElement); expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledOnceWith('myiFrameForSilentRenew', config); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'myiFrameForSilentRenew', + config + ); }); }); describe('codeFlowCallbackSilentRenewIframe', () => { - it('calls processSilentRenewCodeFlowCallback with correct arguments', waitForAsync(() => { + it('calls processSilentRenewCodeFlowCallback with correct arguments', async () => { const config = { configId: 'configId1' }; const allConfigs = [config]; - const spy = spyOn( - flowsService, - 'processSilentRenewCodeFlowCallback' - ).and.returnValue(of({} as CallbackContext)); + const spy = vi + .spyOn(flowsService, 'processSilentRenewCodeFlowCallback') + .mockReturnValue(of({} as CallbackContext)); const expectedContext = { code: 'some-code', refreshToken: '', @@ -152,32 +155,31 @@ describe('SilentRenewService ', () => { silentRenewService .codeFlowCallbackSilentRenewIframe([url, urlParts], config, allConfigs) .subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( expectedContext, config, allConfigs ); }); - })); + }); - it('throws error if url has error param and resets everything on error', waitForAsync(() => { + it('throws error if url has error param and resets everything on error', async () => { const config = { configId: 'configId1' }; const allConfigs = [config]; - const spy = spyOn( - flowsService, - 'processSilentRenewCodeFlowCallback' - ).and.returnValue(of({} as CallbackContext)); - const authStateServiceSpy = spyOn( + const spy = vi + .spyOn(flowsService, 'processSilentRenewCodeFlowCallback') + .mockReturnValue(of({} as CallbackContext)); + const authStateServiceSpy = vi.spyOn( authStateService, 'updateAndPublishAuthState' ); - const resetAuthorizationDataSpy = spyOn( + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); - const setNonceSpy = spyOn(flowsDataService, 'setNonce'); - const stopPeriodicTokenCheckSpy = spyOn( + const setNonceSpy = vi.spyOn(flowsDataService, 'setNonce'); + const stopPeriodicTokenCheckSpy = vi.spyOn( intervalService, 'stopPeriodicTokenCheck' ); @@ -191,121 +193,116 @@ describe('SilentRenewService ', () => { error: (error) => { expect(error).toEqual(new Error('some_error')); expect(spy).not.toHaveBeenCalled(); - expect(authStateServiceSpy).toHaveBeenCalledOnceWith({ + expect(authStateServiceSpy).toHaveBeenCalledExactlyOnceWith({ isAuthenticated: false, validationResult: ValidationResult.LoginRequired, isRenewProcess: true, }); - expect(resetAuthorizationDataSpy).toHaveBeenCalledOnceWith( + expect(resetAuthorizationDataSpy).toHaveBeenCalledExactlyOnceWith( config, allConfigs ); - expect(setNonceSpy).toHaveBeenCalledOnceWith('', config); + expect(setNonceSpy).toHaveBeenCalledExactlyOnceWith('', config); expect(stopPeriodicTokenCheckSpy).toHaveBeenCalledTimes(1); }, }); - })); + }); }); describe('silentRenewEventHandler', () => { - it('returns if no details is given', fakeAsync(() => { - const isCurrentFlowCodeFlowSpy = spyOn( - flowHelper, - 'isCurrentFlowCodeFlow' - ).and.returnValue(false); + it('returns if no details is given', async () => { + const isCurrentFlowCodeFlowSpy = vi + .spyOn(flowHelper, 'isCurrentFlowCodeFlow') + .mockReturnValue(false); - spyOn( + vi.spyOn( implicitFlowCallbackService, 'authenticatedImplicitFlowCallback' - ).and.returnValue(of({} as CallbackContext)); + ).mockReturnValue(of({} as CallbackContext)); const eventData = { detail: null } as CustomEvent; const allConfigs = [{ configId: 'configId1' }]; silentRenewService.silentRenewEventHandler( eventData, - allConfigs[0], + allConfigs[0]!, allConfigs ); - tick(1000); + await vi.advanceTimersByTimeAsync(1000); expect(isCurrentFlowCodeFlowSpy).not.toHaveBeenCalled(); - })); + }); - it('calls authorizedImplicitFlowCallback if current flow is not code flow', fakeAsync(() => { - const isCurrentFlowCodeFlowSpy = spyOn( - flowHelper, - 'isCurrentFlowCodeFlow' - ).and.returnValue(false); - const authorizedImplicitFlowCallbackSpy = spyOn( - implicitFlowCallbackService, - 'authenticatedImplicitFlowCallback' - ).and.returnValue(of({} as CallbackContext)); + it('calls authorizedImplicitFlowCallback if current flow is not code flow', async () => { + const isCurrentFlowCodeFlowSpy = vi + .spyOn(flowHelper, 'isCurrentFlowCodeFlow') + .mockReturnValue(false); + const authorizedImplicitFlowCallbackSpy = vi + .spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback') + .mockReturnValue(of({} as CallbackContext)); const eventData = { detail: 'detail' } as CustomEvent; const allConfigs = [{ configId: 'configId1' }]; silentRenewService.silentRenewEventHandler( eventData, - allConfigs[0], + allConfigs[0]!, allConfigs ); - tick(1000); + await vi.advanceTimersByTimeAsync(1000); expect(isCurrentFlowCodeFlowSpy).toHaveBeenCalled(); - expect(authorizedImplicitFlowCallbackSpy).toHaveBeenCalledOnceWith( - allConfigs[0], + expect(authorizedImplicitFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith( + allConfigs[0]!, allConfigs, 'detail' ); - })); + }); - it('calls codeFlowCallbackSilentRenewIframe if current flow is code flow', fakeAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); - const codeFlowCallbackSilentRenewIframe = spyOn( - silentRenewService, - 'codeFlowCallbackSilentRenewIframe' - ).and.returnValue(of({} as CallbackContext)); + it('calls codeFlowCallbackSilentRenewIframe if current flow is code flow', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); + const codeFlowCallbackSilentRenewIframe = vi + .spyOn(silentRenewService, 'codeFlowCallbackSilentRenewIframe') + .mockReturnValue(of({} as CallbackContext)); const eventData = { detail: 'detail?detail2' } as CustomEvent; const allConfigs = [{ configId: 'configId1' }]; silentRenewService.silentRenewEventHandler( eventData, - allConfigs[0], + allConfigs[0]!, allConfigs ); - tick(1000); - expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledOnceWith( + await vi.advanceTimersByTimeAsync(1000); + expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledExactlyOnceWith( ['detail', 'detail2'], - allConfigs[0], + allConfigs[0]!, allConfigs ); - })); + }); - it('calls authorizedImplicitFlowCallback if current flow is not code flow', fakeAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); - const codeFlowCallbackSilentRenewIframe = spyOn( - silentRenewService, - 'codeFlowCallbackSilentRenewIframe' - ).and.returnValue(of({} as CallbackContext)); + it('calls authorizedImplicitFlowCallback if current flow is not code flow', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); + const codeFlowCallbackSilentRenewIframe = vi + .spyOn(silentRenewService, 'codeFlowCallbackSilentRenewIframe') + .mockReturnValue(of({} as CallbackContext)); const eventData = { detail: 'detail?detail2' } as CustomEvent; const allConfigs = [{ configId: 'configId1' }]; silentRenewService.silentRenewEventHandler( eventData, - allConfigs[0], + allConfigs[0]!, allConfigs ); - tick(1000); - expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledOnceWith( + await vi.advanceTimersByTimeAsync(1000); + expect(codeFlowCallbackSilentRenewIframe).toHaveBeenCalledExactlyOnceWith( ['detail', 'detail2'], - allConfigs[0], + allConfigs[0]!, allConfigs ); - })); + }); - it('calls next on refreshSessionWithIFrameCompleted with callbackcontext', fakeAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); - spyOn( + it('calls next on refreshSessionWithIFrameCompleted with callbackcontext', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); + vi.spyOn( silentRenewService, 'codeFlowCallbackSilentRenewIframe' - ).and.returnValue( + ).mockReturnValue( of({ refreshToken: 'callbackContext' } as CallbackContext) ); const eventData = { detail: 'detail?detail2' } as CustomEvent; @@ -321,42 +318,42 @@ describe('SilentRenewService ', () => { silentRenewService.silentRenewEventHandler( eventData, - allConfigs[0], + allConfigs[0]!, allConfigs ); - tick(1000); - })); + await vi.advanceTimersByTimeAsync(1000); + }); - it('loggs and calls flowsDataService.resetSilentRenewRunning in case of an error', fakeAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); - spyOn( + it('loggs and calls flowsDataService.resetSilentRenewRunning in case of an error', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); + vi.spyOn( silentRenewService, 'codeFlowCallbackSilentRenewIframe' - ).and.returnValue(throwError(() => new Error('ERROR'))); - const resetSilentRenewRunningSpy = spyOn( + ).mockReturnValue(throwError(() => new Error('ERROR'))); + const resetSilentRenewRunningSpy = vi.spyOn( flowsDataService, 'resetSilentRenewRunning' ); - const logErrorSpy = spyOn(loggerService, 'logError'); + const logErrorSpy = vi.spyOn(loggerService, 'logError'); const allConfigs = [{ configId: 'configId1' }]; const eventData = { detail: 'detail?detail2' } as CustomEvent; silentRenewService.silentRenewEventHandler( eventData, - allConfigs[0], + allConfigs[0]!, allConfigs ); - tick(1000); + await vi.advanceTimersByTimeAsync(1000); expect(resetSilentRenewRunningSpy).toHaveBeenCalledTimes(1); expect(logErrorSpy).toHaveBeenCalledTimes(1); - })); + }); - it('calls next on refreshSessionWithIFrameCompleted with null in case of error', fakeAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); - spyOn( + it('calls next on refreshSessionWithIFrameCompleted with null in case of error', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); + vi.spyOn( silentRenewService, 'codeFlowCallbackSilentRenewIframe' - ).and.returnValue(throwError(() => new Error('ERROR'))); + ).mockReturnValue(throwError(() => new Error('ERROR'))); const eventData = { detail: 'detail?detail2' } as CustomEvent; const allConfigs = [{ configId: 'configId1' }]; @@ -368,10 +365,10 @@ describe('SilentRenewService ', () => { silentRenewService.silentRenewEventHandler( eventData, - allConfigs[0], + allConfigs[0]!, allConfigs ); - tick(1000); - })); + await vi.advanceTimersByTimeAsync(1000); + }); }); }); diff --git a/src/iframe/silent-renew.service.ts b/src/iframe/silent-renew.service.ts index 25c62de..62cdef1 100644 --- a/src/iframe/silent-renew.service.ts +++ b/src/iframe/silent-renew.service.ts @@ -1,12 +1,12 @@ import { HttpParams } from '@ngify/http'; -import { inject, Injectable } from 'injection-js'; -import { Observable, Subject, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, Subject, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { AuthStateService } from '../auth-state/auth-state.service'; import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service'; import { IntervalService } from '../callback/interval.service'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsService } from '../flows/flows.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; diff --git a/src/index.ts b/src/index.ts index 9f7afcf..f35cfe4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,6 @@ export * from './auth-options'; export * from './auth-state/auth-result'; export * from './auth-state/auth-state'; export * from './auth.module'; -export * from './auto-login/auto-login-all-routes.guard'; export * from './auto-login/auto-login-partial-routes.guard'; export * from './config/auth-well-known/auth-well-known-endpoints'; export * from './config/config.service'; @@ -29,3 +28,5 @@ export * from './user-data/userdata-result'; export * from './validation/jwtkeys'; export * from './validation/state-validation-result'; export * from './validation/validation-result'; +export * from './injection'; +export * from './router'; diff --git a/src/injection/convention.ts b/src/injection/convention.ts new file mode 100644 index 0000000..28947a1 --- /dev/null +++ b/src/injection/convention.ts @@ -0,0 +1,7 @@ +import { InjectionToken } from 'injection-js'; +import type { Observable } from 'rxjs'; + +export const APP_INITIALIZER = new InjectionToken< + // biome-ignore lint/suspicious/noConfusingVoidType: + readonly (() => void | Observable | Promise)[] +>('APP_INITIALIZER'); diff --git a/src/injection/index.ts b/src/injection/index.ts new file mode 100644 index 0000000..73ce5ec --- /dev/null +++ b/src/injection/index.ts @@ -0,0 +1,3 @@ +export { Module } from './module'; +export { APP_INITIALIZER } from './convention'; +export { injectAbstractType } from './inject'; diff --git a/src/injection/inject.ts b/src/injection/inject.ts new file mode 100644 index 0000000..4ad96bb --- /dev/null +++ b/src/injection/inject.ts @@ -0,0 +1,10 @@ +import { inject } from 'injection-js'; + +// biome-ignore lint/complexity/noBannedTypes: +export interface AbstractType extends Function { + prototype: T; +} + +export function injectAbstractType(abstractType: AbstractType): T { + return inject(abstractType as any); +} diff --git a/src/injection/module.ts b/src/injection/module.ts new file mode 100644 index 0000000..b715030 --- /dev/null +++ b/src/injection/module.ts @@ -0,0 +1,4 @@ +import 'reflect-metadata'; +import type { Injector } from 'injection-js'; + +export type Module = (parentInjector: Injector) => Injector; diff --git a/src/interceptor/auth.interceptor.spec.ts b/src/interceptor/auth.interceptor.spec.ts index d363f2f..9250759 100644 --- a/src/interceptor/auth.interceptor.spec.ts +++ b/src/interceptor/auth.interceptor.spec.ts @@ -1,19 +1,20 @@ +import { TestBed } from '@/testing'; import { HTTP_INTERCEPTORS, HttpClient, provideHttpClient, withInterceptors, withInterceptorsFromDi, -} from '@angular/common/http'; +} from '@ngify/http'; import { HttpTestingController, provideHttpClientTesting, -} from '@angular/common/http/testing'; -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { mockProvider } from '../../test/auto-mock'; +} from '@ngify/http/testing'; +import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; import { ConfigurationService } from '../config/config.service'; import { LoggerService } from '../logging/logger.service'; +import { mockProvider } from '../testing/mock'; import { AuthInterceptor, authInterceptor } from './auth.interceptor'; import { ClosestMatchingRouteService } from './closest-matching-route.service'; @@ -85,18 +86,22 @@ describe(`AuthHttpInterceptor`, () => { }); function runTests(): void { - it('should add an Authorization header when route matches and token is present', waitForAsync(() => { + it('should add an Authorization header when route matches and token is present', async () => { const actionUrl = `https://jsonplaceholder.typicode.com/`; - spyOn(configurationService, 'getAllConfigurations').and.returnValue([ + vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { secureRoutes: [actionUrl], configId: 'configId1', }, ]); - spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken'); - spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue( + 'thisIsAToken' + ); + vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( + true + ); httpClient.get(actionUrl).subscribe((response) => { expect(response).toBeTruthy(); @@ -108,18 +113,22 @@ describe(`AuthHttpInterceptor`, () => { httpRequest.flush('something'); httpTestingController.verify(); - })); + }); - it('should not add an Authorization header when `secureRoutes` is not given', waitForAsync(() => { + it('should not add an Authorization header when `secureRoutes` is not given', async () => { const actionUrl = `https://jsonplaceholder.typicode.com/`; - spyOn(configurationService, 'getAllConfigurations').and.returnValue([ + vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { configId: 'configId1', }, ]); - spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken'); - spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue( + 'thisIsAToken' + ); + vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( + true + ); httpClient.get(actionUrl).subscribe((response) => { expect(response).toBeTruthy(); @@ -131,20 +140,24 @@ describe(`AuthHttpInterceptor`, () => { httpRequest.flush('something'); httpTestingController.verify(); - })); + }); - it('should not add an Authorization header when no routes configured', waitForAsync(() => { + it('should not add an Authorization header when no routes configured', async () => { const actionUrl = `https://jsonplaceholder.typicode.com/`; - spyOn(configurationService, 'getAllConfigurations').and.returnValue([ + vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { secureRoutes: [], configId: 'configId1', }, ]); - spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); - spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken'); + vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( + true + ); + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue( + 'thisIsAToken' + ); httpClient.get(actionUrl).subscribe((response) => { expect(response).toBeTruthy(); @@ -156,19 +169,21 @@ describe(`AuthHttpInterceptor`, () => { httpRequest.flush('something'); httpTestingController.verify(); - })); + }); - it('should not add an Authorization header when no routes configured', waitForAsync(() => { + it('should not add an Authorization header when no routes configured', async () => { const actionUrl = `https://jsonplaceholder.typicode.com/`; - spyOn(configurationService, 'getAllConfigurations').and.returnValue([ + vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { secureRoutes: [], configId: 'configId1', }, ]); - spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); + vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( + true + ); httpClient.get(actionUrl).subscribe((response) => { expect(response).toBeTruthy(); @@ -180,20 +195,22 @@ describe(`AuthHttpInterceptor`, () => { httpRequest.flush('something'); httpTestingController.verify(); - })); + }); - it('should not add an Authorization header when route is configured but no token is present', waitForAsync(() => { + it('should not add an Authorization header when route is configured but no token is present', async () => { const actionUrl = `https://jsonplaceholder.typicode.com/`; - spyOn(configurationService, 'getAllConfigurations').and.returnValue([ + vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { secureRoutes: [actionUrl], configId: 'configId1', }, ]); - spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); - spyOn(authStateService, 'getAccessToken').and.returnValue(''); + vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( + true + ); + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(''); httpClient.get(actionUrl).subscribe((response) => { expect(response).toBeTruthy(); @@ -205,12 +222,14 @@ describe(`AuthHttpInterceptor`, () => { httpRequest.flush('something'); httpTestingController.verify(); - })); + }); - it('should not add an Authorization header when no config is present', waitForAsync(() => { + it('should not add an Authorization header when no config is present', async () => { const actionUrl = `https://jsonplaceholder.typicode.com/`; - spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(false); + vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( + false + ); httpClient.get(actionUrl).subscribe((response) => { expect(response).toBeTruthy(); @@ -222,22 +241,24 @@ describe(`AuthHttpInterceptor`, () => { httpRequest.flush('something'); httpTestingController.verify(); - })); + }); - it('should not add an Authorization header when no configured route is matching the request', waitForAsync(() => { - spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); + it('should not add an Authorization header when no configured route is matching the request', async () => { + vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( + true + ); const actionUrl = `https://jsonplaceholder.typicode.com/`; - spyOn(configurationService, 'getAllConfigurations').and.returnValue([ + vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { secureRoutes: [actionUrl], configId: 'configId1', }, ]); - spyOn( + vi.spyOn( closestMatchingRouteService, 'getConfigIdForClosestMatchingRoute' - ).and.returnValue({ + ).mockReturnValue({ matchingRoute: null, matchingConfig: null, }); @@ -252,18 +273,22 @@ describe(`AuthHttpInterceptor`, () => { httpRequest.flush('something'); httpTestingController.verify(); - })); + }); - it('should add an Authorization header when multiple routes are configured and token is present', waitForAsync(() => { + it('should add an Authorization header when multiple routes are configured and token is present', async () => { const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl2 = `https://some-other-url.com/`; - spyOn(configurationService, 'getAllConfigurations').and.returnValue([ + vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { secureRoutes: [actionUrl, actionUrl2], configId: 'configId1' }, ]); - spyOn(authStateService, 'getAccessToken').and.returnValue('thisIsAToken'); - spyOn(configurationService, 'hasAtLeastOneConfig').and.returnValue(true); + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue( + 'thisIsAToken' + ); + vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( + true + ); httpClient.get(actionUrl).subscribe((response) => { expect(response).toBeTruthy(); @@ -284,6 +309,6 @@ describe(`AuthHttpInterceptor`, () => { httpRequest.flush('something'); httpRequest2.flush('something'); httpTestingController.verify(); - })); + }); } }); diff --git a/src/interceptor/closest-matching-route.service.spec.ts b/src/interceptor/closest-matching-route.service.spec.ts index 7cf5581..ecf8746 100644 --- a/src/interceptor/closest-matching-route.service.spec.ts +++ b/src/interceptor/closest-matching-route.service.spec.ts @@ -1,6 +1,7 @@ -import { TestBed } from '@angular/core/testing'; -import { mockProvider } from '../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; +import { mockProvider } from '../testing/mock'; import { ClosestMatchingRouteService } from './closest-matching-route.service'; describe('ClosestMatchingRouteService', () => { diff --git a/src/logging/logger.service.spec.ts b/src/logging/logger.service.spec.ts index 7da12b9..30111c9 100644 --- a/src/logging/logger.service.spec.ts +++ b/src/logging/logger.service.spec.ts @@ -1,4 +1,5 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { AbstractLoggerService } from './abstract-logger.service'; import { ConsoleLoggerService } from './console-logger.service'; import { LogLevel } from './log-level'; @@ -26,7 +27,7 @@ describe('Logger Service', () => { describe('logError', () => { it('should not log error if loglevel is None', () => { - const spy = spyOn(console, 'error'); + const spy = vi.spyOn(console, 'error'); loggerService.logError( { configId: 'configId1', logLevel: LogLevel.None }, @@ -36,23 +37,25 @@ describe('Logger Service', () => { }); it('should log error as default if error is string', () => { - const spy = spyOn(console, 'error'); + const spy = vi.spyOn(console, 'error'); loggerService.logError({ configId: 'configId1' }, 'some message'); - expect(spy).toHaveBeenCalledOnceWith('[ERROR] configId1 - some message'); + expect(spy).toHaveBeenCalledExactlyOnceWith( + '[ERROR] configId1 - some message' + ); }); it('should log error as default if error is object', () => { - const spy = spyOn(console, 'error'); + const spy = vi.spyOn(console, 'error'); loggerService.logError({ configId: 'configId1' }, { some: 'message' }); - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( '[ERROR] configId1 - {"some":"message"}' ); }); it('should always log error with args', () => { - const spy = spyOn(console, 'error'); + const spy = vi.spyOn(console, 'error'); loggerService.logError( { configId: 'configId1' }, @@ -60,7 +63,7 @@ describe('Logger Service', () => { 'arg1', 'arg2' ); - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( '[ERROR] configId1 - some message', 'arg1', 'arg2' @@ -70,7 +73,7 @@ describe('Logger Service', () => { describe('logWarn', () => { it('should not log if no log level is set (null)', () => { - const spy = spyOn(console, 'warn'); + const spy = vi.spyOn(console, 'warn'); loggerService.logWarning( { configId: 'configId1', logLevel: undefined }, @@ -80,14 +83,14 @@ describe('Logger Service', () => { }); it('should not log if no config is given', () => { - const spy = spyOn(console, 'warn'); + const spy = vi.spyOn(console, 'warn'); loggerService.logWarning({}, 'some message'); expect(spy).not.toHaveBeenCalled(); }); it('should not log if no log level is set (undefined)', () => { - const spy = spyOn(console, 'warn'); + const spy = vi.spyOn(console, 'warn'); loggerService.logWarning({ configId: 'configId1' }, 'some message'); @@ -95,7 +98,7 @@ describe('Logger Service', () => { }); it('should not log if log level is turned off', () => { - const spy = spyOn(console, 'warn'); + const spy = vi.spyOn(console, 'warn'); loggerService.logWarning( { configId: 'configId1', logLevel: LogLevel.None }, @@ -105,29 +108,31 @@ describe('Logger Service', () => { }); it('should log warning when loglevel is Warn and message is string', () => { - const spy = spyOn(console, 'warn'); + const spy = vi.spyOn(console, 'warn'); loggerService.logWarning( { configId: 'configId1', logLevel: LogLevel.Warn }, 'some message' ); - expect(spy).toHaveBeenCalledOnceWith('[WARN] configId1 - some message'); + expect(spy).toHaveBeenCalledExactlyOnceWith( + '[WARN] configId1 - some message' + ); }); it('should log warning when loglevel is Warn and message is object', () => { - const spy = spyOn(console, 'warn'); + const spy = vi.spyOn(console, 'warn'); loggerService.logWarning( { configId: 'configId1', logLevel: LogLevel.Warn }, { some: 'message' } ); - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( '[WARN] configId1 - {"some":"message"}' ); }); it('should log warning when loglevel is Warn with args', () => { - const spy = spyOn(console, 'warn'); + const spy = vi.spyOn(console, 'warn'); loggerService.logWarning( { configId: 'configId1', logLevel: LogLevel.Warn }, @@ -135,7 +140,7 @@ describe('Logger Service', () => { 'arg1', 'arg2' ); - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( '[WARN] configId1 - some message', 'arg1', 'arg2' @@ -143,17 +148,19 @@ describe('Logger Service', () => { }); it('should log warning when loglevel is Debug', () => { - const spy = spyOn(console, 'warn'); + const spy = vi.spyOn(console, 'warn'); loggerService.logWarning( { configId: 'configId1', logLevel: LogLevel.Debug }, 'some message' ); - expect(spy).toHaveBeenCalledOnceWith('[WARN] configId1 - some message'); + expect(spy).toHaveBeenCalledExactlyOnceWith( + '[WARN] configId1 - some message' + ); }); it('should not log warning when loglevel is error', () => { - const spy = spyOn(console, 'warn'); + const spy = vi.spyOn(console, 'warn'); loggerService.logWarning( { configId: 'configId1', logLevel: LogLevel.Error }, @@ -165,7 +172,7 @@ describe('Logger Service', () => { describe('logDebug', () => { it('should not log if no log level is set (null)', () => { - const spy = spyOn(console, 'debug'); + const spy = vi.spyOn(console, 'debug'); loggerService.logDebug( { configId: 'configId1', logLevel: undefined }, @@ -175,14 +182,14 @@ describe('Logger Service', () => { }); it('should not log if no log level is set (undefined)', () => { - const spy = spyOn(console, 'debug'); + const spy = vi.spyOn(console, 'debug'); loggerService.logDebug({ configId: 'configId1' }, 'some message'); expect(spy).not.toHaveBeenCalled(); }); it('should not log if log level is turned off', () => { - const spy = spyOn(console, 'debug'); + const spy = vi.spyOn(console, 'debug'); loggerService.logDebug( { configId: 'configId1', logLevel: LogLevel.None }, @@ -192,29 +199,31 @@ describe('Logger Service', () => { }); it('should log when loglevel is Debug and value is string', () => { - const spy = spyOn(console, 'debug'); + const spy = vi.spyOn(console, 'debug'); loggerService.logDebug( { configId: 'configId1', logLevel: LogLevel.Debug }, 'some message' ); - expect(spy).toHaveBeenCalledOnceWith('[DEBUG] configId1 - some message'); + expect(spy).toHaveBeenCalledExactlyOnceWith( + '[DEBUG] configId1 - some message' + ); }); it('should log when loglevel is Debug and value is object', () => { - const spy = spyOn(console, 'debug'); + const spy = vi.spyOn(console, 'debug'); loggerService.logDebug( { configId: 'configId1', logLevel: LogLevel.Debug }, { some: 'message' } ); - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( '[DEBUG] configId1 - {"some":"message"}' ); }); it('should log when loglevel is Debug with args', () => { - const spy = spyOn(console, 'debug'); + const spy = vi.spyOn(console, 'debug'); loggerService.logDebug( { configId: 'configId1', logLevel: LogLevel.Debug }, @@ -222,7 +231,7 @@ describe('Logger Service', () => { 'arg1', 'arg2' ); - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( '[DEBUG] configId1 - some message', 'arg1', 'arg2' @@ -230,7 +239,7 @@ describe('Logger Service', () => { }); it('should not log when loglevel is Warn', () => { - const spy = spyOn(console, 'debug'); + const spy = vi.spyOn(console, 'debug'); loggerService.logDebug( { configId: 'configId1', logLevel: LogLevel.Warn }, @@ -240,7 +249,7 @@ describe('Logger Service', () => { }); it('should not log when loglevel is error', () => { - const spy = spyOn(console, 'debug'); + const spy = vi.spyOn(console, 'debug'); loggerService.logDebug( { configId: 'configId1', logLevel: LogLevel.Error }, diff --git a/src/logging/logger.service.ts b/src/logging/logger.service.ts index b1f29d6..3e5d251 100644 --- a/src/logging/logger.service.ts +++ b/src/logging/logger.service.ts @@ -1,11 +1,14 @@ -import { inject, Injectable } from 'injection-js'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import { Injectable } from 'injection-js'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import { injectAbstractType } from '../injection/inject'; import { AbstractLoggerService } from './abstract-logger.service'; import { LogLevel } from './log-level'; @Injectable() export class LoggerService { - private readonly abstractLoggerService = inject(AbstractLoggerService); + private readonly abstractLoggerService = injectAbstractType( + AbstractLoggerService + ); logError( configuration: OpenIdConfiguration, diff --git a/src/login/login.service.spec.ts b/src/login/login.service.spec.ts index bf538bd..cc98702 100644 --- a/src/login/login.service.spec.ts +++ b/src/login/login.service.spec.ts @@ -1,9 +1,10 @@ +import { TestBed } from '@/testing'; import { CommonModule } from '@angular/common'; -import { TestBed, waitForAsync } from '@angular/core/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; +import { vi } from 'vitest'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; -import { LoginResponse } from './login-response'; +import { mockProvider } from '../testing/mock'; +import type { LoginResponse } from './login-response'; import { LoginService } from './login.service'; import { ParLoginService } from './par/par-login.service'; import { PopUpLoginService } from './popup/popup-login.service'; @@ -48,8 +49,8 @@ describe('LoginService', () => { describe('login', () => { it('calls parLoginService loginPar if usePushedAuthorisationRequests is true', () => { const config = { usePushedAuthorisationRequests: true }; - const loginParSpy = spyOn(parLoginService, 'loginPar'); - const standardLoginSpy = spyOn(standardLoginService, 'loginStandard'); + const loginParSpy = vi.spyOn(parLoginService, 'loginPar'); + const standardLoginSpy = vi.spyOn(standardLoginService, 'loginStandard'); service.login(config); @@ -59,8 +60,8 @@ describe('LoginService', () => { it('calls standardLoginService loginStandard if usePushedAuthorisationRequests is false', () => { const config = { usePushedAuthorisationRequests: false }; - const loginParSpy = spyOn(parLoginService, 'loginPar'); - const standardLoginSpy = spyOn(standardLoginService, 'loginStandard'); + const loginParSpy = vi.spyOn(parLoginService, 'loginPar'); + const standardLoginSpy = vi.spyOn(standardLoginService, 'loginStandard'); service.login(config); @@ -71,7 +72,7 @@ describe('LoginService', () => { it('stores the customParams to the storage if customParams are given', () => { // arrange const config = { usePushedAuthorisationRequests: false }; - const storagePersistenceServiceSpy = spyOn( + const storagePersistenceServiceSpy = vi.spyOn( storagePersistenceService, 'write' ); @@ -79,7 +80,7 @@ describe('LoginService', () => { service.login(config, authOptions); - expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith( + expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith( 'storageCustomParamsAuthRequest', { custom: 'params' }, config @@ -89,8 +90,8 @@ describe('LoginService', () => { it("should throw error if configuration is null and doesn't call loginPar or loginStandard", () => { // arrange const config = null; - const loginParSpy = spyOn(parLoginService, 'loginPar'); - const standardLoginSpy = spyOn(standardLoginService, 'loginStandard'); + const loginParSpy = vi.spyOn(parLoginService, 'loginPar'); + const standardLoginSpy = vi.spyOn(standardLoginService, 'loginStandard'); const authOptions = { customParams: { custom: 'params' } }; // act @@ -106,17 +107,15 @@ describe('LoginService', () => { }); describe('loginWithPopUp', () => { - it('calls parLoginService loginWithPopUpPar if usePushedAuthorisationRequests is true', waitForAsync(() => { + it('calls parLoginService loginWithPopUpPar if usePushedAuthorisationRequests is true', async () => { // arrange const config = { usePushedAuthorisationRequests: true }; - const loginWithPopUpPar = spyOn( - parLoginService, - 'loginWithPopUpPar' - ).and.returnValue(of({} as LoginResponse)); - const loginWithPopUpStandardSpy = spyOn( - popUpLoginService, - 'loginWithPopUpStandard' - ).and.returnValue(of({} as LoginResponse)); + const loginWithPopUpPar = vi + .spyOn(parLoginService, 'loginWithPopUpPar') + .mockReturnValue(of({} as LoginResponse)); + const loginWithPopUpStandardSpy = vi + .spyOn(popUpLoginService, 'loginWithPopUpStandard') + .mockReturnValue(of({} as LoginResponse)); // act service.loginWithPopUp(config, [config]).subscribe(() => { @@ -124,19 +123,17 @@ describe('LoginService', () => { expect(loginWithPopUpPar).toHaveBeenCalledTimes(1); expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled(); }); - })); + }); - it('calls standardLoginService loginstandard if usePushedAuthorisationRequests is false', waitForAsync(() => { + it('calls standardLoginService loginstandard if usePushedAuthorisationRequests is false', async () => { // arrange const config = { usePushedAuthorisationRequests: false }; - const loginWithPopUpPar = spyOn( - parLoginService, - 'loginWithPopUpPar' - ).and.returnValue(of({} as LoginResponse)); - const loginWithPopUpStandardSpy = spyOn( - popUpLoginService, - 'loginWithPopUpStandard' - ).and.returnValue(of({} as LoginResponse)); + const loginWithPopUpPar = vi + .spyOn(parLoginService, 'loginWithPopUpPar') + .mockReturnValue(of({} as LoginResponse)); + const loginWithPopUpStandardSpy = vi + .spyOn(popUpLoginService, 'loginWithPopUpStandard') + .mockReturnValue(of({} as LoginResponse)); // act service.loginWithPopUp(config, [config]).subscribe(() => { @@ -144,46 +141,44 @@ describe('LoginService', () => { expect(loginWithPopUpPar).not.toHaveBeenCalled(); expect(loginWithPopUpStandardSpy).toHaveBeenCalledTimes(1); }); - })); + }); - it('stores the customParams to the storage if customParams are given', waitForAsync(() => { + it('stores the customParams to the storage if customParams are given', async () => { // arrange const config = { usePushedAuthorisationRequests: false }; - const storagePersistenceServiceSpy = spyOn( + const storagePersistenceServiceSpy = vi.spyOn( storagePersistenceService, 'write' ); const authOptions = { customParams: { custom: 'params' } }; - spyOn(popUpLoginService, 'loginWithPopUpStandard').and.returnValue( + vi.spyOn(popUpLoginService, 'loginWithPopUpStandard').mockReturnValue( of({} as LoginResponse) ); // act service.loginWithPopUp(config, [config], authOptions).subscribe(() => { // assert - expect(storagePersistenceServiceSpy).toHaveBeenCalledOnceWith( + expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith( 'storageCustomParamsAuthRequest', { custom: 'params' }, config ); }); - })); + }); it('returns error if there is already a popup open', () => { // arrange const config = { usePushedAuthorisationRequests: false }; const authOptions = { customParams: { custom: 'params' } }; - const loginWithPopUpPar = spyOn( - parLoginService, - 'loginWithPopUpPar' - ).and.returnValue(of({} as LoginResponse)); - const loginWithPopUpStandardSpy = spyOn( - popUpLoginService, - 'loginWithPopUpStandard' - ).and.returnValue(of({} as LoginResponse)); + const loginWithPopUpPar = vi + .spyOn(parLoginService, 'loginWithPopUpPar') + .mockReturnValue(of({} as LoginResponse)); + const loginWithPopUpStandardSpy = vi + .spyOn(popUpLoginService, 'loginWithPopUpStandard') + .mockReturnValue(of({} as LoginResponse)); - spyOn(popUpService, 'isCurrentlyInPopup').and.returnValue(true); + vi.spyOn(popUpService, 'isCurrentlyInPopup').mockReturnValue(true); // act service diff --git a/src/login/login.service.ts b/src/login/login.service.ts index d88d102..709145b 100644 --- a/src/login/login.service.ts +++ b/src/login/login.service.ts @@ -1,12 +1,12 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable, of } from 'rxjs'; -import { AuthOptions } from '../auth-options'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, of } from 'rxjs'; +import type { AuthOptions } from '../auth-options'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; -import { LoginResponse } from './login-response'; +import type { LoginResponse } from './login-response'; import { ParLoginService } from './par/par-login.service'; import { PopUpLoginService } from './popup/popup-login.service'; -import { PopupOptions } from './popup/popup-options'; +import type { PopupOptions } from './popup/popup-options'; import { PopUpService } from './popup/popup.service'; import { StandardLoginService } from './standard/standard-login.service'; @@ -45,12 +45,9 @@ export class LoginService { } if (usePushedAuthorisationRequests) { - return this.parLoginService.loginPar(configuration, authOptions); + this.parLoginService.loginPar(configuration, authOptions); } else { - return this.standardLoginService.loginStandard( - configuration, - authOptions - ); + this.standardLoginService.loginStandard(configuration, authOptions); } } diff --git a/src/login/par/par-login.service.spec.ts b/src/login/par/par-login.service.spec.ts index 02e0a99..c41831e 100644 --- a/src/login/par/par-login.service.spec.ts +++ b/src/login/par/par-login.service.spec.ts @@ -1,17 +1,18 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; +import { vi } from 'vitest'; import { CheckAuthService } from '../../auth-state/check-auth.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { LoggerService } from '../../logging/logger.service'; +import { mockProvider } from '../../testing/mock'; import { RedirectService } from '../../utils/redirect/redirect.service'; import { UrlService } from '../../utils/url/url.service'; -import { LoginResponse } from '../login-response'; -import { PopupResult } from '../popup/popup-result'; +import type { LoginResponse } from '../login-response'; +import type { PopupResult } from '../popup/popup-result'; import { PopUpService } from '../popup/popup.service'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; import { ParLoginService } from './par-login.service'; -import { ParResponse } from './par-response'; +import type { ParResponse } from './par-response'; import { ParService } from './par.service'; describe('ParLoginService', () => { @@ -60,33 +61,33 @@ describe('ParLoginService', () => { }); describe('loginPar', () => { - it('does nothing if it has an invalid response type', waitForAsync(() => { - spyOn( + it('does nothing if it has an invalid response type', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(false); - const loggerSpy = spyOn(loggerService, 'logError'); + ).mockReturnValue(false); + const loggerSpy = vi.spyOn(loggerService, 'logError'); const result = service.loginPar({}); expect(result).toBeUndefined(); expect(loggerSpy).toHaveBeenCalled(); - })); + }); - it('calls parService.postParRequest without custom params when no custom params are passed', waitForAsync(() => { - spyOn( + it('calls parService.postParRequest without custom params when no custom params are passed', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - const spy = spyOn(parService, 'postParRequest').and.returnValue( - of({ requestUri: 'requestUri' } as ParResponse) - ); + const spy = vi + .spyOn(parService, 'postParRequest') + .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); const result = service.loginPar({ authWellknownEndpointUrl: 'authWellknownEndpoint', @@ -95,69 +96,69 @@ describe('ParLoginService', () => { expect(result).toBeUndefined(); expect(spy).toHaveBeenCalled(); - })); + }); - it('calls parService.postParRequest with custom params when custom params are passed', waitForAsync(() => { - spyOn( + it('calls parService.postParRequest with custom params when custom params are passed', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - const spy = spyOn(parService, 'postParRequest').and.returnValue( - of({ requestUri: 'requestUri' } as ParResponse) - ); + const spy = vi + .spyOn(parService, 'postParRequest') + .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); const result = service.loginPar(config, { customParams: { some: 'thing' }, }); expect(result).toBeUndefined(); - expect(spy).toHaveBeenCalledOnceWith(config, { + expect(spy).toHaveBeenCalledExactlyOnceWith(config, { customParams: { some: 'thing' }, }); - })); + }); - it('returns undefined and logs error when no url could be created', waitForAsync(() => { - spyOn( + it('returns undefined and logs error when no url could be created', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn(parService, 'postParRequest').and.returnValue( + vi.spyOn(parService, 'postParRequest').mockReturnValue( of({ requestUri: 'requestUri' } as ParResponse) ); - spyOn(urlService, 'getAuthorizeParUrl').and.returnValue(''); - const spy = spyOn(loggerService, 'logError'); + vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(''); + const spy = vi.spyOn(loggerService, 'logError'); const result = service.loginPar(config); expect(result).toBeUndefined(); expect(spy).toHaveBeenCalledTimes(1); - })); + }); - it('calls redirect service redirectTo when url could be created', waitForAsync(() => { - spyOn( + it('calls redirect service redirectTo when url could be created', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', @@ -165,42 +166,46 @@ describe('ParLoginService', () => { const authOptions = {}; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn(parService, 'postParRequest').and.returnValue( + vi.spyOn(parService, 'postParRequest').mockReturnValue( of({ requestUri: 'requestUri' } as ParResponse) ); - spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); - const spy = spyOn(redirectService, 'redirectTo'); + vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue( + 'some-par-url' + ); + const spy = vi.spyOn(redirectService, 'redirectTo'); service.loginPar(config, authOptions); - expect(spy).toHaveBeenCalledOnceWith('some-par-url'); - })); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-par-url'); + }); - it('calls urlHandler when URL is passed', waitForAsync(() => { - spyOn( + it('calls urlHandler when URL is passed', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn(parService, 'postParRequest').and.returnValue( + vi.spyOn(parService, 'postParRequest').mockReturnValue( of({ requestUri: 'requestUri' } as ParResponse) ); - spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); - const redirectToSpy = spyOn(redirectService, 'redirectTo'); + vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue( + 'some-par-url' + ); + const redirectToSpy = vi.spyOn(redirectService, 'redirectTo'); const spy = jasmine.createSpy(); const urlHandler = (url: any): void => { spy(url); @@ -208,18 +213,18 @@ describe('ParLoginService', () => { service.loginPar(config, { urlHandler }); - expect(spy).toHaveBeenCalledOnceWith('some-par-url'); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-par-url'); expect(redirectToSpy).not.toHaveBeenCalled(); - })); + }); }); describe('loginWithPopUpPar', () => { - it('does nothing if it has an invalid response type', waitForAsync(() => { - spyOn( + it('does nothing if it has an invalid response type', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(false); - const loggerSpy = spyOn(loggerService, 'logError'); + ).mockReturnValue(false); + const loggerSpy = vi.spyOn(loggerService, 'logError'); const config = {}; const allConfigs = [config]; @@ -229,27 +234,27 @@ describe('ParLoginService', () => { expect(err.message).toBe('Invalid response type!'); }, }); - })); + }); - it('calls parService.postParRequest without custom params when no custom params are passed', waitForAsync(() => { - spyOn( + it('calls parService.postParRequest without custom params when no custom params are passed', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; const allConfigs = [config]; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - const spy = spyOn(parService, 'postParRequest').and.returnValue( - of({ requestUri: 'requestUri' } as ParResponse) - ); + const spy = vi + .spyOn(parService, 'postParRequest') + .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); service.loginWithPopUpPar(config, allConfigs).subscribe({ error: (err) => { @@ -259,27 +264,27 @@ describe('ParLoginService', () => { ); }, }); - })); + }); - it('calls parService.postParRequest with custom params when custom params are passed', waitForAsync(() => { - spyOn( + it('calls parService.postParRequest with custom params when custom params are passed', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; const allConfigs = [config]; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - const spy = spyOn(parService, 'postParRequest').and.returnValue( - of({ requestUri: 'requestUri' } as ParResponse) - ); + const spy = vi + .spyOn(parService, 'postParRequest') + .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); service .loginWithPopUpPar(config, allConfigs, { @@ -287,7 +292,7 @@ describe('ParLoginService', () => { }) .subscribe({ error: (err) => { - expect(spy).toHaveBeenCalledOnceWith(config, { + expect(spy).toHaveBeenCalledExactlyOnceWith(config, { customParams: { some: 'thing' }, }); expect(err.message).toBe( @@ -295,29 +300,29 @@ describe('ParLoginService', () => { ); }, }); - })); + }); - it('returns undefined and logs error when no URL could be created', waitForAsync(() => { - spyOn( + it('returns undefined and logs error when no URL could be created', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; const allConfigs = [config]; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn(parService, 'postParRequest').and.returnValue( + vi.spyOn(parService, 'postParRequest').mockReturnValue( of({ requestUri: 'requestUri' } as ParResponse) ); - spyOn(urlService, 'getAuthorizeParUrl').and.returnValue(''); - const spy = spyOn(loggerService, 'logError'); + vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(''); + const spy = vi.spyOn(loggerService, 'logError'); service .loginWithPopUpPar(config, allConfigs, { @@ -331,46 +336,52 @@ describe('ParLoginService', () => { expect(spy).toHaveBeenCalledTimes(1); }, }); - })); + }); - it('calls popupService openPopUp when URL could be created', waitForAsync(() => { - spyOn( + it('calls popupService openPopUp when URL could be created', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; const allConfigs = [config]; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn(parService, 'postParRequest').and.returnValue( + vi.spyOn(parService, 'postParRequest').mockReturnValue( of({ requestUri: 'requestUri' } as ParResponse) ); - spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); - spyOn(checkAuthService, 'checkAuth').and.returnValue( + vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue( + 'some-par-url' + ); + vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue( of({} as LoginResponse) ); - spyOnProperty(popupService, 'result$').and.returnValue( + vi.spyOnProperty(popupService, 'result$').mockReturnValue( of({} as PopupResult) ); - const spy = spyOn(popupService, 'openPopUp'); + const spy = vi.spyOn(popupService, 'openPopUp'); service.loginWithPopUpPar(config, allConfigs).subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith('some-par-url', undefined, config); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'some-par-url', + undefined, + config + ); }); - })); + }); - it('returns correct properties if URL is received', waitForAsync(() => { - spyOn( + it('returns correct properties if URL is received', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', @@ -378,34 +389,40 @@ describe('ParLoginService', () => { }; const allConfigs = [config]; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn(parService, 'postParRequest').and.returnValue( + vi.spyOn(parService, 'postParRequest').mockReturnValue( of({ requestUri: 'requestUri' } as ParResponse) ); - spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); - - const checkAuthSpy = spyOn(checkAuthService, 'checkAuth').and.returnValue( - of({ - isAuthenticated: true, - configId: 'configId1', - idToken: '', - userData: { any: 'userData' }, - accessToken: 'anyAccessToken', - }) + vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue( + 'some-par-url' ); + + const checkAuthSpy = vi + .spyOn(checkAuthService, 'checkAuth') + .mockReturnValue( + of({ + isAuthenticated: true, + configId: 'configId1', + idToken: '', + userData: { any: 'userData' }, + accessToken: 'anyAccessToken', + }) + ); const popupResult: PopupResult = { userClosed: false, receivedUrl: 'someUrl', }; - spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult)); + vi.spyOnProperty(popupService, 'result$').mockReturnValue( + of(popupResult) + ); service.loginWithPopUpPar(config, allConfigs).subscribe((result) => { - expect(checkAuthSpy).toHaveBeenCalledOnceWith( + expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith( config, allConfigs, 'someUrl' @@ -419,13 +436,13 @@ describe('ParLoginService', () => { accessToken: 'anyAccessToken', }); }); - })); + }); - it('returns correct properties if popup was closed by user', waitForAsync(() => { - spyOn( + it('returns correct properties if popup was closed by user', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); + ).mockReturnValue(true); const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', @@ -433,20 +450,24 @@ describe('ParLoginService', () => { }; const allConfigs = [config]; - spyOn( + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + ).mockReturnValue(of({})); - spyOn(parService, 'postParRequest').and.returnValue( + vi.spyOn(parService, 'postParRequest').mockReturnValue( of({ requestUri: 'requestUri' } as ParResponse) ); - spyOn(urlService, 'getAuthorizeParUrl').and.returnValue('some-par-url'); + vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue( + 'some-par-url' + ); - const checkAuthSpy = spyOn(checkAuthService, 'checkAuth'); + const checkAuthSpy = vi.spyOn(checkAuthService, 'checkAuth'); const popupResult = { userClosed: true } as PopupResult; - spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult)); + vi.spyOnProperty(popupService, 'result$').mockReturnValue( + of(popupResult) + ); service.loginWithPopUpPar(config, allConfigs).subscribe((result) => { expect(checkAuthSpy).not.toHaveBeenCalled(); @@ -459,6 +480,6 @@ describe('ParLoginService', () => { accessToken: '', }); }); - })); + }); }); }); diff --git a/src/login/par/par-login.service.ts b/src/login/par/par-login.service.ts index 8d34d9a..7019d35 100644 --- a/src/login/par/par-login.service.ts +++ b/src/login/par/par-login.service.ts @@ -1,19 +1,19 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable, of, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, of, throwError } from 'rxjs'; import { switchMap, take } from 'rxjs/operators'; -import { AuthOptions } from '../../auth-options'; +import type { AuthOptions } from '../../auth-options'; import { CheckAuthService } from '../../auth-state/check-auth.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { RedirectService } from '../../utils/redirect/redirect.service'; import { UrlService } from '../../utils/url/url.service'; -import { LoginResponse } from '../login-response'; -import { PopupOptions } from '../popup/popup-options'; -import { PopupResult } from '../popup/popup-result'; +import type { LoginResponse } from '../login-response'; +import type { PopupOptions } from '../popup/popup-options'; +import type { PopupResult } from '../popup/popup-result'; import { PopUpService } from '../popup/popup.service'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; -import { ParResponse } from './par-response'; +import type { ParResponse } from './par-response'; import { ParService } from './par.service'; @Injectable() diff --git a/src/login/par/par.service.spec.ts b/src/login/par/par.service.spec.ts index 7a222d7..4e1faed 100644 --- a/src/login/par/par.service.spec.ts +++ b/src/login/par/par.service.spec.ts @@ -1,11 +1,12 @@ -import { HttpHeaders } from '@angular/common/http'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; +import { HttpHeaders } from '@ngify/http'; import { of, throwError } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; -import { createRetriableStream } from '../../../test/create-retriable-stream.helper'; +import { vi } from 'vitest'; import { DataService } from '../../api/data.service'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; +import { createRetriableStream } from '../../testing/create-retriable-stream.helper'; +import { mockProvider } from '../../testing/mock'; import { UrlService } from '../../utils/url/url.service'; import { ParService } from './par.service'; @@ -40,13 +41,15 @@ describe('ParService', () => { }); describe('postParRequest', () => { - it('throws error if authWellKnownEndPoints does not exist in storage', waitForAsync(() => { - spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( + it('throws error if authWellKnownEndPoints does not exist in storage', async () => { + vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue( of(null) ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue(null); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => null + ); service.postParRequest({ configId: 'configId1' }).subscribe({ error: (err) => { expect(err.message).toBe( @@ -54,15 +57,17 @@ describe('ParService', () => { ); }, }); - })); + }); - it('throws error if par endpoint does not exist in storage', waitForAsync(() => { - spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( + it('throws error if par endpoint does not exist in storage', async () => { + vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue( of(null) ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ some: 'thing' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ some: 'thing' }) + ); service.postParRequest({ configId: 'configId1' }).subscribe({ error: (err) => { expect(err.message).toBe( @@ -70,77 +75,87 @@ describe('ParService', () => { ); }, }); - })); + }); - it('calls data service with correct params', waitForAsync(() => { - spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( + it('calls data service with correct params', async () => { + vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue( of('some-url123') ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ parEndpoint: 'parEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ parEndpoint: 'parEndpoint' }) + ); - const dataServiceSpy = spyOn(dataService, 'post').and.returnValue(of({})); + const dataServiceSpy = vi + .spyOn(dataService, 'post') + .mockReturnValue(of({})); service.postParRequest({ configId: 'configId1' }).subscribe(() => { - expect(dataServiceSpy).toHaveBeenCalledOnceWith( + expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith( 'parEndpoint', 'some-url123', { configId: 'configId1' }, - jasmine.any(HttpHeaders) + expect.any(HttpHeaders) ); }); - })); + }); - it('Gives back correct object properties', waitForAsync(() => { - spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( + it('Gives back correct object properties', async () => { + vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue( of('some-url456') ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ parEndpoint: 'parEndpoint' }); - spyOn(dataService, 'post').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ parEndpoint: 'parEndpoint' }) + ); + vi.spyOn(dataService, 'post').mockReturnValue( of({ expires_in: 123, request_uri: 'request_uri' }) ); service.postParRequest({ configId: 'configId1' }).subscribe((result) => { expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); }); - })); + }); - it('throws error if data service has got an error', waitForAsync(() => { - spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( + it('throws error if data service has got an error', async () => { + vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue( of('some-url789') ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ parEndpoint: 'parEndpoint' }); - spyOn(dataService, 'post').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ parEndpoint: 'parEndpoint' }) + ); + vi.spyOn(dataService, 'post').mockReturnValue( throwError(() => new Error('ERROR')) ); - const loggerSpy = spyOn(loggerService, 'logError'); + const loggerSpy = vi.spyOn(loggerService, 'logError'); service.postParRequest({ configId: 'configId1' }).subscribe({ error: (err) => { expect(err.message).toBe( 'There was an error on ParService postParRequest' ); - expect(loggerSpy).toHaveBeenCalledOnceWith( + expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'There was an error on ParService postParRequest', - jasmine.any(Error) + expect.any(Error) ); }, }); - })); + }); - it('should retry once', waitForAsync(() => { - spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( + it('should retry once', async () => { + vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue( of('some-url456') ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ parEndpoint: 'parEndpoint' }); - spyOn(dataService, 'post').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ parEndpoint: 'parEndpoint' }) + ); + vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => new Error('ERROR')), of({ expires_in: 123, request_uri: 'request_uri' }) @@ -153,16 +168,18 @@ describe('ParService', () => { expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); }, }); - })); + }); - it('should retry twice', waitForAsync(() => { - spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( + it('should retry twice', async () => { + vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue( of('some-url456') ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ parEndpoint: 'parEndpoint' }); - spyOn(dataService, 'post').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ parEndpoint: 'parEndpoint' }) + ); + vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => new Error('ERROR')), throwError(() => new Error('ERROR')), @@ -176,16 +193,18 @@ describe('ParService', () => { expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); }, }); - })); + }); - it('should fail after three tries', waitForAsync(() => { - spyOn(urlService, 'createBodyForParCodeFlowRequest').and.returnValue( + it('should fail after three tries', async () => { + vi.spyOn(urlService, 'createBodyForParCodeFlowRequest').mockReturnValue( of('some-url456') ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ parEndpoint: 'parEndpoint' }); - spyOn(dataService, 'post').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ parEndpoint: 'parEndpoint' }) + ); + vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => new Error('ERROR')), throwError(() => new Error('ERROR')), @@ -199,6 +218,6 @@ describe('ParService', () => { expect(err).toBeTruthy(); }, }); - })); + }); }); }); diff --git a/src/login/popup/popup-login.service.spec.ts b/src/login/popup/popup-login.service.spec.ts index 9d8116e..ee27439 100644 --- a/src/login/popup/popup-login.service.spec.ts +++ b/src/login/popup/popup-login.service.spec.ts @@ -1,15 +1,16 @@ +import { TestBed } from '@/testing'; import { CommonModule } from '@angular/common'; -import { TestBed, waitForAsync } from '@angular/core/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; +import { vi } from 'vitest'; import { CheckAuthService } from '../../auth-state/check-auth.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { LoggerService } from '../../logging/logger.service'; +import { mockProvider } from '../../testing/mock'; import { UrlService } from '../../utils/url/url.service'; -import { LoginResponse } from '../login-response'; +import type { LoginResponse } from '../login-response'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; import { PopUpLoginService } from './popup-login.service'; -import { PopupResult } from './popup-result'; +import type { PopupResult } from './popup-result'; import { PopUpService } from './popup.service'; describe('PopUpLoginService', () => { @@ -53,14 +54,14 @@ describe('PopUpLoginService', () => { }); describe('loginWithPopUpStandard', () => { - it('does nothing if it has an invalid response type', waitForAsync(() => { + it('does nothing if it has an invalid response type', async () => { const config = { responseType: 'stubValue' }; - spyOn( + vi.spyOn( responseTypValidationService, 'hasConfigValidResponseType' - ).and.returnValue(false); - const loggerSpy = spyOn(loggerService, 'logError'); + ).mockReturnValue(false); + const loggerSpy = vi.spyOn(loggerService, 'logError'); popUpLoginService.loginWithPopUpStandard(config, [config]).subscribe({ error: (err) => { @@ -68,27 +69,27 @@ describe('PopUpLoginService', () => { expect(err.message).toBe('Invalid response type!'); }, }); - })); + }); - it('calls urlService.getAuthorizeUrl() if everything fits', waitForAsync(() => { + it('calls urlService.getAuthorizeUrl() if everything fits', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - spyOnProperty(popupService, 'result$').and.returnValue( + ).mockReturnValue(of({})); + vi.spyOnProperty(popupService, 'result$').mockReturnValue( of({} as PopupResult) ); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); - spyOn(checkAuthService, 'checkAuth').and.returnValue( + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); + vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue( of({} as LoginResponse) ); @@ -97,74 +98,78 @@ describe('PopUpLoginService', () => { .subscribe(() => { expect(urlService.getAuthorizeUrl).toHaveBeenCalled(); }); - })); + }); - it('opens popup if everything fits', waitForAsync(() => { + it('opens popup if everything fits', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); - spyOnProperty(popupService, 'result$').and.returnValue( + ).mockReturnValue(of({})); + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); + vi.spyOnProperty(popupService, 'result$').mockReturnValue( of({} as PopupResult) ); - spyOn(checkAuthService, 'checkAuth').and.returnValue( + vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue( of({} as LoginResponse) ); - const popupSpy = spyOn(popupService, 'openPopUp'); + const popupSpy = vi.spyOn(popupService, 'openPopUp'); popUpLoginService .loginWithPopUpStandard(config, [config]) .subscribe(() => { expect(popupSpy).toHaveBeenCalled(); }); - })); + }); - it('returns three properties when popupservice received an url', waitForAsync(() => { + it('returns three properties when popupservice received an url', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); - spyOn(popupService, 'openPopUp'); - const checkAuthSpy = spyOn(checkAuthService, 'checkAuth').and.returnValue( - of({ - isAuthenticated: true, - configId: 'configId1', - idToken: '', - userData: { any: 'userData' }, - accessToken: 'anyAccessToken', - }) - ); + ).mockReturnValue(of({})); + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); + vi.spyOn(popupService, 'openPopUp'); + const checkAuthSpy = vi + .spyOn(checkAuthService, 'checkAuth') + .mockReturnValue( + of({ + isAuthenticated: true, + configId: 'configId1', + idToken: '', + userData: { any: 'userData' }, + accessToken: 'anyAccessToken', + }) + ); const popupResult: PopupResult = { userClosed: false, receivedUrl: 'someUrl', }; - spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult)); + vi.spyOnProperty(popupService, 'result$').mockReturnValue( + of(popupResult) + ); popUpLoginService .loginWithPopUpStandard(config, [config]) .subscribe((result) => { - expect(checkAuthSpy).toHaveBeenCalledOnceWith( + expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith( config, [config], 'someUrl' @@ -178,31 +183,33 @@ describe('PopUpLoginService', () => { accessToken: 'anyAccessToken', }); }); - })); + }); - it('returns two properties if popup was closed by user', waitForAsync(() => { + it('returns two properties if popup was closed by user', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', configId: 'configId1', }; - spyOn( + vi.spyOn( responseTypValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); - spyOn(popupService, 'openPopUp'); - const checkAuthSpy = spyOn(checkAuthService, 'checkAuth').and.returnValue( - of({} as LoginResponse) - ); + ).mockReturnValue(of({})); + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); + vi.spyOn(popupService, 'openPopUp'); + const checkAuthSpy = vi + .spyOn(checkAuthService, 'checkAuth') + .mockReturnValue(of({} as LoginResponse)); const popupResult = { userClosed: true } as PopupResult; - spyOnProperty(popupService, 'result$').and.returnValue(of(popupResult)); + vi.spyOnProperty(popupService, 'result$').mockReturnValue( + of(popupResult) + ); popUpLoginService .loginWithPopUpStandard(config, [config]) @@ -217,6 +224,6 @@ describe('PopUpLoginService', () => { accessToken: '', }); }); - })); + }); }); }); diff --git a/src/login/popup/popup-login.service.ts b/src/login/popup/popup-login.service.ts index 9a47674..c6411df 100644 --- a/src/login/popup/popup-login.service.ts +++ b/src/login/popup/popup-login.service.ts @@ -1,16 +1,16 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable, of, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, of, throwError } from 'rxjs'; import { switchMap, take, tap } from 'rxjs/operators'; -import { AuthOptions } from '../../auth-options'; +import type { AuthOptions } from '../../auth-options'; import { CheckAuthService } from '../../auth-state/check-auth.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { UrlService } from '../../utils/url/url.service'; -import { LoginResponse } from '../login-response'; +import type { LoginResponse } from '../login-response'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; -import { PopupOptions } from './popup-options'; -import { PopupResult } from './popup-result'; +import type { PopupOptions } from './popup-options'; +import type { PopupResult } from './popup-result'; import { PopUpService } from './popup.service'; @Injectable() diff --git a/src/login/popup/popup.service.spec.ts b/src/login/popup/popup.service.spec.ts index 27d714e..d28810a 100644 --- a/src/login/popup/popup.service.spec.ts +++ b/src/login/popup/popup.service.spec.ts @@ -1,9 +1,10 @@ -import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; -import { mockProvider } from '../../../test/auto-mock'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import { TestBed, fakeAsync, tick } from '@/testing'; +import { vi } from 'vitest'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; -import { PopupResult } from './popup-result'; +import { mockProvider } from '../../testing/mock'; +import type { PopupResult } from './popup-result'; import { PopUpService } from './popup.service'; describe('PopUpService', () => { @@ -18,9 +19,6 @@ describe('PopUpService', () => { mockProvider(LoggerService), ], }); - }); - - beforeEach(() => { storagePersistenceService = TestBed.inject(StoragePersistenceService); loggerService = TestBed.inject(LoggerService); popUpService = TestBed.inject(PopUpService); @@ -51,13 +49,13 @@ describe('PopUpService', () => { describe('isCurrentlyInPopup', () => { it('returns false if can not access Session Storage', () => { // arrange - spyOn(popUpService as any, 'canAccessSessionStorage').and.returnValue( + vi.spyOn(popUpService as any, 'canAccessSessionStorage').mockReturnValue( false ); - spyOnProperty(popUpService as any, 'windowInternal').and.returnValue({ + vi.spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({ opener: {} as Window, }); - spyOn(storagePersistenceService, 'read').and.returnValue({ + vi.spyOn(storagePersistenceService, 'read').mockReturnValue({ popupauth: true, }); const config = {} as OpenIdConfiguration; @@ -71,10 +69,10 @@ describe('PopUpService', () => { it('returns false if window has no opener', () => { // arrange - spyOn(popUpService as any, 'canAccessSessionStorage').and.returnValue( + vi.spyOn(popUpService as any, 'canAccessSessionStorage').mockReturnValue( true ); - spyOn(storagePersistenceService, 'read').and.returnValue({ + vi.spyOn(storagePersistenceService, 'read').mockReturnValue({ popupauth: true, }); const config = {} as OpenIdConfiguration; @@ -88,13 +86,13 @@ describe('PopUpService', () => { it('returns true if isCurrentlyInPopup', () => { // arrange - spyOn(popUpService as any, 'canAccessSessionStorage').and.returnValue( + vi.spyOn(popUpService as any, 'canAccessSessionStorage').mockReturnValue( true ); - spyOnProperty(popUpService as any, 'windowInternal').and.returnValue({ + vi.spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({ opener: {} as Window, }); - spyOn(storagePersistenceService, 'read').and.returnValue({ + vi.spyOn(storagePersistenceService, 'read').mockReturnValue({ popupauth: true, }); const config = {} as OpenIdConfiguration; @@ -108,7 +106,7 @@ describe('PopUpService', () => { }); describe('result$', () => { - it('emits when internal subject is called', waitForAsync(() => { + it('emits when internal subject is called', async () => { const popupResult: PopupResult = { userClosed: false, receivedUrl: 'some-url1111', @@ -119,62 +117,62 @@ describe('PopUpService', () => { }); (popUpService as any).resultInternal$.next(popupResult); - })); + }); }); describe('openPopup', () => { - it('popup opens with parameters and default options', waitForAsync(() => { + it('popup opens with parameters and default options', async () => { // arrange - const popupSpy = spyOn(window, 'open').and.callFake( + const popupSpy = vi.spyOn(window, 'open').and.callFake( () => ({ closed: true, close: () => undefined, - } as Window) + }) as Window ); // act popUpService.openPopUp('url', {}, { configId: 'configId1' }); // assert - expect(popupSpy).toHaveBeenCalledOnceWith( + expect(popupSpy).toHaveBeenCalledExactlyOnceWith( 'url', '_blank', - jasmine.any(String) + expect.any(String) ); - })); + }); - it('popup opens with parameters and passed options', waitForAsync(() => { + it('popup opens with parameters and passed options', async () => { // arrange - const popupSpy = spyOn(window, 'open').and.callFake( + const popupSpy = vi.spyOn(window, 'open').and.callFake( () => ({ closed: true, close: () => undefined, - } as Window) + }) as Window ); // act popUpService.openPopUp('url', { width: 100 }, { configId: 'configId1' }); // assert - expect(popupSpy).toHaveBeenCalledOnceWith( + expect(popupSpy).toHaveBeenCalledExactlyOnceWith( 'url', '_blank', - jasmine.any(String) + expect.any(String) ); - })); + }); it('logs error and return if popup could not be opened', () => { // arrange - spyOn(window, 'open').and.callFake(() => null); - const loggerSpy = spyOn(loggerService, 'logError'); + vi.spyOn(window, 'open').mockImplementation(() => null); + const loggerSpy = vi.spyOn(loggerService, 'logError'); // act popUpService.openPopUp('url', { width: 100 }, { configId: 'configId1' }); // assert - expect(loggerSpy).toHaveBeenCalledOnceWith( + expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( { configId: 'configId1' }, 'Could not open popup' ); @@ -191,21 +189,21 @@ describe('PopUpService', () => { close: () => undefined, } as Window; - spyOn(window, 'open').and.returnValue(popup); + vi.spyOn(window, 'open').mockReturnValue(popup); - cleanUpSpy = spyOn(popUpService as any, 'cleanUp').and.callThrough(); + cleanUpSpy = vi.spyOn(popUpService as any, 'cleanUp')(); popupResult = {} as PopupResult; popUpService.result$.subscribe((result) => (popupResult = result)); }); - it('message received with data', fakeAsync(() => { + it('message received with data', async () => { let listener: (event: MessageEvent) => void = () => { return; }; - spyOn(window, 'addEventListener').and.callFake( + vi.spyOn(window, 'addEventListener').and.callFake( (_: any, func: any) => (listener = func) ); @@ -222,20 +220,20 @@ describe('PopUpService', () => { userClosed: false, receivedUrl: 'some-url1111', }); - expect(cleanUpSpy).toHaveBeenCalledOnceWith(listener, { + expect(cleanUpSpy).toHaveBeenCalledExactlyOnceWith(listener, { configId: 'configId1', }); - })); + }); - it('message received without data does return but cleanup does not throw event', fakeAsync(() => { + it('message received without data does return but cleanup does not throw event', async () => { let listener: (event: MessageEvent) => void = () => { return; }; - spyOn(window, 'addEventListener').and.callFake( + vi.spyOn(window, 'addEventListener').and.callFake( (_: any, func: any) => (listener = func) ); - const nextSpy = spyOn((popUpService as any).resultInternal$, 'next'); + const nextSpy = vi.spyOn((popUpService as any).resultInternal$, 'next'); popUpService.openPopUp('url', {}, { configId: 'configId1' }); @@ -249,9 +247,9 @@ describe('PopUpService', () => { expect(popupResult).toEqual({} as PopupResult); expect(cleanUpSpy).toHaveBeenCalled(); expect(nextSpy).not.toHaveBeenCalled(); - })); + }); - it('user closed', fakeAsync(() => { + it('user closed', async () => { popUpService.openPopUp('url', undefined, { configId: 'configId1' }); expect(popupResult).toEqual({} as PopupResult); @@ -266,48 +264,48 @@ describe('PopUpService', () => { receivedUrl: '', } as PopupResult); expect(cleanUpSpy).toHaveBeenCalled(); - })); + }); }); }); describe('sendMessageToMainWindow', () => { - it('does nothing if window.opener is null', waitForAsync(() => { + it('does nothing if window.opener is null', async () => { // arrange - spyOnProperty(window, 'opener').and.returnValue(null); + vi.spyOnProperty(window, 'opener').mockReturnValue(null); - const sendMessageSpy = spyOn(popUpService as any, 'sendMessage'); + const sendMessageSpy = vi.spyOn(popUpService as any, 'sendMessage'); // act popUpService.sendMessageToMainWindow('', {}); // assert expect(sendMessageSpy).not.toHaveBeenCalled(); - })); + }); - it('calls postMessage when window opener is given', waitForAsync(() => { + it('calls postMessage when window opener is given', async () => { // arrange - spyOnProperty(window, 'opener').and.returnValue({ + vi.spyOnProperty(window, 'opener').mockReturnValue({ postMessage: () => undefined, }); - const sendMessageSpy = spyOn(window.opener, 'postMessage'); + const sendMessageSpy = vi.spyOn(window.opener, 'postMessage'); // act popUpService.sendMessageToMainWindow('someUrl', {}); // assert - expect(sendMessageSpy).toHaveBeenCalledOnceWith( + expect(sendMessageSpy).toHaveBeenCalledExactlyOnceWith( 'someUrl', - jasmine.any(String) + expect.any(String) ); - })); + }); }); describe('cleanUp', () => { - it('calls removeEventListener on window with correct params', waitForAsync(() => { + it('calls removeEventListener on window with correct params', async () => { // arrange - const spy = spyOn(window, 'removeEventListener').and.callFake( - () => undefined - ); + const spy = vi + .spyOn(window, 'removeEventListener') + .mockImplementation(() => undefined); const listener: any = null; // act @@ -315,29 +313,29 @@ describe('PopUpService', () => { // assert expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledOnceWith('message', listener, false); - })); + expect(spy).toHaveBeenCalledExactlyOnceWith('message', listener, false); + }); - it('removes popup from sessionstorage, closes and nulls when popup is opened', waitForAsync(() => { + it('removes popup from sessionstorage, closes and nulls when popup is opened', async () => { // arrange const popupMock = { anyThing: 'truthy', sessionStorage: mockStorage, close: (): void => undefined, }; - const removeItemSpy = spyOn(storagePersistenceService, 'remove'); - const closeSpy = spyOn(popupMock, 'close'); + const removeItemSpy = vi.spyOn(storagePersistenceService, 'remove'); + const closeSpy = vi.spyOn(popupMock, 'close'); // act (popUpService as any).popUp = popupMock; (popUpService as any).cleanUp(null, { configId: 'configId1' }); // assert - expect(removeItemSpy).toHaveBeenCalledOnceWith('popupauth', { + expect(removeItemSpy).toHaveBeenCalledExactlyOnceWith('popupauth', { configId: 'configId1', }); expect(closeSpy).toHaveBeenCalledTimes(1); expect((popUpService as any).popUp).toBeNull(); - })); + }); }); }); diff --git a/src/login/response-type-validation/response-type-validation.service.spec.ts b/src/login/response-type-validation/response-type-validation.service.spec.ts index 679841c..d3e0583 100644 --- a/src/login/response-type-validation/response-type-validation.service.spec.ts +++ b/src/login/response-type-validation/response-type-validation.service.spec.ts @@ -1,6 +1,7 @@ -import { TestBed } from '@angular/core/testing'; -import { mockProvider } from '../../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { LoggerService } from '../../logging/logger.service'; +import { mockProvider } from '../../testing/mock'; import { FlowHelper } from '../../utils/flowHelper/flow-helper.service'; import { ResponseTypeValidationService } from './response-type-validation.service'; @@ -32,7 +33,9 @@ describe('ResponseTypeValidationService', () => { describe('hasConfigValidResponseType', () => { it('returns true if current configured flow is any implicit flow', () => { - spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true); + vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue( + true + ); const result = responseTypeValidationService.hasConfigValidResponseType({ configId: 'configId1', @@ -42,8 +45,10 @@ describe('ResponseTypeValidationService', () => { }); it('returns true if current configured flow is code flow', () => { - spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(false); - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); + vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue( + false + ); + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); const result = responseTypeValidationService.hasConfigValidResponseType({ configId: 'configId1', @@ -53,8 +58,10 @@ describe('ResponseTypeValidationService', () => { }); it('returns false if current configured flow is neither code nor implicit flow', () => { - spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(false); - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); + vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue( + false + ); + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); const result = responseTypeValidationService.hasConfigValidResponseType({ configId: 'configId1', diff --git a/src/login/standard/standard-login.service.spec.ts b/src/login/standard/standard-login.service.spec.ts index 4313ccc..f655eaf 100644 --- a/src/login/standard/standard-login.service.spec.ts +++ b/src/login/standard/standard-login.service.spec.ts @@ -1,9 +1,10 @@ -import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { TestBed, fakeAsync, tick } from '@/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; +import { vi } from 'vitest'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { FlowsDataService } from '../../flows/flows-data.service'; import { LoggerService } from '../../logging/logger.service'; +import { mockProvider } from '../../testing/mock'; import { RedirectService } from '../../utils/redirect/redirect.service'; import { UrlService } from '../../utils/url/url.service'; import { ResponseTypeValidationService } from '../response-type-validation/response-type-validation.service'; @@ -51,12 +52,12 @@ describe('StandardLoginService', () => { }); describe('loginStandard', () => { - it('does nothing if it has an invalid response type', waitForAsync(() => { - spyOn( + it('does nothing if it has an invalid response type', async () => { + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(false); - const loggerSpy = spyOn(loggerService, 'logError'); + ).mockReturnValue(false); + const loggerSpy = vi.spyOn(loggerService, 'logError'); const result = standardLoginService.loginStandard({ configId: 'configId1', @@ -64,95 +65,92 @@ describe('StandardLoginService', () => { expect(result).toBeUndefined(); expect(loggerSpy).toHaveBeenCalled(); - })); + }); - it('calls flowsDataService.setCodeFlowInProgress() if everything fits', waitForAsync(() => { + it('calls flowsDataService.setCodeFlowInProgress() if everything fits', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); - const flowsDataSpy = spyOn(flowsDataService, 'setCodeFlowInProgress'); + ).mockReturnValue(of({})); + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); + const flowsDataSpy = vi.spyOn(flowsDataService, 'setCodeFlowInProgress'); const result = standardLoginService.loginStandard(config); expect(result).toBeUndefined(); expect(flowsDataSpy).toHaveBeenCalled(); - })); + }); - it('calls urlService.getAuthorizeUrl() if everything fits', waitForAsync(() => { + it('calls urlService.getAuthorizeUrl() if everything fits', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); + ).mockReturnValue(of({})); + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); const result = standardLoginService.loginStandard(config); expect(result).toBeUndefined(); - })); + }); - it('redirects to URL with no URL handler', fakeAsync(() => { + it('redirects to URL with no URL handler', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); - const redirectSpy = spyOn( - redirectService, - 'redirectTo' - ).and.callThrough(); + ).mockReturnValue(of({})); + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); + const redirectSpy = vi.spyOn(redirectService, 'redirectTo')(); standardLoginService.loginStandard(config); tick(); - expect(redirectSpy).toHaveBeenCalledOnceWith('someUrl'); - })); + expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl'); + }); - it('redirects to URL with URL handler when urlHandler is given', fakeAsync(() => { + it('redirects to URL with URL handler when urlHandler is given', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); - const redirectSpy = spyOn(redirectService, 'redirectTo').and.callFake( - () => undefined - ); + ).mockReturnValue(of({})); + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); + const redirectSpy = vi + .spyOn(redirectService, 'redirectTo') + .mockImplementation(() => undefined); const spy = jasmine.createSpy(); const urlHandler = (url: any): void => { spy(url); @@ -160,94 +158,96 @@ describe('StandardLoginService', () => { standardLoginService.loginStandard(config, { urlHandler }); tick(); - expect(spy).toHaveBeenCalledOnceWith('someUrl'); + expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl'); expect(redirectSpy).not.toHaveBeenCalled(); - })); + }); - it('calls resetSilentRenewRunning', fakeAsync(() => { + it('calls resetSilentRenewRunning', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('someUrl')); - const flowsDataSpy = spyOn(flowsDataService, 'resetSilentRenewRunning'); + ).mockReturnValue(of({})); + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); + const flowsDataSpy = vi.spyOn( + flowsDataService, + 'resetSilentRenewRunning' + ); standardLoginService.loginStandard(config, {}); tick(); expect(flowsDataSpy).toHaveBeenCalled(); - })); + }); - it('calls getAuthorizeUrl with custom params if they are given as parameter', fakeAsync(() => { + it('calls getAuthorizeUrl with custom params if they are given as parameter', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - const getAuthorizeUrlSpy = spyOn( - urlService, - 'getAuthorizeUrl' - ).and.returnValue(of('someUrl')); - const redirectSpy = spyOn(redirectService, 'redirectTo').and.callFake( - () => undefined - ); + ).mockReturnValue(of({})); + const getAuthorizeUrlSpy = vi + .spyOn(urlService, 'getAuthorizeUrl') + .mockReturnValue(of('someUrl')); + const redirectSpy = vi + .spyOn(redirectService, 'redirectTo') + .mockImplementation(() => undefined); standardLoginService.loginStandard(config, { customParams: { to: 'add', as: 'well' }, }); tick(); - expect(redirectSpy).toHaveBeenCalledOnceWith('someUrl'); - expect(getAuthorizeUrlSpy).toHaveBeenCalledOnceWith(config, { + expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl'); + expect(getAuthorizeUrlSpy).toHaveBeenCalledExactlyOnceWith(config, { customParams: { to: 'add', as: 'well' }, }); - })); + }); - it('does nothing, logs only if getAuthorizeUrl returns falsy', fakeAsync(() => { + it('does nothing, logs only if getAuthorizeUrl returns falsy', async () => { const config = { authWellknownEndpointUrl: 'authWellknownEndpoint', responseType: 'stubValue', }; - spyOn( + vi.spyOn( responseTypeValidationService, 'hasConfigValidResponseType' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); - const loggerSpy = spyOn(loggerService, 'logError'); + ).mockReturnValue(of({})); + const loggerSpy = vi.spyOn(loggerService, 'logError'); - spyOn(urlService, 'getAuthorizeUrl').and.returnValue(of('')); - const redirectSpy = spyOn(redirectService, 'redirectTo').and.callFake( - () => undefined - ); + vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('')); + const redirectSpy = vi + .spyOn(redirectService, 'redirectTo') + .mockImplementation(() => undefined); standardLoginService.loginStandard(config); tick(); - expect(loggerSpy).toHaveBeenCalledOnceWith( + expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( config, 'Could not create URL', '' ); expect(redirectSpy).not.toHaveBeenCalled(); - })); + }); }); }); diff --git a/src/logoff-revoke/logoff-revocation.service.spec.ts b/src/logoff-revoke/logoff-revocation.service.spec.ts index 94d6299..2601739 100644 --- a/src/logoff-revoke/logoff-revocation.service.spec.ts +++ b/src/logoff-revoke/logoff-revocation.service.spec.ts @@ -1,13 +1,14 @@ -import { HttpHeaders } from '@angular/common/http'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; +import type { HttpHeaders } from '@ngify/http'; import { Observable, of, throwError } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; -import { createRetriableStream } from '../../test/create-retriable-stream.helper'; +import { vi } from 'vitest'; import { DataService } from '../api/data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { CheckSessionService } from '../iframe/check-session.service'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { createRetriableStream } from '../testing/create-retriable-stream.helper'; +import { mockProvider } from '../testing/mock'; import { RedirectService } from '../utils/redirect/redirect.service'; import { UrlService } from '../utils/url/url.service'; import { LogoffRevocationService } from './logoff-revocation.service'; @@ -55,70 +56,70 @@ describe('Logout and Revoke Service', () => { it('uses token parameter if token as parameter is passed in the method', () => { // Arrange const paramToken = 'passedTokenAsParam'; - const revocationSpy = spyOn( + const revocationSpy = vi.spyOn( urlService, 'createRevocationEndpointBodyAccessToken' ); const config = { configId: 'configId1' }; - spyOn(dataService, 'post').and.returnValue(of(null)); + vi.spyOn(dataService, 'post').mockReturnValue(of(null)); // Act service.revokeAccessToken(config, paramToken); // Assert - expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config); + expect(revocationSpy).toHaveBeenCalledExactlyOnceWith(paramToken, config); }); it('uses token parameter from persistence if no param is provided', () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( paramToken ); - const revocationSpy = spyOn( + const revocationSpy = vi.spyOn( urlService, 'createRevocationEndpointBodyAccessToken' ); - spyOn(dataService, 'post').and.returnValue(of(null)); + vi.spyOn(dataService, 'post').mockReturnValue(of(null)); const config = { configId: 'configId1' }; // Act service.revokeAccessToken(config); // Assert - expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config); + expect(revocationSpy).toHaveBeenCalledExactlyOnceWith(paramToken, config); }); it('returns type observable', () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - spyOn(dataService, 'post').and.returnValue(of(null)); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + vi.spyOn(dataService, 'post').mockReturnValue(of(null)); const config = { configId: 'configId1' }; // Act const result = service.revokeAccessToken(config); // Assert - expect(result).toEqual(jasmine.any(Observable)); + expect(result).toEqual(expect.any(Observable)); }); - it('loggs and returns unmodified response if request is positive', waitForAsync(() => { + it('loggs and returns unmodified response if request is positive', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logDebug'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logDebug'); - spyOn(dataService, 'post').and.returnValue(of({ data: 'anything' })); + vi.spyOn(dataService, 'post').mockReturnValue(of({ data: 'anything' })); const config = { configId: 'configId1' }; // Act @@ -127,20 +128,20 @@ describe('Logout and Revoke Service', () => { expect(result).toEqual({ data: 'anything' }); expect(loggerSpy).toHaveBeenCalled(); }); - })); + }); - it('loggs error when request is negative', waitForAsync(() => { + it('loggs error when request is negative', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logError'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logError'); const config = { configId: 'configId1' }; - spyOn(dataService, 'post').and.returnValue( + vi.spyOn(dataService, 'post').mockReturnValue( throwError(() => new Error('Error')) ); @@ -151,20 +152,20 @@ describe('Logout and Revoke Service', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('should retry once', waitForAsync(() => { + it('should retry once', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logDebug'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logDebug'); const config = { configId: 'configId1' }; - spyOn(dataService, 'post').and.returnValue( + vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), of({ data: 'anything' }) @@ -179,20 +180,20 @@ describe('Logout and Revoke Service', () => { expect(loggerSpy).toHaveBeenCalled(); }, }); - })); + }); - it('should retry twice', waitForAsync(() => { + it('should retry twice', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logDebug'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logDebug'); const config = { configId: 'configId1' }; - spyOn(dataService, 'post').and.returnValue( + vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), throwError(() => new Error('Error')), @@ -208,20 +209,20 @@ describe('Logout and Revoke Service', () => { expect(loggerSpy).toHaveBeenCalled(); }, }); - })); + }); - it('should fail after three tries', waitForAsync(() => { + it('should fail after three tries', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logError'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logError'); const config = { configId: 'configId1' }; - spyOn(dataService, 'post').and.returnValue( + vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), throwError(() => new Error('Error')), @@ -236,76 +237,76 @@ describe('Logout and Revoke Service', () => { expect(loggerSpy).toHaveBeenCalled(); }, }); - })); + }); }); describe('revokeRefreshToken', () => { it('uses refresh token parameter if token as parameter is passed in the method', () => { // Arrange const paramToken = 'passedTokenAsParam'; - const revocationSpy = spyOn( + const revocationSpy = vi.spyOn( urlService, 'createRevocationEndpointBodyRefreshToken' ); - spyOn(dataService, 'post').and.returnValue(of(null)); + vi.spyOn(dataService, 'post').mockReturnValue(of(null)); const config = { configId: 'configId1' }; // Act service.revokeRefreshToken(config, paramToken); // Assert - expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config); + expect(revocationSpy).toHaveBeenCalledExactlyOnceWith(paramToken, config); }); it('uses refresh token parameter from persistence if no param is provided', () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); const config = { configId: 'configId1' }; - const revocationSpy = spyOn( + const revocationSpy = vi.spyOn( urlService, 'createRevocationEndpointBodyRefreshToken' ); - spyOn(dataService, 'post').and.returnValue(of(null)); + vi.spyOn(dataService, 'post').mockReturnValue(of(null)); // Act service.revokeRefreshToken(config); // Assert - expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config); + expect(revocationSpy).toHaveBeenCalledExactlyOnceWith(paramToken, config); }); it('returns type observable', () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - spyOn(dataService, 'post').and.returnValue(of(null)); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + vi.spyOn(dataService, 'post').mockReturnValue(of(null)); const config = { configId: 'configId1' }; // Act const result = service.revokeRefreshToken(config); // Assert - expect(result).toEqual(jasmine.any(Observable)); + expect(result).toEqual(expect.any(Observable)); }); - it('loggs and returns unmodified response if request is positive', waitForAsync(() => { + it('loggs and returns unmodified response if request is positive', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logDebug'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logDebug'); - spyOn(dataService, 'post').and.returnValue(of({ data: 'anything' })); + vi.spyOn(dataService, 'post').mockReturnValue(of({ data: 'anything' })); const config = { configId: 'configId1' }; // Act @@ -314,20 +315,20 @@ describe('Logout and Revoke Service', () => { expect(result).toEqual({ data: 'anything' }); expect(loggerSpy).toHaveBeenCalled(); }); - })); + }); - it('loggs error when request is negative', waitForAsync(() => { + it('loggs error when request is negative', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logError'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logError'); const config = { configId: 'configId1' }; - spyOn(dataService, 'post').and.returnValue( + vi.spyOn(dataService, 'post').mockReturnValue( throwError(() => new Error('Error')) ); @@ -338,20 +339,20 @@ describe('Logout and Revoke Service', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('should retry once', waitForAsync(() => { + it('should retry once', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logDebug'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logDebug'); const config = { configId: 'configId1' }; - spyOn(dataService, 'post').and.returnValue( + vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), of({ data: 'anything' }) @@ -366,20 +367,20 @@ describe('Logout and Revoke Service', () => { expect(loggerSpy).toHaveBeenCalled(); }, }); - })); + }); - it('should retry twice', waitForAsync(() => { + it('should retry twice', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logDebug'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logDebug'); const config = { configId: 'configId1' }; - spyOn(dataService, 'post').and.returnValue( + vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), throwError(() => new Error('Error')), @@ -395,20 +396,20 @@ describe('Logout and Revoke Service', () => { expect(loggerSpy).toHaveBeenCalled(); }, }); - })); + }); - it('should fail after three tries', waitForAsync(() => { + it('should fail after three tries', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); - const loggerSpy = spyOn(loggerService, 'logError'); + vi.spyOn(urlService, 'createRevocationEndpointBodyAccessToken'); + const loggerSpy = vi.spyOn(loggerService, 'logError'); const config = { configId: 'configId1' }; - spyOn(dataService, 'post').and.returnValue( + vi.spyOn(dataService, 'post').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), throwError(() => new Error('Error')), @@ -423,15 +424,15 @@ describe('Logout and Revoke Service', () => { expect(loggerSpy).toHaveBeenCalled(); }, }); - })); + }); }); describe('logoff', () => { - it('logs and returns if `endSessionUrl` is false', waitForAsync(() => { + it('logs and returns if `endSessionUrl` is false', async () => { // Arrange - spyOn(urlService, 'getEndSessionUrl').and.returnValue(''); + vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue(''); - const serverStateChangedSpy = spyOn( + const serverStateChangedSpy = vi.spyOn( checkSessionService, 'serverStateChanged' ); @@ -444,14 +445,14 @@ describe('Logout and Revoke Service', () => { result$.subscribe(() => { expect(serverStateChangedSpy).not.toHaveBeenCalled(); }); - })); + }); - it('logs and returns if `serverStateChanged` is true', waitForAsync(() => { + it('logs and returns if `serverStateChanged` is true', async () => { // Arrange - spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); - const redirectSpy = spyOn(redirectService, 'redirectTo'); + vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue'); + const redirectSpy = vi.spyOn(redirectService, 'redirectTo'); - spyOn(checkSessionService, 'serverStateChanged').and.returnValue(true); + vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue(true); const config = { configId: 'configId1' }; // Act @@ -461,22 +462,24 @@ describe('Logout and Revoke Service', () => { result$.subscribe(() => { expect(redirectSpy).not.toHaveBeenCalled(); }); - })); + }); - it('calls urlHandler if urlhandler is passed', waitForAsync(() => { + it('calls urlHandler if urlhandler is passed', async () => { // Arrange - spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); + vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue'); const spy = jasmine.createSpy(); const urlHandler = (url: string): void => { spy(url); }; - const redirectSpy = spyOn(redirectService, 'redirectTo'); - const resetAuthorizationDataSpy = spyOn( + const redirectSpy = vi.spyOn(redirectService, 'redirectTo'); + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); - spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); + vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue( + false + ); const config = { configId: 'configId1' }; // Act @@ -485,18 +488,20 @@ describe('Logout and Revoke Service', () => { // Assert result$.subscribe(() => { expect(redirectSpy).not.toHaveBeenCalled(); - expect(spy).toHaveBeenCalledOnceWith('someValue'); + expect(spy).toHaveBeenCalledExactlyOnceWith('someValue'); expect(resetAuthorizationDataSpy).toHaveBeenCalled(); }); - })); + }); - it('calls redirect service if no logoutOptions are passed', waitForAsync(() => { + it('calls redirect service if no logoutOptions are passed', async () => { // Arrange - spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); + vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue'); - const redirectSpy = spyOn(redirectService, 'redirectTo'); + const redirectSpy = vi.spyOn(redirectService, 'redirectTo'); - spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); + vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue( + false + ); const config = { configId: 'configId1' }; // Act @@ -504,17 +509,19 @@ describe('Logout and Revoke Service', () => { // Assert result$.subscribe(() => { - expect(redirectSpy).toHaveBeenCalledOnceWith('someValue'); + expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); }); - })); + }); - it('calls redirect service if logoutOptions are passed and method is GET', waitForAsync(() => { + it('calls redirect service if logoutOptions are passed and method is GET', async () => { // Arrange - spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); + vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue'); - const redirectSpy = spyOn(redirectService, 'redirectTo'); + const redirectSpy = vi.spyOn(redirectService, 'redirectTo'); - spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); + vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue( + false + ); const config = { configId: 'configId1' }; // Act @@ -522,28 +529,30 @@ describe('Logout and Revoke Service', () => { // Assert result$.subscribe(() => { - expect(redirectSpy).toHaveBeenCalledOnceWith('someValue'); + expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); }); - })); + }); - it('calls dataservice post if logoutOptions are passed and method is POST', waitForAsync(() => { + it('calls dataservice post if logoutOptions are passed and method is POST', async () => { // Arrange - spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); + vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue'); - const redirectSpy = spyOn(redirectService, 'redirectTo'); + const redirectSpy = vi.spyOn(redirectService, 'redirectTo'); - spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue( + false + ); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'id-token' ); - spyOn(urlService, 'getPostLogoutRedirectUrl').and.returnValue( + vi.spyOn(urlService, 'getPostLogoutRedirectUrl').mockReturnValue( 'post-logout-redirect-url' ); - spyOn(urlService, 'getEndSessionEndpoint').and.returnValue({ + vi.spyOn(urlService, 'getEndSessionEndpoint').mockReturnValue({ url: 'some-url', existingParams: '', }); - const postSpy = spyOn(dataService, 'post').and.returnValue(of(null)); + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of(null)); const config = { configId: 'configId1', clientId: 'clientId' }; // Act @@ -554,7 +563,7 @@ describe('Logout and Revoke Service', () => { // Assert result$.subscribe(() => { expect(redirectSpy).not.toHaveBeenCalled(); - expect(postSpy).toHaveBeenCalledOnceWith( + expect(postSpy).toHaveBeenCalledExactlyOnceWith( 'some-url', { id_token_hint: 'id-token', @@ -562,36 +571,38 @@ describe('Logout and Revoke Service', () => { post_logout_redirect_uri: 'post-logout-redirect-url', }, config, - jasmine.anything() + expect.anything() ); const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; - expect(httpHeaders.has('Content-Type')).toBeTrue(); + expect(httpHeaders.has('Content-Type')).toBeTruthy(); expect(httpHeaders.get('Content-Type')).toBe( 'application/x-www-form-urlencoded' ); }); - })); + }); - it('calls dataservice post if logoutOptions with customParams are passed and method is POST', waitForAsync(() => { + it('calls dataservice post if logoutOptions with customParams are passed and method is POST', async () => { // Arrange - spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); + vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue'); - const redirectSpy = spyOn(redirectService, 'redirectTo'); + const redirectSpy = vi.spyOn(redirectService, 'redirectTo'); - spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + vi.spyOn(checkSessionService, 'serverStateChanged').mockReturnValue( + false + ); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'id-token' ); - spyOn(urlService, 'getPostLogoutRedirectUrl').and.returnValue( + vi.spyOn(urlService, 'getPostLogoutRedirectUrl').mockReturnValue( 'post-logout-redirect-url' ); - spyOn(urlService, 'getEndSessionEndpoint').and.returnValue({ + vi.spyOn(urlService, 'getEndSessionEndpoint').mockReturnValue({ url: 'some-url', existingParams: '', }); - const postSpy = spyOn(dataService, 'post').and.returnValue(of(null)); + const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of(null)); const config = { configId: 'configId1', clientId: 'clientId' }; // Act @@ -607,7 +618,7 @@ describe('Logout and Revoke Service', () => { // Assert result$.subscribe(() => { expect(redirectSpy).not.toHaveBeenCalled(); - expect(postSpy).toHaveBeenCalledOnceWith( + expect(postSpy).toHaveBeenCalledExactlyOnceWith( 'some-url', { id_token_hint: 'id-token', @@ -618,23 +629,23 @@ describe('Logout and Revoke Service', () => { ui_locales: 'de fr en', }, config, - jasmine.anything() + expect.anything() ); const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; - expect(httpHeaders.has('Content-Type')).toBeTrue(); + expect(httpHeaders.has('Content-Type')).toBeTruthy(); expect(httpHeaders.get('Content-Type')).toBe( 'application/x-www-form-urlencoded' ); }); - })); + }); }); describe('logoffLocal', () => { it('calls flowsService.resetAuthorizationData', () => { // Arrange - const resetAuthorizationDataSpy = spyOn( + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); @@ -649,25 +660,25 @@ describe('Logout and Revoke Service', () => { }); describe('logoffAndRevokeTokens', () => { - it('calls revokeRefreshToken and revokeAccessToken when storage holds a refreshtoken', waitForAsync(() => { + it('calls revokeRefreshToken and revokeAccessToken when storage holds a refreshtoken', async () => { // Arrange const paramToken = 'damien'; const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ revocationEndpoint: 'revocationEndpoint' }); - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ revocationEndpoint: 'revocationEndpoint' }) + ); + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - const revokeRefreshTokenSpy = spyOn( - service, - 'revokeRefreshToken' - ).and.returnValue(of({ any: 'thing' })); - const revokeAccessTokenSpy = spyOn( - service, - 'revokeAccessToken' - ).and.returnValue(of({ any: 'thing' })); + const revokeRefreshTokenSpy = vi + .spyOn(service, 'revokeRefreshToken') + .mockReturnValue(of({ any: 'thing' })); + const revokeAccessTokenSpy = vi + .spyOn(service, 'revokeAccessToken') + .mockReturnValue(of({ any: 'thing' })); // Act service.logoffAndRevokeTokens(config, [config]).subscribe(() => { @@ -675,25 +686,27 @@ describe('Logout and Revoke Service', () => { expect(revokeRefreshTokenSpy).toHaveBeenCalled(); expect(revokeAccessTokenSpy).toHaveBeenCalled(); }); - })); + }); - it('logs error when revokeaccesstoken throws an error', waitForAsync(() => { + it('logs error when revokeaccesstoken throws an error', async () => { // Arrange const paramToken = 'damien'; const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ revocationEndpoint: 'revocationEndpoint' }); - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ revocationEndpoint: 'revocationEndpoint' }) + ); + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - spyOn(service, 'revokeRefreshToken').and.returnValue( + vi.spyOn(service, 'revokeRefreshToken').mockReturnValue( of({ any: 'thing' }) ); - const loggerSpy = spyOn(loggerService, 'logError'); + const loggerSpy = vi.spyOn(loggerService, 'logError'); - spyOn(service, 'revokeAccessToken').and.returnValue( + vi.spyOn(service, 'revokeAccessToken').mockReturnValue( throwError(() => new Error('Error')) ); @@ -704,20 +717,22 @@ describe('Logout and Revoke Service', () => { expect(err).toBeTruthy(); }, }); - })); + }); - it('calls logoff in case of success', waitForAsync(() => { + it('calls logoff in case of success', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - spyOn(service, 'revokeRefreshToken').and.returnValue( + vi.spyOn(service, 'revokeRefreshToken').mockReturnValue( of({ any: 'thing' }) ); - spyOn(service, 'revokeAccessToken').and.returnValue(of({ any: 'thing' })); - const logoffSpy = spyOn(service, 'logoff').and.returnValue(of(null)); + vi.spyOn(service, 'revokeAccessToken').mockReturnValue( + of({ any: 'thing' }) + ); + const logoffSpy = vi.spyOn(service, 'logoff').mockReturnValue(of(null)); const config = { configId: 'configId1' }; // Act @@ -725,20 +740,22 @@ describe('Logout and Revoke Service', () => { // Assert expect(logoffSpy).toHaveBeenCalled(); }); - })); + }); - it('calls logoff with urlhandler in case of success', waitForAsync(() => { + it('calls logoff with urlhandler in case of success', async () => { // Arrange const paramToken = 'damien'; - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( paramToken ); - spyOn(service, 'revokeRefreshToken').and.returnValue( + vi.spyOn(service, 'revokeRefreshToken').mockReturnValue( of({ any: 'thing' }) ); - spyOn(service, 'revokeAccessToken').and.returnValue(of({ any: 'thing' })); - const logoffSpy = spyOn(service, 'logoff').and.returnValue(of(null)); + vi.spyOn(service, 'revokeAccessToken').mockReturnValue( + of({ any: 'thing' }) + ); + const logoffSpy = vi.spyOn(service, 'logoff').mockReturnValue(of(null)); const urlHandler = (_url: string): void => undefined; const config = { configId: 'configId1' }; @@ -747,26 +764,29 @@ describe('Logout and Revoke Service', () => { .logoffAndRevokeTokens(config, [config], { urlHandler }) .subscribe(() => { // Assert - expect(logoffSpy).toHaveBeenCalledOnceWith(config, [config], { + expect(logoffSpy).toHaveBeenCalledExactlyOnceWith(config, [config], { urlHandler, }); }); - })); + }); - it('calls revokeAccessToken when storage does not hold a refreshtoken', waitForAsync(() => { + it('calls revokeAccessToken when storage does not hold a refreshtoken', async () => { // Arrange const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ revocationEndpoint: 'revocationEndpoint' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ revocationEndpoint: 'revocationEndpoint' }) + ); - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue(''); - const revokeRefreshTokenSpy = spyOn(service, 'revokeRefreshToken'); - const revokeAccessTokenSpy = spyOn( - service, - 'revokeAccessToken' - ).and.returnValue(of({ any: 'thing' })); + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( + '' + ); + const revokeRefreshTokenSpy = vi.spyOn(service, 'revokeRefreshToken'); + const revokeAccessTokenSpy = vi + .spyOn(service, 'revokeAccessToken') + .mockReturnValue(of({ any: 'thing' })); // Act service.logoffAndRevokeTokens(config, [config]).subscribe(() => { @@ -774,19 +794,23 @@ describe('Logout and Revoke Service', () => { expect(revokeRefreshTokenSpy).not.toHaveBeenCalled(); expect(revokeAccessTokenSpy).toHaveBeenCalled(); }); - })); + }); - it('logs error when revokeaccesstoken throws an error', waitForAsync(() => { + it('logs error when revokeaccesstoken throws an error', async () => { // Arrange const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ revocationEndpoint: 'revocationEndpoint' }); - spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue(''); - const loggerSpy = spyOn(loggerService, 'logError'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ revocationEndpoint: 'revocationEndpoint' }) + ); + vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue( + '' + ); + const loggerSpy = vi.spyOn(loggerService, 'logError'); - spyOn(service, 'revokeAccessToken').and.returnValue( + vi.spyOn(service, 'revokeAccessToken').mockReturnValue( throwError(() => new Error('Error')) ); @@ -797,18 +821,18 @@ describe('Logout and Revoke Service', () => { expect(err).toBeTruthy(); }, }); - })); + }); }); describe('logoffLocalMultiple', () => { it('calls logoffLocal for every config which is present', () => { // Arrange const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }]; - const resetAuthorizationDataSpy = spyOn( + const resetAuthorizationDataSpy = vi.spyOn( resetAuthDataService, 'resetAuthorizationData' ); - const checkSessionServiceSpy = spyOn(checkSessionService, 'stop'); + const checkSessionServiceSpy = vi.spyOn(checkSessionService, 'stop'); // Act service.logoffLocalMultiple(allConfigs); @@ -816,8 +840,8 @@ describe('Logout and Revoke Service', () => { // Assert expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(2); expect(checkSessionServiceSpy).toHaveBeenCalledTimes(2); - expect(resetAuthorizationDataSpy.calls.allArgs()).toEqual([ - [allConfigs[0], allConfigs], + expect(resetAuthorizationDataSpy).toBeCalledWith([ + [allConfigs[0]!, allConfigs], [allConfigs[1], allConfigs], ]); }); diff --git a/src/logoff-revoke/logoff-revocation.service.ts b/src/logoff-revoke/logoff-revocation.service.ts index b3187d6..8988c59 100644 --- a/src/logoff-revoke/logoff-revocation.service.ts +++ b/src/logoff-revoke/logoff-revocation.service.ts @@ -1,10 +1,10 @@ import { HttpHeaders } from '@ngify/http'; -import { inject, Injectable } from 'injection-js'; -import { Observable, of, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, of, throwError } from 'rxjs'; import { catchError, concatMap, retry, switchMap } from 'rxjs/operators'; import { DataService } from '../api/data.service'; -import { LogoutAuthOptions } from '../auth-options'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { LogoutAuthOptions } from '../auth-options'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { CheckSessionService } from '../iframe/check-session.service'; import { LoggerService } from '../logging/logger.service'; diff --git a/src/oidc.security.service.spec.ts b/src/oidc.security.service.spec.ts index e9d124d..bffcd4b 100644 --- a/src/oidc.security.service.spec.ts +++ b/src/oidc.security.service.spec.ts @@ -1,6 +1,6 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { Observable, of } from 'rxjs'; -import { mockProvider } from '../test/auto-mock'; +import { TestBed } from '@/testing'; +import { Observable, lastValueFrom, of } from 'rxjs'; +import { vi } from 'vitest'; import { AuthStateService } from './auth-state/auth-state.service'; import { CheckAuthService } from './auth-state/check-auth.service'; import { CallbackService } from './callback/callback.service'; @@ -9,10 +9,11 @@ import { AuthWellKnownService } from './config/auth-well-known/auth-well-known.s import { ConfigurationService } from './config/config.service'; import { FlowsDataService } from './flows/flows-data.service'; import { CheckSessionService } from './iframe/check-session.service'; -import { LoginResponse } from './login/login-response'; +import type { LoginResponse } from './login/login-response'; import { LoginService } from './login/login.service'; import { LogoffRevocationService } from './logoff-revoke/logoff-revocation.service'; import { OidcSecurityService } from './oidc.security.service'; +import { mockProvider } from './testing/mock'; import { UserService } from './user-data/user.service'; import { TokenHelperService } from './utils/tokenHelper/token-helper.service'; import { UrlService } from './utils/url/url.service'; @@ -55,9 +56,6 @@ describe('OidcSecurityService', () => { mockProvider(AuthWellKnownService), ], }); - }); - - beforeEach(() => { authStateService = TestBed.inject(AuthStateService); tokenHelperService = TestBed.inject(TokenHelperService); configurationService = TestBed.inject(ConfigurationService); @@ -73,15 +71,14 @@ describe('OidcSecurityService', () => { callbackService = TestBed.inject(CallbackService); // this is required because these methods will be invoked by the signal properties when the service is created - authenticatedSpy = spyOnProperty( - authStateService, - 'authenticated$' - ).and.returnValue( - of({ isAuthenticated: false, allConfigsAuthenticated: [] }) - ); - userDataSpy = spyOnProperty(userService, 'userData$').and.returnValue( - of({ userData: null, allUserData: [] }) - ); + authenticatedSpy = vi + .spyOnProperty(authStateService, 'authenticated$') + .mockReturnValue( + of({ isAuthenticated: false, allConfigsAuthenticated: [] }) + ); + userDataSpy = vi + .spyOnProperty(userService, 'userData$') + .mockReturnValue(of({ userData: null, allUserData: [] })); oidcSecurityService = TestBed.inject(OidcSecurityService); }); @@ -90,94 +87,93 @@ describe('OidcSecurityService', () => { }); describe('userData$', () => { - it('calls userService.userData$', waitForAsync(() => { + it('calls userService.userData$', async () => { oidcSecurityService.userData$.subscribe(() => { // 1x from this subscribe // 1x by the signal property expect(userDataSpy).toHaveBeenCalledTimes(2); }); - })); + }); }); describe('userData', () => { - it('calls userService.userData$', waitForAsync(() => { - const _userdata = oidcSecurityService.userData(); + it('calls userService.userData$', async () => { + const _userdata = await lastValueFrom(oidcSecurityService.userData()); expect(userDataSpy).toHaveBeenCalledTimes(1); - })); + }); }); describe('isAuthenticated$', () => { - it('calls authStateService.isAuthenticated$', waitForAsync(() => { + it('calls authStateService.isAuthenticated$', async () => { oidcSecurityService.isAuthenticated$.subscribe(() => { // 1x from this subscribe // 1x by the signal property expect(authenticatedSpy).toHaveBeenCalledTimes(2); }); - })); + }); }); describe('authenticated', () => { - it('calls authStateService.isAuthenticated$', waitForAsync(() => { - const _authenticated = oidcSecurityService.authenticated(); + it('calls authStateService.isAuthenticated$', async () => { + const _authenticated = await lastValueFrom( + oidcSecurityService.authenticated() + ); expect(authenticatedSpy).toHaveBeenCalledTimes(1); - })); + }); }); describe('checkSessionChanged$', () => { - it('calls checkSessionService.checkSessionChanged$', waitForAsync(() => { - const spy = spyOnProperty( - checkSessionService, - 'checkSessionChanged$' - ).and.returnValue(of(true)); + it('calls checkSessionService.checkSessionChanged$', async () => { + const spy = vi + .spyOnProperty(checkSessionService, 'checkSessionChanged$') + .mockReturnValue(of(true)); oidcSecurityService.checkSessionChanged$.subscribe(() => { expect(spy).toHaveBeenCalledTimes(1); }); - })); + }); }); describe('stsCallback$', () => { - it('calls callbackService.stsCallback$', waitForAsync(() => { - const spy = spyOnProperty( - callbackService, - 'stsCallback$' - ).and.returnValue(of()); + it('calls callbackService.stsCallback$', async () => { + const spy = vi + .spyOnProperty(callbackService, 'stsCallback$') + .mockReturnValue(of()); oidcSecurityService.stsCallback$.subscribe(() => { expect(spy).toHaveBeenCalledTimes(1); }); - })); + }); }); describe('preloadAuthWellKnownDocument', () => { - it('calls authWellKnownService.queryAndStoreAuthWellKnownEndPoints with config', waitForAsync(() => { + it('calls authWellKnownService.queryAndStoreAuthWellKnownEndPoints with config', async () => { const config = { configId: 'configid1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn( - authWellKnownService, - 'queryAndStoreAuthWellKnownEndPoints' - ).and.returnValue(of({})); + const spy = vi + .spyOn(authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints') + .mockReturnValue(of({})); oidcSecurityService.preloadAuthWellKnownDocument().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); - })); + }); }); describe('getConfigurations', () => { it('is not of type observable', () => { expect(oidcSecurityService.getConfigurations).not.toEqual( - jasmine.any(Observable) + expect.any(Observable) ); }); it('calls configurationProvider.getAllConfigurations', () => { - const spy = spyOn(configurationService, 'getAllConfigurations'); + const spy = vi.spyOn(configurationService, 'getAllConfigurations'); oidcSecurityService.getConfigurations(); @@ -188,607 +184,635 @@ describe('OidcSecurityService', () => { describe('getConfiguration', () => { it('is not of type observable', () => { expect(oidcSecurityService.getConfiguration).not.toEqual( - jasmine.any(Observable) + expect.any(Observable) ); }); it('calls configurationProvider.getOpenIDConfiguration with passed configId when configId is passed', () => { - const spy = spyOn(configurationService, 'getOpenIDConfiguration'); + const spy = vi.spyOn(configurationService, 'getOpenIDConfiguration'); oidcSecurityService.getConfiguration('configId'); - expect(spy).toHaveBeenCalledOnceWith('configId'); + expect(spy).toHaveBeenCalledExactlyOnceWith('configId'); }); }); describe('getUserData', () => { - it('calls configurationProvider.getOpenIDConfiguration with config', waitForAsync(() => { + it('calls configurationProvider.getOpenIDConfiguration with config', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(userService, 'getUserDataFromStore').and.returnValue({ - some: 'thing', - }); + const spy = vi + .spyOn(userService, 'getUserDataFromStore') + .mockReturnValue({ + some: 'thing', + }); oidcSecurityService.getUserData('configId').subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); - })); + }); - it('returns userdata', waitForAsync(() => { + it('returns userdata', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - spyOn(userService, 'getUserDataFromStore').and.returnValue({ + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue({ some: 'thing', }); oidcSecurityService.getUserData('configId').subscribe((result) => { expect(result).toEqual({ some: 'thing' }); }); - })); + }); }); describe('checkAuth', () => { - it('calls checkAuthService.checkAuth() without url if none is passed', waitForAsync(() => { + it('calls checkAuthService.checkAuth() without url if none is passed', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn(checkAuthService, 'checkAuth').and.returnValue( - of({} as LoginResponse) - ); + const spy = vi + .spyOn(checkAuthService, 'checkAuth') + .mockReturnValue(of({} as LoginResponse)); oidcSecurityService.checkAuth().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, [config], undefined); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + undefined + ); }); - })); + }); - it('calls checkAuthService.checkAuth() with url if one is passed', waitForAsync(() => { + it('calls checkAuthService.checkAuth() with url if one is passed', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn(checkAuthService, 'checkAuth').and.returnValue( - of({} as LoginResponse) - ); + const spy = vi + .spyOn(checkAuthService, 'checkAuth') + .mockReturnValue(of({} as LoginResponse)); oidcSecurityService.checkAuth('some-url').subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-url'); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + 'some-url' + ); }); - })); + }); }); describe('checkAuthMultiple', () => { - it('calls checkAuthService.checkAuth() without url if none is passed', waitForAsync(() => { + it('calls checkAuthService.checkAuth() without url if none is passed', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn(checkAuthService, 'checkAuthMultiple').and.returnValue( - of([{}] as LoginResponse[]) - ); + const spy = vi + .spyOn(checkAuthService, 'checkAuthMultiple') + .mockReturnValue(of([{}] as LoginResponse[])); oidcSecurityService.checkAuthMultiple().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith([config], undefined); + expect(spy).toHaveBeenCalledExactlyOnceWith([config], undefined); }); - })); + }); - it('calls checkAuthService.checkAuthMultiple() with url if one is passed', waitForAsync(() => { + it('calls checkAuthService.checkAuthMultiple() with url if one is passed', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn(checkAuthService, 'checkAuthMultiple').and.returnValue( - of([{}] as LoginResponse[]) - ); + const spy = vi + .spyOn(checkAuthService, 'checkAuthMultiple') + .mockReturnValue(of([{}] as LoginResponse[])); oidcSecurityService.checkAuthMultiple('some-url').subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith([config], 'some-url'); + expect(spy).toHaveBeenCalledExactlyOnceWith([config], 'some-url'); }); - })); + }); }); describe('isAuthenticated()', () => { - it('calls authStateService.isAuthenticated with passed configId when configId is passed', waitForAsync(() => { + it('calls authStateService.isAuthenticated with passed configId when configId is passed', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(authStateService, 'isAuthenticated').and.returnValue( - true - ); + const spy = vi + .spyOn(authStateService, 'isAuthenticated') + .mockReturnValue(true); oidcSecurityService.isAuthenticated().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); - })); + }); }); describe('checkAuthIncludingServer', () => { - it('calls checkAuthService.checkAuthIncludingServer()', waitForAsync(() => { + it('calls checkAuthService.checkAuthIncludingServer()', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn( - checkAuthService, - 'checkAuthIncludingServer' - ).and.returnValue(of({} as LoginResponse)); + const spy = vi + .spyOn(checkAuthService, 'checkAuthIncludingServer') + .mockReturnValue(of({} as LoginResponse)); oidcSecurityService.checkAuthIncludingServer().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, [config]); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config]); }); - })); + }); }); describe('getAccessToken', () => { - it('calls authStateService.getAccessToken()', waitForAsync(() => { + it('calls authStateService.getAccessToken()', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(authStateService, 'getAccessToken').and.returnValue(''); + const spy = vi + .spyOn(authStateService, 'getAccessToken') + .mockReturnValue(''); oidcSecurityService.getAccessToken().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); - })); + }); }); describe('getIdToken', () => { - it('calls authStateService.getIdToken()', waitForAsync(() => { + it('calls authStateService.getIdToken()', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(authStateService, 'getIdToken').and.returnValue(''); + const spy = vi.spyOn(authStateService, 'getIdToken').mockReturnValue(''); oidcSecurityService.getIdToken().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); - })); + }); }); describe('getRefreshToken', () => { - it('calls authStateService.getRefreshToken()', waitForAsync(() => { + it('calls authStateService.getRefreshToken()', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(authStateService, 'getRefreshToken').and.returnValue( - '' - ); + const spy = vi + .spyOn(authStateService, 'getRefreshToken') + .mockReturnValue(''); oidcSecurityService.getRefreshToken().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); - })); + }); }); describe('getAuthenticationResult', () => { - it('calls authStateService.getAuthenticationResult()', waitForAsync(() => { + it('calls authStateService.getAuthenticationResult()', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn( - authStateService, - 'getAuthenticationResult' - ).and.returnValue(null); + const spy = vi + .spyOn(authStateService, 'getAuthenticationResult') + .mockReturnValue(null); oidcSecurityService.getAuthenticationResult().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); - })); + }); }); describe('getPayloadFromIdToken', () => { - it('calls `authStateService.getIdToken` method, encode = false', waitForAsync(() => { + it('calls `authStateService.getIdToken` method, encode = false', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - spyOn(authStateService, 'getIdToken').and.returnValue('some-token'); - const spy = spyOn( - tokenHelperService, - 'getPayloadFromToken' - ).and.returnValue(null); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('some-token'); + const spy = vi + .spyOn(tokenHelperService, 'getPayloadFromToken') + .mockReturnValue(null); oidcSecurityService.getPayloadFromIdToken().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith('some-token', false, config); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'some-token', + false, + config + ); }); - })); + }); - it('calls `authStateService.getIdToken` method, encode = true', waitForAsync(() => { + it('calls `authStateService.getIdToken` method, encode = true', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - spyOn(authStateService, 'getIdToken').and.returnValue('some-token'); - const spy = spyOn( - tokenHelperService, - 'getPayloadFromToken' - ).and.returnValue(null); + vi.spyOn(authStateService, 'getIdToken').mockReturnValue('some-token'); + const spy = vi + .spyOn(tokenHelperService, 'getPayloadFromToken') + .mockReturnValue(null); oidcSecurityService.getPayloadFromIdToken(true).subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith('some-token', true, config); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-token', true, config); }); - })); + }); }); describe('getPayloadFromAccessToken', () => { - it('calls `authStateService.getAccessToken` method, encode = false', waitForAsync(() => { + it('calls `authStateService.getAccessToken` method, encode = false', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - spyOn(authStateService, 'getAccessToken').and.returnValue( + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue( 'some-access-token' ); - const spy = spyOn( - tokenHelperService, - 'getPayloadFromToken' - ).and.returnValue(null); + const spy = vi + .spyOn(tokenHelperService, 'getPayloadFromToken') + .mockReturnValue(null); oidcSecurityService.getPayloadFromAccessToken().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( 'some-access-token', false, config ); }); - })); + }); - it('calls `authStateService.getIdToken` method, encode = true', waitForAsync(() => { + it('calls `authStateService.getIdToken` method, encode = true', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - spyOn(authStateService, 'getAccessToken').and.returnValue( + vi.spyOn(authStateService, 'getAccessToken').mockReturnValue( 'some-access-token' ); - const spy = spyOn( - tokenHelperService, - 'getPayloadFromToken' - ).and.returnValue(null); + const spy = vi + .spyOn(tokenHelperService, 'getPayloadFromToken') + .mockReturnValue(null); oidcSecurityService.getPayloadFromAccessToken(true).subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith('some-access-token', true, config); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'some-access-token', + true, + config + ); }); - })); + }); }); describe('setState', () => { - it('calls flowsDataService.setAuthStateControl with param', waitForAsync(() => { + it('calls flowsDataService.setAuthStateControl with param', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(flowsDataService, 'setAuthStateControl'); + const spy = vi.spyOn(flowsDataService, 'setAuthStateControl'); oidcSecurityService.setState('anyString').subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith('anyString', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('anyString', config); }); - })); + }); }); describe('getState', () => { - it('calls flowsDataService.getAuthStateControl', waitForAsync(() => { + it('calls flowsDataService.getAuthStateControl', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(flowsDataService, 'getAuthStateControl'); + const spy = vi.spyOn(flowsDataService, 'getAuthStateControl'); oidcSecurityService.getState().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); - })); + }); }); describe('authorize', () => { - it('calls login service login', waitForAsync(() => { + it('calls login service login', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(loginService, 'login'); + const spy = vi.spyOn(loginService, 'login'); - oidcSecurityService.authorize(); + await lastValueFrom(oidcSecurityService.authorize()); - expect(spy).toHaveBeenCalledOnceWith(config, undefined); - })); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); + }); - it('calls login service login with authoptions', waitForAsync(() => { + it('calls login service login with authoptions', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(loginService, 'login'); + const spy = vi.spyOn(loginService, 'login'); - oidcSecurityService.authorize('configId', { + await lastValueFrom( + oidcSecurityService.authorize('configId', { + customParams: { some: 'param' }, + }) + ); + + expect(spy).toHaveBeenCalledExactlyOnceWith(config, { customParams: { some: 'param' }, }); - - expect(spy).toHaveBeenCalledOnceWith(config, { - customParams: { some: 'param' }, - }); - })); + }); }); describe('authorizeWithPopUp', () => { - it('calls login service loginWithPopUp', waitForAsync(() => { + it('calls login service loginWithPopUp', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn(loginService, 'loginWithPopUp').and.callFake(() => - of({} as LoginResponse) - ); + const spy = vi + .spyOn(loginService, 'loginWithPopUp') + .mockImplementation(() => of({} as LoginResponse)); oidcSecurityService.authorizeWithPopUp().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( config, [config], undefined, undefined ); }); - })); + }); }); describe('forceRefreshSession', () => { - it('calls refreshSessionService userForceRefreshSession with configId from config when none is passed', waitForAsync(() => { + it('calls refreshSessionService userForceRefreshSession with configId from config when none is passed', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn( - refreshSessionService, - 'userForceRefreshSession' - ).and.returnValue(of({} as LoginResponse)); + const spy = vi + .spyOn(refreshSessionService, 'userForceRefreshSession') + .mockReturnValue(of({} as LoginResponse)); oidcSecurityService.forceRefreshSession().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, [config], undefined); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + undefined + ); }); - })); + }); }); describe('logoffAndRevokeTokens', () => { - it('calls logoffRevocationService.logoffAndRevokeTokens', waitForAsync(() => { + it('calls logoffRevocationService.logoffAndRevokeTokens', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn( - logoffRevocationService, - 'logoffAndRevokeTokens' - ).and.returnValue(of(null)); + const spy = vi + .spyOn(logoffRevocationService, 'logoffAndRevokeTokens') + .mockReturnValue(of(null)); oidcSecurityService.logoffAndRevokeTokens().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, [config], undefined); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + undefined + ); }); - })); + }); }); describe('logoff', () => { - it('calls logoffRevocationService.logoff', waitForAsync(() => { + it('calls logoffRevocationService.logoff', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn(logoffRevocationService, 'logoff').and.returnValue( - of(null) - ); + const spy = vi + .spyOn(logoffRevocationService, 'logoff') + .mockReturnValue(of(null)); oidcSecurityService.logoff().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, [config], undefined); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + undefined + ); }); - })); + }); }); describe('logoffLocal', () => { - it('calls logoffRevocationService.logoffLocal', waitForAsync(() => { + it('calls logoffRevocationService.logoffLocal', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn(logoffRevocationService, 'logoffLocal'); + const spy = vi.spyOn(logoffRevocationService, 'logoffLocal'); - oidcSecurityService.logoffLocal(); - expect(spy).toHaveBeenCalledOnceWith(config, [config]); - })); + await lastValueFrom(oidcSecurityService.logoffLocal()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config]); + }); }); describe('logoffLocalMultiple', () => { - it('calls logoffRevocationService.logoffLocalMultiple', waitForAsync(() => { + it('calls logoffRevocationService.logoffLocalMultiple', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfigurations').mockReturnValue( of({ allConfigs: [config], currentConfig: config }) ); - const spy = spyOn(logoffRevocationService, 'logoffLocalMultiple'); + const spy = vi.spyOn(logoffRevocationService, 'logoffLocalMultiple'); - oidcSecurityService.logoffLocalMultiple(); - expect(spy).toHaveBeenCalledOnceWith([config]); - })); + await lastValueFrom(oidcSecurityService.logoffLocalMultiple()); + expect(spy).toHaveBeenCalledExactlyOnceWith([config]); + }); }); describe('revokeAccessToken', () => { - it('calls logoffRevocationService.revokeAccessToken', waitForAsync(() => { + it('calls logoffRevocationService.revokeAccessToken', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn( - logoffRevocationService, - 'revokeAccessToken' - ).and.returnValue(of(null)); + const spy = vi + .spyOn(logoffRevocationService, 'revokeAccessToken') + .mockReturnValue(of(null)); oidcSecurityService.revokeAccessToken().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, undefined); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); }); - })); + }); - it('calls logoffRevocationService.revokeAccessToken with accesstoken', waitForAsync(() => { + it('calls logoffRevocationService.revokeAccessToken with accesstoken', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn( - logoffRevocationService, - 'revokeAccessToken' - ).and.returnValue(of(null)); + const spy = vi + .spyOn(logoffRevocationService, 'revokeAccessToken') + .mockReturnValue(of(null)); oidcSecurityService.revokeAccessToken('access_token').subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, 'access_token'); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, 'access_token'); }); - })); + }); }); describe('revokeRefreshToken', () => { - it('calls logoffRevocationService.revokeRefreshToken', waitForAsync(() => { + it('calls logoffRevocationService.revokeRefreshToken', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn( - logoffRevocationService, - 'revokeRefreshToken' - ).and.returnValue(of(null)); + const spy = vi + .spyOn(logoffRevocationService, 'revokeRefreshToken') + .mockReturnValue(of(null)); oidcSecurityService.revokeRefreshToken().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, undefined); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); }); - })); + }); - it('calls logoffRevocationService.revokeRefreshToken with refresh token', waitForAsync(() => { + it('calls logoffRevocationService.revokeRefreshToken with refresh token', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn( - logoffRevocationService, - 'revokeRefreshToken' - ).and.returnValue(of(null)); + const spy = vi + .spyOn(logoffRevocationService, 'revokeRefreshToken') + .mockReturnValue(of(null)); oidcSecurityService.revokeRefreshToken('refresh_token').subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, 'refresh_token'); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, 'refresh_token'); }); - })); + }); }); describe('getEndSessionUrl', () => { - it('calls logoffRevocationService.getEndSessionUrl ', waitForAsync(() => { + it('calls logoffRevocationService.getEndSessionUrl ', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(urlService, 'getEndSessionUrl').and.returnValue(null); + const spy = vi + .spyOn(urlService, 'getEndSessionUrl') + .mockReturnValue(null); oidcSecurityService.getEndSessionUrl().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, undefined); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); }); - })); + }); - it('calls logoffRevocationService.getEndSessionUrl with customparams', waitForAsync(() => { + it('calls logoffRevocationService.getEndSessionUrl with customparams', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(urlService, 'getEndSessionUrl').and.returnValue(null); + const spy = vi + .spyOn(urlService, 'getEndSessionUrl') + .mockReturnValue(null); oidcSecurityService .getEndSessionUrl({ custom: 'params' }) .subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, { custom: 'params' }); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, { + custom: 'params', + }); }); - })); + }); }); describe('getAuthorizeUrl', () => { - it('calls urlService.getAuthorizeUrl ', waitForAsync(() => { + it('calls urlService.getAuthorizeUrl ', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(urlService, 'getAuthorizeUrl').and.returnValue( - of(null) - ); + const spy = vi + .spyOn(urlService, 'getAuthorizeUrl') + .mockReturnValue(of(null)); oidcSecurityService.getAuthorizeUrl().subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, undefined); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); }); - })); + }); - it('calls urlService.getAuthorizeUrl with customparams', waitForAsync(() => { + it('calls urlService.getAuthorizeUrl with customparams', async () => { const config = { configId: 'configId1' }; - spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue( + vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue( of(config) ); - const spy = spyOn(urlService, 'getAuthorizeUrl').and.returnValue( - of(null) - ); + const spy = vi + .spyOn(urlService, 'getAuthorizeUrl') + .mockReturnValue(of(null)); oidcSecurityService .getAuthorizeUrl({ custom: 'params' }) .subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith(config, { + expect(spy).toHaveBeenCalledExactlyOnceWith(config, { customParams: { custom: 'params' }, }); }); - })); + }); }); }); diff --git a/src/oidc.security.service.ts b/src/oidc.security.service.ts index e3eb186..c9bd3ed 100644 --- a/src/oidc.security.service.ts +++ b/src/oidc.security.service.ts @@ -1,28 +1,28 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable } from 'rxjs'; -import { concatMap, map } from 'rxjs/operators'; -import { AuthOptions, LogoutAuthOptions } from './auth-options'; -import { AuthenticatedResult } from './auth-state/auth-result'; +import { Injectable, inject } from 'injection-js'; +import { toSignal } from 'injection-js/rxjs-interop'; +import type { Observable } from 'rxjs'; +import { concatMap, map, shareReplay } from 'rxjs/operators'; +import type { AuthOptions, LogoutAuthOptions } from './auth-options'; +import type { AuthenticatedResult } from './auth-state/auth-result'; import { AuthStateService } from './auth-state/auth-state.service'; import { CheckAuthService } from './auth-state/check-auth.service'; import { CallbackService } from './callback/callback.service'; import { RefreshSessionService } from './callback/refresh-session.service'; -import { AuthWellKnownEndpoints } from './config/auth-well-known/auth-well-known-endpoints'; +import type { AuthWellKnownEndpoints } from './config/auth-well-known/auth-well-known-endpoints'; import { AuthWellKnownService } from './config/auth-well-known/auth-well-known.service'; import { ConfigurationService } from './config/config.service'; -import { OpenIdConfiguration } from './config/openid-configuration'; -import { AuthResult } from './flows/callback-context'; +import type { OpenIdConfiguration } from './config/openid-configuration'; +import type { AuthResult } from './flows/callback-context'; import { FlowsDataService } from './flows/flows-data.service'; import { CheckSessionService } from './iframe/check-session.service'; -import { LoginResponse } from './login/login-response'; +import type { LoginResponse } from './login/login-response'; import { LoginService } from './login/login.service'; -import { PopupOptions } from './login/popup/popup-options'; +import type { PopupOptions } from './login/popup/popup-options'; import { LogoffRevocationService } from './logoff-revoke/logoff-revocation.service'; import { UserService } from './user-data/user.service'; -import { UserDataResult } from './user-data/userdata-result'; +import type { UserDataResult } from './user-data/userdata-result'; import { TokenHelperService } from './utils/tokenHelper/token-helper.service'; import { UrlService } from './utils/url/url.service'; -import { toSignal } from 'injection-js/rxjs-interop'; @Injectable() export class OidcSecurityService { @@ -355,10 +355,17 @@ export class OidcSecurityService { * @param configId The configId to perform the action in behalf of. If not passed, the first configs will be taken * @param authOptions The custom options for the the authentication request. */ - authorize(configId?: string, authOptions?: AuthOptions): void { - this.configurationService + authorize(configId?: string, authOptions?: AuthOptions): Observable { + const result$ = this.configurationService .getOpenIDConfiguration(configId) - .subscribe((config) => this.loginService.login(config, authOptions)); + .pipe( + map((config) => this.loginService.login(config, authOptions)), + shareReplay(1) + ); + + result$.subscribe(); + + return result$; } /** @@ -471,24 +478,34 @@ export class OidcSecurityService { * * @param configId The configId to perform the action in behalf of. If not passed, the first configs will be taken */ - logoffLocal(configId?: string): void { - this.configurationService + logoffLocal(configId?: string): Observable { + const result$ = this.configurationService .getOpenIDConfigurations(configId) - .subscribe(({ allConfigs, currentConfig }) => - this.logoffRevocationService.logoffLocal(currentConfig, allConfigs) + .pipe( + map(({ allConfigs, currentConfig }) => + this.logoffRevocationService.logoffLocal(currentConfig, allConfigs) + ), + shareReplay(1) ); + result$.subscribe(); + return result$; } /** * Logs the user out of the application for all configs without logging them out of the server. * Use this method if you have _multiple_ configs enabled. */ - logoffLocalMultiple(): void { - this.configurationService - .getOpenIDConfigurations() - .subscribe(({ allConfigs }) => + logoffLocalMultiple(): Observable { + const result$ = this.configurationService.getOpenIDConfigurations().pipe( + map(({ allConfigs }) => this.logoffRevocationService.logoffLocalMultiple(allConfigs) - ); + ), + shareReplay(1) + ); + + result$.subscribe(); + + return result$; } /** diff --git a/src/provide-auth.spec.ts b/src/provide-auth.spec.ts index af0a4e0..ef27217 100644 --- a/src/provide-auth.spec.ts +++ b/src/provide-auth.spec.ts @@ -1,7 +1,7 @@ -import { APP_INITIALIZER } from '@angular/core'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed, createSpyObj } from '@/testing'; +import { mockProvider } from '@/testing/mock'; +import { APP_INITIALIZER } from 'oidc-client-rx'; import { of } from 'rxjs'; -import { mockProvider } from '../test/auto-mock'; import { PASSED_CONFIG } from './auth-config'; import { ConfigurationService } from './config/config.service'; import { @@ -14,14 +14,14 @@ import { provideAuth, withAppInitializerAuthCheck } from './provide-auth'; describe('provideAuth', () => { describe('APP_CONFIG', () => { - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + beforeEach(async () => { + await TestBed.configureTestingModule({ providers: [ provideAuth({ config: { authority: 'something' } }), mockProvider(ConfigurationService), ], }).compileComponents(); - })); + }); it('should provide config', () => { const config = TestBed.inject(PASSED_CONFIG); @@ -37,8 +37,8 @@ describe('provideAuth', () => { }); describe('StsConfigHttpLoader', () => { - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + beforeEach(async () => { + await TestBed.configureTestingModule({ providers: [ provideAuth({ loader: { @@ -49,7 +49,7 @@ describe('provideAuth', () => { mockProvider(ConfigurationService), ], }).compileComponents(); - })); + }); it('should create StsConfigStaticLoader if config is passed', () => { const configLoader = TestBed.inject(StsConfigLoader); @@ -59,14 +59,14 @@ describe('provideAuth', () => { }); describe('features', () => { - let oidcSecurityServiceMock: jasmine.SpyObj; + let oidcSecurityServiceMock: OidcSecurityService; - beforeEach(waitForAsync(() => { - oidcSecurityServiceMock = jasmine.createSpyObj( + beforeEach(async () => { + oidcSecurityServiceMock = createSpyObj( 'OidcSecurityService', ['checkAuthMultiple'] ); - TestBed.configureTestingModule({ + await TestBed.configureTestingModule({ providers: [ provideAuth( { config: { authority: 'something' } }, @@ -79,14 +79,15 @@ describe('provideAuth', () => { }, ], }).compileComponents(); - })); + }); it('should provide APP_INITIALIZER config', () => { const config = TestBed.inject(APP_INITIALIZER); - expect(config.length) - .withContext('Expected an APP_INITIALIZER to be registered') - .toBe(1); + expect( + config.length, + 'Expected an APP_INITIALIZER to be registered' + ).toBe(1); expect(oidcSecurityServiceMock.checkAuthMultiple).toHaveBeenCalledTimes( 1 ); diff --git a/src/provide-auth.ts b/src/provide-auth.ts index 1730034..3f12f7f 100644 --- a/src/provide-auth.ts +++ b/src/provide-auth.ts @@ -1,15 +1,11 @@ +import type { Provider } from 'injection-js'; import { - APP_INITIALIZER, - EnvironmentProviders, - makeEnvironmentProviders, - Provider, -} from 'injection-js'; -import { - createStaticLoader, PASSED_CONFIG, - PassedInitialConfig, + type PassedInitialConfig, + createStaticLoader, } from './auth-config'; import { StsConfigLoader } from './config/loader/config-loader'; +import { APP_INITIALIZER } from './injection'; import { AbstractLoggerService } from './logging/abstract-logger.service'; import { ConsoleLoggerService } from './logging/console-logger.service'; import { OidcSecurityService } from './oidc.security.service'; @@ -26,14 +22,14 @@ export interface AuthFeature { export function provideAuth( passedConfig: PassedInitialConfig, ...features: AuthFeature[] -): EnvironmentProviders { +): Provider[] { const providers = _provideAuth(passedConfig); for (const feature of features) { providers.push(...feature.ɵproviders); } - return makeEnvironmentProviders(providers); + return providers; } export function _provideAuth(passedConfig: PassedInitialConfig): Provider[] { diff --git a/src/public-events/event-types.ts b/src/public-events/event-types.ts index 54f35a9..41b5bba 100644 --- a/src/public-events/event-types.ts +++ b/src/public-events/event-types.ts @@ -1,17 +1,18 @@ +// biome-ignore lint/nursery/noEnum: export enum EventTypes { /** * This only works in the AppModule Constructor */ - ConfigLoaded, - CheckingAuth, - CheckingAuthFinished, - CheckingAuthFinishedWithError, - ConfigLoadingFailed, - CheckSessionReceived, - UserDataChanged, - NewAuthenticationResult, - TokenExpired, - IdTokenExpired, - SilentRenewStarted, - SilentRenewFailed, + ConfigLoaded = 0, + CheckingAuth = 1, + CheckingAuthFinished = 2, + CheckingAuthFinishedWithError = 3, + ConfigLoadingFailed = 4, + CheckSessionReceived = 5, + UserDataChanged = 6, + NewAuthenticationResult = 7, + TokenExpired = 8, + IdTokenExpired = 9, + SilentRenewStarted = 10, + SilentRenewFailed = 11, } diff --git a/src/public-events/public-events.service.spec.ts b/src/public-events/public-events.service.spec.ts index dcc5a78..f0e6766 100644 --- a/src/public-events/public-events.service.spec.ts +++ b/src/public-events/public-events.service.spec.ts @@ -1,5 +1,6 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { filter } from 'rxjs/operators'; +import { vi } from 'vitest'; import { EventTypes } from './event-types'; import { PublicEventsService } from './public-events.service'; @@ -20,7 +21,7 @@ describe('Events Service', () => { expect(eventsService).toBeTruthy(); }); - it('registering to single event with one event emit works', waitForAsync(() => { + it('registering to single event with one event emit works', async () => { eventsService.registerForEvents().subscribe((firedEvent) => { expect(firedEvent).toBeTruthy(); expect(firedEvent).toEqual({ @@ -29,9 +30,9 @@ describe('Events Service', () => { }); }); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' }); - })); + }); - it('registering to single event with multiple same event emit works', waitForAsync(() => { + it('registering to single event with multiple same event emit works', async () => { const spy = jasmine.createSpy('spy'); eventsService.registerForEvents().subscribe((firedEvent) => { @@ -50,9 +51,9 @@ describe('Events Service', () => { type: EventTypes.ConfigLoaded, value: { myKey: 'myValue2' }, }); - })); + }); - it('registering to single event with multiple emit works', waitForAsync(() => { + it('registering to single event with multiple emit works', async () => { eventsService .registerForEvents() .pipe(filter((x) => x.type === EventTypes.ConfigLoaded)) @@ -65,5 +66,5 @@ describe('Events Service', () => { }); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' }); eventsService.fireEvent(EventTypes.NewAuthenticationResult, true); - })); + }); }); diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..c0f1f96 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,27 @@ +export type RouteData = { + [key: string | symbol]: any; +}; + +export interface ActivatedRouteSnapshot { + data: RouteData; +} + +export interface RouterStateSnapshot { + url: string; +} + +export abstract class AbstractRouter { + navigateByUrl(url: string): void { + // TODO + // Implementation of navigating to a URL + } + + getCurrentNavigation(): any { + // TODO + // Implementation of getting the current navigation + return null; + } + + // TODO + parseUrl(url: string) {} +} diff --git a/src/storage/browser-storage.service.spec.ts b/src/storage/browser-storage.service.spec.ts index e40c86e..9fa3f97 100644 --- a/src/storage/browser-storage.service.spec.ts +++ b/src/storage/browser-storage.service.spec.ts @@ -1,6 +1,7 @@ -import { TestBed } from '@angular/core/testing'; -import { mockClass, mockProvider } from '../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; +import { mockClass, mockProvider } from '../testing/mock'; import { AbstractSecurityStorage } from './abstract-security-storage'; import { BrowserStorageService } from './browser-storage.service'; import { DefaultSessionStorageService } from './default-sessionstorage.service'; @@ -34,7 +35,7 @@ describe('BrowserStorageService', () => { it('returns null if there is no storage', () => { const config = { configId: 'configId1' }; - spyOn(service as any, 'hasStorage').and.returnValue(false); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(false); expect(service.read('anything', config)).toBeNull(); }); @@ -42,7 +43,7 @@ describe('BrowserStorageService', () => { it('returns null if getItem returns null', () => { const config = { configId: 'configId1' }; - spyOn(service as any, 'hasStorage').and.returnValue(true); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); const result = service.read('anything', config); @@ -52,10 +53,10 @@ describe('BrowserStorageService', () => { it('returns the item if getItem returns an item', () => { const config = { configId: 'configId1' }; - spyOn(service as any, 'hasStorage').and.returnValue(true); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); const returnValue = `{ "name":"John", "age":30, "city":"New York"}`; - spyOn(abstractSecurityStorage, 'read').and.returnValue(returnValue); + vi.spyOn(abstractSecurityStorage, 'read').mockReturnValue(returnValue); const result = service.read('anything', config); expect(result).toEqual(JSON.parse(returnValue)); @@ -66,24 +67,21 @@ describe('BrowserStorageService', () => { it('returns false if there is no storage', () => { const config = { configId: 'configId1' }; - spyOn(service as any, 'hasStorage').and.returnValue(false); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(false); - expect(service.write('anyvalue', config)).toBeFalse(); + expect(service.write('anyvalue', config)).toBeFalsy(); }); it('writes object correctly with configId', () => { const config = { configId: 'configId1' }; - spyOn(service as any, 'hasStorage').and.returnValue(true); - const writeSpy = spyOn( - abstractSecurityStorage, - 'write' - ).and.callThrough(); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); + const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')(); const result = service.write({ anyKey: 'anyvalue' }, config); expect(result).toBe(true); - expect(writeSpy).toHaveBeenCalledOnceWith( + expect(writeSpy).toHaveBeenCalledExactlyOnceWith( 'configId1', JSON.stringify({ anyKey: 'anyvalue' }) ); @@ -92,18 +90,15 @@ describe('BrowserStorageService', () => { it('writes null if item is falsy', () => { const config = { configId: 'configId1' }; - spyOn(service as any, 'hasStorage').and.returnValue(true); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); - const writeSpy = spyOn( - abstractSecurityStorage, - 'write' - ).and.callThrough(); + const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')(); const somethingFalsy = ''; const result = service.write(somethingFalsy, config); expect(result).toBe(true); - expect(writeSpy).toHaveBeenCalledOnceWith( + expect(writeSpy).toHaveBeenCalledExactlyOnceWith( 'configId1', JSON.stringify(null) ); @@ -114,41 +109,35 @@ describe('BrowserStorageService', () => { it('returns false if there is no storage', () => { const config = { configId: 'configId1' }; - spyOn(service as any, 'hasStorage').and.returnValue(false); - expect(service.remove('anything', config)).toBeFalse(); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(false); + expect(service.remove('anything', config)).toBeFalsy(); }); it('returns true if removeItem is called', () => { - spyOn(service as any, 'hasStorage').and.returnValue(true); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); const config = { configId: 'configId1' }; - const setItemSpy = spyOn( - abstractSecurityStorage, - 'remove' - ).and.callThrough(); + const setItemSpy = vi.spyOn(abstractSecurityStorage, 'remove')(); const result = service.remove('anyKey', config); expect(result).toBe(true); - expect(setItemSpy).toHaveBeenCalledOnceWith('anyKey'); + expect(setItemSpy).toHaveBeenCalledExactlyOnceWith('anyKey'); }); }); describe('clear', () => { it('returns false if there is no storage', () => { - spyOn(service as any, 'hasStorage').and.returnValue(false); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(false); const config = { configId: 'configId1' }; - expect(service.clear(config)).toBeFalse(); + expect(service.clear(config)).toBeFalsy(); }); it('returns true if clear is called', () => { - spyOn(service as any, 'hasStorage').and.returnValue(true); + vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); - const setItemSpy = spyOn( - abstractSecurityStorage, - 'clear' - ).and.callThrough(); + const setItemSpy = vi.spyOn(abstractSecurityStorage, 'clear')(); const config = { configId: 'configId1' }; const result = service.clear(config); @@ -161,7 +150,7 @@ describe('BrowserStorageService', () => { describe('hasStorage', () => { it('returns false if there is no storage', () => { (Storage as any) = undefined; - expect((service as any).hasStorage()).toBeFalse(); + expect((service as any).hasStorage()).toBeFalsy(); Storage = Storage; }); }); diff --git a/src/storage/default-localstorage.service.spec.ts b/src/storage/default-localstorage.service.spec.ts index 319d609..5009d4a 100644 --- a/src/storage/default-localstorage.service.spec.ts +++ b/src/storage/default-localstorage.service.spec.ts @@ -1,4 +1,5 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { DefaultLocalStorageService } from './default-localstorage.service'; describe('DefaultLocalStorageService', () => { @@ -20,37 +21,37 @@ describe('DefaultLocalStorageService', () => { describe('read', () => { it('should call localstorage.getItem', () => { - const spy = spyOn(localStorage, 'getItem'); + const spy = vi.spyOn(localStorage, 'getItem'); service.read('henlo'); - expect(spy).toHaveBeenCalledOnceWith('henlo'); + expect(spy).toHaveBeenCalledExactlyOnceWith('henlo'); }); }); describe('write', () => { it('should call localstorage.setItem', () => { - const spy = spyOn(localStorage, 'setItem'); + const spy = vi.spyOn(localStorage, 'setItem'); service.write('henlo', 'furiend'); - expect(spy).toHaveBeenCalledOnceWith('henlo', 'furiend'); + expect(spy).toHaveBeenCalledExactlyOnceWith('henlo', 'furiend'); }); }); describe('remove', () => { it('should call localstorage.removeItem', () => { - const spy = spyOn(localStorage, 'removeItem'); + const spy = vi.spyOn(localStorage, 'removeItem'); service.remove('henlo'); - expect(spy).toHaveBeenCalledOnceWith('henlo'); + expect(spy).toHaveBeenCalledExactlyOnceWith('henlo'); }); }); describe('clear', () => { it('should call localstorage.clear', () => { - const spy = spyOn(localStorage, 'clear'); + const spy = vi.spyOn(localStorage, 'clear'); service.clear(); diff --git a/src/storage/default-sessionstorage.service.spec.ts b/src/storage/default-sessionstorage.service.spec.ts index 73f6cb6..6f904d7 100644 --- a/src/storage/default-sessionstorage.service.spec.ts +++ b/src/storage/default-sessionstorage.service.spec.ts @@ -1,4 +1,5 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { DefaultSessionStorageService } from './default-sessionstorage.service'; describe('DefaultSessionStorageService', () => { @@ -20,37 +21,37 @@ describe('DefaultSessionStorageService', () => { describe('read', () => { it('should call sessionstorage.getItem', () => { - const spy = spyOn(sessionStorage, 'getItem'); + const spy = vi.spyOn(sessionStorage, 'getItem'); service.read('henlo'); - expect(spy).toHaveBeenCalledOnceWith('henlo'); + expect(spy).toHaveBeenCalledExactlyOnceWith('henlo'); }); }); describe('write', () => { it('should call sessionstorage.setItem', () => { - const spy = spyOn(sessionStorage, 'setItem'); + const spy = vi.spyOn(sessionStorage, 'setItem'); service.write('henlo', 'furiend'); - expect(spy).toHaveBeenCalledOnceWith('henlo', 'furiend'); + expect(spy).toHaveBeenCalledExactlyOnceWith('henlo', 'furiend'); }); }); describe('remove', () => { it('should call sessionstorage.removeItem', () => { - const spy = spyOn(sessionStorage, 'removeItem'); + const spy = vi.spyOn(sessionStorage, 'removeItem'); service.remove('henlo'); - expect(spy).toHaveBeenCalledOnceWith('henlo'); + expect(spy).toHaveBeenCalledExactlyOnceWith('henlo'); }); }); describe('clear', () => { it('should call sessionstorage.clear', () => { - const spy = spyOn(sessionStorage, 'clear'); + const spy = vi.spyOn(sessionStorage, 'clear'); service.clear(); diff --git a/src/storage/storage-persistence.service.spec.ts b/src/storage/storage-persistence.service.spec.ts index 204fee1..33f77c1 100644 --- a/src/storage/storage-persistence.service.spec.ts +++ b/src/storage/storage-persistence.service.spec.ts @@ -1,5 +1,6 @@ -import { TestBed } from '@angular/core/testing'; -import { mockProvider } from '../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; +import { mockProvider } from '../testing/mock'; import { BrowserStorageService } from './browser-storage.service'; import { StoragePersistenceService } from './storage-persistence.service'; @@ -25,16 +26,16 @@ describe('Storage Persistence Service', () => { describe('read', () => { it('reads from oidcSecurityStorage with configId', () => { const config = { configId: 'configId1' }; - const spy = spyOn(securityStorage, 'read'); + const spy = vi.spyOn(securityStorage, 'read'); service.read('authNonce', config); - expect(spy).toHaveBeenCalledOnceWith('authNonce', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authNonce', config); }); it('returns undefined (not throws exception) if key to read is not present on config', () => { const config = { configId: 'configId1' }; - spyOn(securityStorage, 'read').and.returnValue({ some: 'thing' }); + vi.spyOn(securityStorage, 'read').mockReturnValue({ some: 'thing' }); const result = service.read('authNonce', config); expect(result).toBeUndefined(); @@ -44,13 +45,13 @@ describe('Storage Persistence Service', () => { describe('write', () => { it('writes to oidcSecurityStorage with correct key and correct config', () => { const config = { configId: 'configId1' }; - const readSpy = spyOn(securityStorage, 'read'); - const writeSpy = spyOn(securityStorage, 'write'); + const readSpy = vi.spyOn(securityStorage, 'read'); + const writeSpy = vi.spyOn(securityStorage, 'write'); service.write('authNonce', 'anyValue', config); - expect(readSpy).toHaveBeenCalledOnceWith('authNonce', config); - expect(writeSpy).toHaveBeenCalledOnceWith( + expect(readSpy).toHaveBeenCalledExactlyOnceWith('authNonce', config); + expect(writeSpy).toHaveBeenCalledExactlyOnceWith( { authNonce: 'anyValue' }, config ); @@ -60,32 +61,32 @@ describe('Storage Persistence Service', () => { describe('remove', () => { it('should remove key from config', () => { const config = { configId: 'configId1' }; - const readSpy = spyOn(securityStorage, 'read').and.returnValue({ + const readSpy = vi.spyOn(securityStorage, 'read').mockReturnValue({ authNonce: 'anyValue', }); - const writeSpy = spyOn(securityStorage, 'write'); + const writeSpy = vi.spyOn(securityStorage, 'write'); service.remove('authNonce', config); - expect(readSpy).toHaveBeenCalledOnceWith('authNonce', config); - expect(writeSpy).toHaveBeenCalledOnceWith({}, config); + expect(readSpy).toHaveBeenCalledExactlyOnceWith('authNonce', config); + expect(writeSpy).toHaveBeenCalledExactlyOnceWith({}, config); }); it('does not crash when read with configId returns null', () => { const config = { configId: 'configId1' }; - const readSpy = spyOn(securityStorage, 'read').and.returnValue(null); - const writeSpy = spyOn(securityStorage, 'write'); + const readSpy = vi.spyOn(securityStorage, 'read').mockReturnValue(null); + const writeSpy = vi.spyOn(securityStorage, 'write'); service.remove('authNonce', config); - expect(readSpy).toHaveBeenCalledOnceWith('authNonce', config); - expect(writeSpy).toHaveBeenCalledOnceWith({}, config); + expect(readSpy).toHaveBeenCalledExactlyOnceWith('authNonce', config); + expect(writeSpy).toHaveBeenCalledExactlyOnceWith({}, config); }); }); describe('clear', () => { it('should call oidcSecurityStorage.clear()', () => { - const clearSpy = spyOn(securityStorage, 'clear'); + const clearSpy = vi.spyOn(securityStorage, 'clear'); service.clear({}); @@ -96,49 +97,58 @@ describe('Storage Persistence Service', () => { describe('resetStorageFlowData', () => { it('resets the correct values', () => { const config = { configId: 'configId1' }; - const spy = spyOn(service, 'remove'); + const spy = vi.spyOn(service, 'remove'); service.resetStorageFlowData(config); expect(spy).toHaveBeenCalledTimes(10); - expect(spy.calls.argsFor(0)).toEqual(['session_state', config]); - expect(spy.calls.argsFor(1)).toEqual([ + expect(vi.mocked(spy).mock.calls[0]).toEqual(['session_state', config]); + expect(vi.mocked(spy).mock.calls[1]).toEqual([ 'storageSilentRenewRunning', config, ]); - expect(spy.calls.argsFor(2)).toEqual([ + expect(vi.mocked(spy).mock.calls[2]).toEqual([ 'storageCodeFlowInProgress', config, ]); - expect(spy.calls.argsFor(3)).toEqual(['codeVerifier', config]); - expect(spy.calls.argsFor(4)).toEqual(['userData', config]); - expect(spy.calls.argsFor(5)).toEqual([ + expect(vi.mocked(spy).mock.calls[3]).toEqual(['codeVerifier', config]); + expect(vi.mocked(spy).mock.calls[4]).toEqual(['userData', config]); + expect(vi.mocked(spy).mock.calls[5]).toEqual([ 'storageCustomParamsAuthRequest', config, ]); - expect(spy.calls.argsFor(6)).toEqual(['access_token_expires_at', config]); - expect(spy.calls.argsFor(7)).toEqual([ + expect(vi.mocked(spy).mock.calls[6]).toEqual([ + 'access_token_expires_at', + config, + ]); + expect(vi.mocked(spy).mock.calls[7]).toEqual([ 'storageCustomParamsRefresh', config, ]); - expect(spy.calls.argsFor(8)).toEqual([ + expect(vi.mocked(spy).mock.calls[8]).toEqual([ 'storageCustomParamsEndSession', config, ]); - expect(spy.calls.argsFor(9)).toEqual(['reusable_refresh_token', config]); + expect(vi.mocked(spy).mock.calls[9]).toEqual([ + 'reusable_refresh_token', + config, + ]); }); }); describe('resetAuthStateInStorage', () => { it('resets the correct values', () => { const config = { configId: 'configId1' }; - const spy = spyOn(service, 'remove'); + const spy = vi.spyOn(service, 'remove'); service.resetAuthStateInStorage(config); - expect(spy.calls.argsFor(0)).toEqual(['authzData', config]); - expect(spy.calls.argsFor(1)).toEqual(['reusable_refresh_token', config]); - expect(spy.calls.argsFor(2)).toEqual(['authnResult', config]); + expect(vi.mocked(spy).mock.calls[0]).toEqual(['authzData', config]); + expect(vi.mocked(spy).mock.calls[1]).toEqual([ + 'reusable_refresh_token', + config, + ]); + expect(vi.mocked(spy).mock.calls[2]).toEqual(['authnResult', config]); }); }); @@ -146,41 +156,45 @@ describe('Storage Persistence Service', () => { it('get calls oidcSecurityStorage.read with correct key and returns the value', () => { const returnValue = { authzData: 'someValue' }; const config = { configId: 'configId1' }; - const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); + const spy = vi + .spyOn(securityStorage, 'read') + .mockReturnValue(returnValue); const result = service.getAccessToken(config); expect(result).toBe('someValue'); - expect(spy).toHaveBeenCalledOnceWith('authzData', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authzData', config); }); it('get calls oidcSecurityStorage.read with correct key and returns null', () => { - const spy = spyOn(securityStorage, 'read').and.returnValue(null); + const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null); const config = { configId: 'configId1' }; const result = service.getAccessToken(config); expect(result).toBeFalsy(); - expect(spy).toHaveBeenCalledOnceWith('authzData', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authzData', config); }); }); describe('getIdToken', () => { it('get calls oidcSecurityStorage.read with correct key and returns the value', () => { const returnValue = { authnResult: { id_token: 'someValue' } }; - const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); + const spy = vi + .spyOn(securityStorage, 'read') + .mockReturnValue(returnValue); const config = { configId: 'configId1' }; const result = service.getIdToken(config); expect(result).toBe('someValue'); - expect(spy).toHaveBeenCalledOnceWith('authnResult', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config); }); it('get calls oidcSecurityStorage.read with correct key and returns null', () => { - const spy = spyOn(securityStorage, 'read').and.returnValue(null); + const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null); const config = { configId: 'configId1' }; const result = service.getIdToken(config); expect(result).toBeFalsy(); - expect(spy).toHaveBeenCalledOnceWith('authnResult', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config); }); }); @@ -188,32 +202,36 @@ describe('Storage Persistence Service', () => { it('get calls oidcSecurityStorage.read with correct key and returns the value', () => { const returnValue = { authnResult: { id_token: 'someValue' } }; const config = { configId: 'configId1' }; - const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); + const spy = vi + .spyOn(securityStorage, 'read') + .mockReturnValue(returnValue); const result = service.getAuthenticationResult(config); expect(result.id_token).toBe('someValue'); - expect(spy).toHaveBeenCalledOnceWith('authnResult', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config); }); it('get calls oidcSecurityStorage.read with correct key and returns null', () => { - const spy = spyOn(securityStorage, 'read').and.returnValue(null); + const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null); const config = { configId: 'configId1' }; const result = service.getAuthenticationResult(config); expect(result).toBeFalsy(); - expect(spy).toHaveBeenCalledOnceWith('authnResult', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config); }); }); describe('getRefreshToken', () => { it('get calls oidcSecurityStorage.read with correct key and returns the value (refresh token with mandatory rotation - default)', () => { const returnValue = { authnResult: { refresh_token: 'someValue' } }; - const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); + const spy = vi + .spyOn(securityStorage, 'read') + .mockReturnValue(returnValue); const config = { configId: 'configId1' }; const result = service.getRefreshToken(config); expect(result).toBe('someValue'); - expect(spy).toHaveBeenCalledOnceWith('authnResult', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config); }); it('get calls oidcSecurityStorage.read with correct key and returns the value (refresh token without rotation)', () => { @@ -222,12 +240,12 @@ describe('Storage Persistence Service', () => { configId: 'configId1', allowUnsafeReuseRefreshToken: true, }; - const spy = spyOn(securityStorage, 'read'); + const spy = vi.spyOn(securityStorage, 'read'); spy .withArgs('reusable_refresh_token', config) - .and.returnValue(returnValue); - spy.withArgs('authnResult', config).and.returnValue(undefined); + .mockReturnValue(returnValue); + spy.withArgs('authnResult', config).mockReturnValue(undefined); const result = service.getRefreshToken(config); expect(result).toBe(returnValue.reusable_refresh_token); @@ -238,21 +256,23 @@ describe('Storage Persistence Service', () => { it('get calls oidcSecurityStorage.read with correct key and returns null', () => { const returnValue = { authnResult: { NO_refresh_token: 'someValue' } }; - const spy = spyOn(securityStorage, 'read').and.returnValue(returnValue); + const spy = vi + .spyOn(securityStorage, 'read') + .mockReturnValue(returnValue); const config = { configId: 'configId1' }; const result = service.getRefreshToken(config); expect(result).toBeUndefined(); - expect(spy).toHaveBeenCalledOnceWith('authnResult', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config); }); it('get calls oidcSecurityStorage.read with correct key and returns null', () => { - const spy = spyOn(securityStorage, 'read').and.returnValue(null); + const spy = vi.spyOn(securityStorage, 'read').mockReturnValue(null); const config = { configId: 'configId1' }; const result = service.getRefreshToken(config); expect(result).toBeUndefined(); - expect(spy).toHaveBeenCalledOnceWith('authnResult', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('authnResult', config); }); }); }); diff --git a/src/storage/storage-persistence.service.ts b/src/storage/storage-persistence.service.ts index 5f1cea2..ea1adf9 100644 --- a/src/storage/storage-persistence.service.ts +++ b/src/storage/storage-persistence.service.ts @@ -1,6 +1,6 @@ -import { inject, Injectable } from 'injection-js'; -import { AuthResult } from '../flows/callback-context'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import { inject } from 'injection-js'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { AuthResult } from '../flows/callback-context'; import { BrowserStorageService } from './browser-storage.service'; export type StorageKeys = @@ -24,7 +24,6 @@ export type StorageKeys = | 'jwtKeys' | 'popupauth'; - export class StoragePersistenceService { private readonly browserStorageService = inject(BrowserStorageService); diff --git a/test/create-retriable-stream.helper.ts b/src/testing/create-retriable-stream.helper.ts similarity index 84% rename from test/create-retriable-stream.helper.ts rename to src/testing/create-retriable-stream.helper.ts index ec6fa36..6b339de 100644 --- a/test/create-retriable-stream.helper.ts +++ b/src/testing/create-retriable-stream.helper.ts @@ -1,4 +1,4 @@ -import { Observable, of } from 'rxjs'; +import { type Observable, of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; // Create retriable observable stream to test retry / retryWhen. Credits to: @@ -6,7 +6,7 @@ import { switchMap } from 'rxjs/operators'; export const createRetriableStream = (...resp$: any): Observable => { const fetchData: jasmine.Spy = jasmine.createSpy('fetchData'); - fetchData.and.returnValues(...resp$); + fetchData.mockReturnValues(...resp$); return of(null).pipe(switchMap((_) => fetchData())); }; diff --git a/src/testing/index.ts b/src/testing/index.ts new file mode 100644 index 0000000..37fb2b2 --- /dev/null +++ b/src/testing/index.ts @@ -0,0 +1,7 @@ +export { TestBed } from './testbed'; +export { + createSpyObj, + mockImplementationWhenArgsEqual, +} from './spy'; +export { createRetriableStream } from './create-retriable-stream.helper'; +export { MockRouter, mockRouterProvider } from './router'; diff --git a/src/test.ts b/src/testing/init-test.ts similarity index 63% rename from src/test.ts rename to src/testing/init-test.ts index b4d91d6..8de1e00 100644 --- a/src/test.ts +++ b/src/testing/init-test.ts @@ -1,8 +1,4 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js'; -import 'zone.js/testing'; -import { getTestBed } from 'injection-js/testing'; +import { getTestBed } from '@/testing/testbed'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting, diff --git a/test/auto-mock.ts b/src/testing/mock.ts similarity index 78% rename from test/auto-mock.ts rename to src/testing/mock.ts index 231ba4a..cc9f692 100644 --- a/test/auto-mock.ts +++ b/src/testing/mock.ts @@ -1,11 +1,11 @@ -import { Provider } from 'injection-js'; +import type { Provider } from 'injection-js'; export function mockClass(obj: new (...args: any[]) => T): any { const keys = Object.getOwnPropertyNames(obj.prototype); const allMethods = keys.filter((key) => { try { return typeof obj.prototype[key] === 'function'; - } catch (error) { + } catch { return false; } }); @@ -13,21 +13,20 @@ export function mockClass(obj: new (...args: any[]) => T): any { const mockedClass = class T {}; - allMethods.forEach( - (method: string) => - ((mockedClass.prototype as any)[method] = (): void => { - return; - }) - ); + for (const method of allMethods) { + (mockedClass.prototype as any)[method] = (): void => { + return; + }; + } - allProperties.forEach((method) => { + for (const method of allProperties) { Object.defineProperty(mockedClass.prototype, method, { get() { return ''; }, configurable: true, }); - }); + } return mockedClass; } diff --git a/src/testing/router.ts b/src/testing/router.ts new file mode 100644 index 0000000..908b70c --- /dev/null +++ b/src/testing/router.ts @@ -0,0 +1,12 @@ +import type { Provider } from 'injection-js'; +import { AbstractRouter } from 'oidc-client-rx'; + +// TODO +export class MockRouter extends AbstractRouter {} + +export function mockRouterProvider(): Provider { + return { + useClass: MockRouter, + provide: AbstractRouter, // This is the token that will be injected into components + }; +} diff --git a/src/testing/spy.ts b/src/testing/spy.ts new file mode 100644 index 0000000..5cdaa9b --- /dev/null +++ b/src/testing/spy.ts @@ -0,0 +1,53 @@ +import { isEqual } from 'lodash-es'; +import { type MockInstance, vi } from 'vitest'; + +export function createSpyObj(baseName: string, methods: (keyof T)[]): T { + const SpyClass = new Function( + `return class Spy${baseName} { + constructor() {} + }` + ) as new () => T; + + const spyObj = new SpyClass(); + + for (const method of methods) { + Object.defineProperty(spyObj, method, { + value: vi.fn(), + writable: true, + }); + } + + return spyObj; +} + +export function mockImplementationWhenArgsEqual>( + mockInstance: M, + whenArgs: Parameters ? T : never>, + implementation: Exclude, undefined> +): M { + const spyImpl = mockInstance.getMockImplementation()!; + + return mockInstance.mockImplementation((...args) => { + if (isEqual(args, whenArgs)) { + return implementation(...args); + } + return spyImpl?.(...args); + }); +} + +export function mockImplementationWhenArgs>( + mockInstance: M, + whenArgs: ( + ...args: Parameters ? T : never> + ) => boolean, + implementation: Exclude, undefined> +): M { + const spyImpl = mockInstance.getMockImplementation()!; + + return mockInstance.mockImplementation((...args) => { + if (isEqual(args, whenArgs)) { + return implementation(...args); + } + return spyImpl?.(...args); + }); +} diff --git a/src/testing/testbed.ts b/src/testing/testbed.ts new file mode 100644 index 0000000..1339522 --- /dev/null +++ b/src/testing/testbed.ts @@ -0,0 +1,90 @@ +import { + type InjectionToken, + type Injector, + type Provider, + ReflectiveInjector, + type Type, +} from 'injection-js'; +import { setCurrentInjector } from 'injection-js/lib/injector_compatibility'; + +export interface TestModuleMetadata { + providers?: Provider[]; + imports?: ((parentInjector: Injector) => Injector)[]; +} + +export class TestBed { + private injector: ReflectiveInjector; + private providers: Provider[] = []; + private imports: Injector[] = []; + + constructor(metadata: TestModuleMetadata = {}) { + const providers = metadata.providers ?? []; + const imports = metadata.imports ?? []; + this.injector = ReflectiveInjector.resolveAndCreate(providers); + this.imports = imports.map((importFn) => importFn(this.injector)); + } + + static #instance?: TestBed; + + static configureTestingModule(metadata: TestModuleMetadata = {}) { + const newTestBed = new TestBed(metadata); + TestBed.#instance = newTestBed; + + return newTestBed; + } + + /** + * 在 TestBed 的注入上下文中运行函数 + */ + static runInInjectionContext(fn: () => T): T { + const injector = TestBed.#instance?.injector; + if (!injector) { + throw new Error( + 'TestBed is not configured. Call configureTestingModule first.' + ); + } + + // 保存当前的注入器 + const previousInjector = setCurrentInjector(injector); + + try { + // 在注入上下文中执行函数 + return fn(); + } finally { + // 恢复之前的注入器 + setCurrentInjector(previousInjector); + } + } + + compileComponents(): Promise { + return Promise.resolve(); + } + + static get instance(): TestBed { + if (!TestBed.#instance) { + throw new Error('TestBest.configureTestingModule should be called first'); + } + return TestBed.#instance; + } + + static get(token: Type | InjectionToken): T; + static get(token: any): any; + static get(token: unknown): any { + const g = TestBed.instance; + return g.injector.get(token); + } + + static inject(token: Type | InjectionToken): T; + static inject(token: any): any; + static inject(token: unknown): any { + return TestBed.get(token as any); + } + + static [Symbol.dispose]() { + TestBed.#instance = undefined; + } +} + +export function getTestBed() { + return TestBed.instance; +} diff --git a/src/user-data/user-service.spec.ts b/src/user-data/user-service.spec.ts index bdf8210..f525ade 100644 --- a/src/user-data/user-service.spec.ts +++ b/src/user-data/user-service.spec.ts @@ -1,13 +1,14 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { Observable, of, throwError } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; -import { createRetriableStream } from '../../test/create-retriable-stream.helper'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; +import { Observable, lastValueFrom, of, throwError } from 'rxjs'; +import { vi } from 'vitest'; import { DataService } from '../api/data.service'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { LoggerService } from '../logging/logger.service'; import { EventTypes } from '../public-events/event-types'; import { PublicEventsService } from '../public-events/public-events.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { createRetriableStream } from '../testing/create-retriable-stream.helper'; +import { mockProvider } from '../testing/mock'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { PlatformProvider } from '../utils/platform-provider/platform.provider'; import { TokenHelperService } from '../utils/tokenHelper/token-helper.service'; @@ -38,9 +39,6 @@ describe('User Service', () => { FlowHelper, ], }); - }); - - beforeEach(() => { loggerService = TestBed.inject(LoggerService); userService = TestBed.inject(UserService); storagePersistenceService = TestBed.inject(StoragePersistenceService); @@ -53,11 +51,11 @@ describe('User Service', () => { }); it('public authorize$ is observable$', () => { - expect(userService.userData$).toEqual(jasmine.any(Observable)); + expect(userService.userData$).toBeInstanceOf(Observable); }); describe('getAndPersistUserDataInStore', () => { - it('if not currentFlow is NOT id Token or Code flow, return decoded ID Token - passed as argument', waitForAsync(() => { + it('if not currentFlow is NOT id Token or Code flow, return decoded ID Token - passed as argument', async () => { const isRenewProcess = false; const idToken = ''; const decodedIdToken = 'decodedIdToken'; @@ -68,24 +66,24 @@ describe('User Service', () => { configId: 'configId1', } as OpenIdConfiguration; - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( userDataInstore ); - userService - .getAndPersistUserDataInStore( + const token = await lastValueFrom( + userService.getAndPersistUserDataInStore( config, [config], isRenewProcess, idToken, decodedIdToken ) - .subscribe((token) => { - expect(decodedIdToken).toBe(token); - }); - })); + ); - it('if not currentFlow is NOT id Token or Code flow, "setUserDataToStore" is called with the decodedIdToken', waitForAsync(() => { + expect(decodedIdToken).toBe(token); + }); + + it('if not currentFlow is NOT id Token or Code flow, "setUserDataToStore" is called with the decodedIdToken', async () => { const isRenewProcess = false; const idToken = ''; const decodedIdToken = 'decodedIdToken'; @@ -96,27 +94,27 @@ describe('User Service', () => { configId: 'configId1', } as OpenIdConfiguration; - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( userDataInstore ); - spyOn(userService, 'setUserDataToStore'); + vi.spyOn(userService, 'setUserDataToStore'); - userService - .getAndPersistUserDataInStore( + const token = await lastValueFrom( + userService.getAndPersistUserDataInStore( config, [config], isRenewProcess, idToken, decodedIdToken ) - .subscribe((token) => { - expect(decodedIdToken).toBe(token); - }); + ); + + expect(decodedIdToken).toBe(token); expect(userService.setUserDataToStore).toHaveBeenCalled(); - })); + }); - it('if not currentFlow is id token or code flow with renewProcess going -> return existing data from storage', waitForAsync(() => { + it('if not currentFlow is id token or code flow with renewProcess going -> return existing data from storage', async () => { const isRenewProcess = true; const idToken = ''; const decodedIdToken = 'decodedIdToken'; @@ -127,24 +125,24 @@ describe('User Service', () => { configId: 'configId1', } as OpenIdConfiguration; - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( userDataInstore ); - userService - .getAndPersistUserDataInStore( + const token = await lastValueFrom( + userService.getAndPersistUserDataInStore( config, [config], isRenewProcess, idToken, decodedIdToken ) - .subscribe((token) => { - expect(userDataInstore).toBe(token); - }); - })); + ); - it('if not currentFlow is id token or code flow and not renewProcess --> ask server for data', waitForAsync(() => { + expect(userDataInstore).toBe(token); + }); + + it('if not currentFlow is id token or code flow and not renewProcess --> ask server for data', async () => { const isRenewProcess = false; const idToken = ''; const decodedIdToken = 'decodedIdToken'; @@ -156,32 +154,31 @@ describe('User Service', () => { configId: 'configId1', } as OpenIdConfiguration; - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( userDataInstore ); - const spy = spyOn( - userService as any, - 'getIdentityUserData' - ).and.returnValue(of(userDataFromSts)); + const spy = vi + .spyOn(userService as any, 'getIdentityUserData') + .mockReturnValue(of(userDataFromSts)); - userService - .getAndPersistUserDataInStore( + const token = await lastValueFrom( + userService.getAndPersistUserDataInStore( config, [config], isRenewProcess, idToken, decodedIdToken ) - .subscribe((token) => { - expect(userDataFromSts).toEqual(token); - }); + ); + + expect(userDataFromSts).toEqual(token); expect(spy).toHaveBeenCalled(); - })); + }); it(`if not currentFlow is id token or code flow and not renewprocess --> ask server for data - --> logging if it has userdata`, waitForAsync(() => { + --> logging if it has userdata`, async () => { const isRenewProcess = false; const idToken = ''; const decodedIdToken = 'decodedIdToken'; @@ -193,38 +190,37 @@ describe('User Service', () => { configId: 'configId1', } as OpenIdConfiguration; - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( userDataInstore ); - const spy = spyOn( - userService as any, - 'getIdentityUserData' - ).and.returnValue(of(userDataFromSts)); + const spy = vi + .spyOn(userService as any, 'getIdentityUserData') + .mockReturnValue(of(userDataFromSts)); - spyOn(loggerService, 'logDebug'); - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(loggerService, 'logDebug'); + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'accessToken' ); - userService - .getAndPersistUserDataInStore( + const token = await lastValueFrom( + userService.getAndPersistUserDataInStore( config, [config], isRenewProcess, idToken, decodedIdToken ) - .subscribe((token) => { - expect(userDataFromSts).toEqual(token); - }); + ); + + expect(userDataFromSts).toEqual(token); expect(spy).toHaveBeenCalled(); expect(loggerService.logDebug).toHaveBeenCalled(); - })); + }); it(`if not currentFlow is id token or code flow and not renewprocess --> ask server for data - --> throwing Error if it has no userdata `, waitForAsync(() => { + --> throwing Error if it has no userdata `, async () => { const isRenewProcess = false; const idToken = ''; const decodedIdToken = { sub: 'decodedIdToken' }; @@ -236,40 +232,37 @@ describe('User Service', () => { configId: 'configId1', } as OpenIdConfiguration; - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( userDataInstore ); - const spyGetIdentityUserData = spyOn( - userService as any, - 'getIdentityUserData' - ).and.returnValue(of(userDataFromSts)); + const spyGetIdentityUserData = vi + .spyOn(userService as any, 'getIdentityUserData') + .mockReturnValue(of(userDataFromSts)); - spyOn(loggerService, 'logDebug'); - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(loggerService, 'logDebug'); + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'accessToken' ); - userService - .getAndPersistUserDataInStore( - config, - [config], - isRenewProcess, - idToken, - decodedIdToken - ) - .subscribe({ - error: (err) => { - expect(err.message).toEqual( - 'Received no user data, request failed' - ); - }, - }); + try { + await lastValueFrom( + userService.getAndPersistUserDataInStore( + config, + [config], + isRenewProcess, + idToken, + decodedIdToken + ) + ); + } catch (err: any) { + expect(err.message).toEqual('Received no user data, request failed'); + } expect(spyGetIdentityUserData).toHaveBeenCalled(); - })); + }); it(`if not currentFlow is id token or code flow and renewprocess and renewUserInfoAfterTokenRenew - --> ask server for data`, waitForAsync(() => { + --> ask server for data`, async () => { const isRenewProcess = true; const idToken = ''; const decodedIdToken = 'decodedIdToken'; @@ -282,28 +275,26 @@ describe('User Service', () => { configId: 'configId1', } as OpenIdConfiguration; - spyOn(userService, 'getUserDataFromStore').and.returnValue( + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( userDataInstore ); - const spy = spyOn( - userService as any, - 'getIdentityUserData' - ).and.returnValue(of(userDataFromSts)); + const spy = vi + .spyOn(userService as any, 'getIdentityUserData') + .mockReturnValue(of(userDataFromSts)); - userService - .getAndPersistUserDataInStore( + const token = await lastValueFrom( + userService.getAndPersistUserDataInStore( config, [config], isRenewProcess, idToken, decodedIdToken ) - .subscribe((token) => { - expect(userDataFromSts).toEqual(token); - }); + ); + expect(userDataFromSts).toEqual(token); expect(spy).toHaveBeenCalled(); - })); + }); }); describe('getUserDataFromStore', () => { @@ -317,9 +308,11 @@ describe('User Service', () => { it('returns value if there is data', () => { const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'read') - .withArgs('userData', config) - .and.returnValue('userData'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['userData', config], + () => 'userData' + ); const result = userService.getUserDataFromStore(config); expect(result).toBeTruthy(); @@ -329,10 +322,10 @@ describe('User Service', () => { describe('setUserDataToStore', () => { it('sets userData in storagePersistenceService', () => { const config = { configId: 'configId1' }; - const spy = spyOn(storagePersistenceService, 'write'); + const spy = vi.spyOn(storagePersistenceService, 'write'); userService.setUserDataToStore('userDataForTest', config, [config]); - expect(spy).toHaveBeenCalledOnceWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( 'userData', 'userDataForTest', config @@ -342,11 +335,11 @@ describe('User Service', () => { it('userDataInternal$ is called when userData is set', () => { const config = { configId: 'configId1' }; - const spy = spyOn((userService as any).userDataInternal$, 'next'); + const spy = vi.spyOn((userService as any).userDataInternal$, 'next'); userService.setUserDataToStore('userDataForTest', config, [config]); - expect(spy).toHaveBeenCalledOnceWith({ + expect(spy).toHaveBeenCalledExactlyOnceWith({ userData: 'userDataForTest', allUserData: [{ configId: 'configId1', userData: 'userDataForTest' }], }); @@ -354,11 +347,11 @@ describe('User Service', () => { it('eventService.fireEvent is called when userData is set', () => { const config = { configId: 'configId1' }; - const spy = spyOn(eventsService, 'fireEvent'); + const spy = vi.spyOn(eventsService, 'fireEvent'); userService.setUserDataToStore('userDataForTest', config, [config]); - expect(spy).toHaveBeenCalledOnceWith(EventTypes.UserDataChanged, { + expect(spy).toHaveBeenCalledExactlyOnceWith(EventTypes.UserDataChanged, { configId: 'configId1', userData: 'userDataForTest', }); @@ -368,20 +361,20 @@ describe('User Service', () => { describe('resetUserDataInStore', () => { it('resets userData sets null in storagePersistenceService', () => { const config = { configId: 'configId1' }; - const spy = spyOn(storagePersistenceService, 'remove'); + const spy = vi.spyOn(storagePersistenceService, 'remove'); userService.resetUserDataInStore(config, [config]); - expect(spy).toHaveBeenCalledOnceWith('userData', config); + expect(spy).toHaveBeenCalledExactlyOnceWith('userData', config); }); it('userDataInternal$ is called with null when userData is reset', () => { const config = { configId: 'configId1' }; - const spy = spyOn((userService as any).userDataInternal$, 'next'); + const spy = vi.spyOn((userService as any).userDataInternal$, 'next'); userService.resetUserDataInStore(config, [config]); - expect(spy).toHaveBeenCalledOnceWith({ + expect(spy).toHaveBeenCalledExactlyOnceWith({ userData: null, allUserData: [{ configId: 'configId1', userData: null }], }); @@ -389,11 +382,11 @@ describe('User Service', () => { it('eventService.fireEvent is called with null when userData is reset', () => { const config = { configId: 'configId1' }; - const spy = spyOn(eventsService, 'fireEvent'); + const spy = vi.spyOn(eventsService, 'fireEvent'); userService.resetUserDataInStore(config, [config]); - expect(spy).toHaveBeenCalledOnceWith(EventTypes.UserDataChanged, { + expect(spy).toHaveBeenCalledExactlyOnceWith(EventTypes.UserDataChanged, { configId: 'configId1', userData: null, }); @@ -402,12 +395,12 @@ describe('User Service', () => { describe('publishUserDataIfExists', () => { it('do nothing if no userData is stored', () => { - spyOn(userService, 'getUserDataFromStore').and.returnValue(''); - const observableSpy = spyOn( + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(''); + const observableSpy = vi.spyOn( (userService as any).userDataInternal$, 'next' ); - const eventSpy = spyOn(eventsService, 'fireEvent'); + const eventSpy = vi.spyOn(eventsService, 'fireEvent'); const config = { configId: 'configId1' }; userService.publishUserDataIfExists(config, [config]); @@ -417,8 +410,10 @@ describe('User Service', () => { }); it('userDataInternal is fired if userData exists with single config', () => { - spyOn(userService, 'getUserDataFromStore').and.returnValue('something'); - const observableSpy = spyOn( + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( + 'something' + ); + const observableSpy = vi.spyOn( (userService as any).userDataInternal$, 'next' ); @@ -426,7 +421,7 @@ describe('User Service', () => { userService.publishUserDataIfExists(config, [config]); - expect(observableSpy).toHaveBeenCalledOnceWith({ + expect(observableSpy).toHaveBeenCalledExactlyOnceWith({ userData: 'something', allUserData: [{ configId: 'configId1', userData: 'something' }], }); @@ -434,20 +429,24 @@ describe('User Service', () => { it('userDataInternal is fired if userData exists with multiple configs', () => { const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }]; - const observableSpy = spyOn( + const observableSpy = vi.spyOn( (userService as any).userDataInternal$, 'next' ); - spyOn(storagePersistenceService, 'read') - .withArgs('userData', allConfigs[0]) - .and.returnValue('somethingForConfig1') - .withArgs('userData', allConfigs[1]) - .and.returnValue('somethingForConfig2'); + mockImplementationWhenArgsEqual( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['userData', allConfigs[0]!], + () => 'somethingForConfig1' + ), + ['userData', allConfigs[1]!], + () => 'somethingForConfig2' + ); - userService.publishUserDataIfExists(allConfigs[0], allConfigs); + userService.publishUserDataIfExists(allConfigs[0]!, allConfigs); - expect(observableSpy).toHaveBeenCalledOnceWith({ + expect(observableSpy).toHaveBeenCalledExactlyOnceWith({ userData: null, allUserData: [ { configId: 'configId1', userData: 'somethingForConfig1' }, @@ -459,15 +458,20 @@ describe('User Service', () => { it('event service UserDataChanged is fired if userData exists', () => { const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }]; - spyOn(userService, 'getUserDataFromStore').and.returnValue('something'); - const eventSpy = spyOn(eventsService, 'fireEvent'); + vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue( + 'something' + ); + const eventSpy = vi.spyOn(eventsService, 'fireEvent'); - userService.publishUserDataIfExists(allConfigs[0], allConfigs); + userService.publishUserDataIfExists(allConfigs[0]!, allConfigs); - expect(eventSpy).toHaveBeenCalledOnceWith(EventTypes.UserDataChanged, { - configId: 'configId1', - userData: 'something', - }); + expect(eventSpy).toHaveBeenCalledExactlyOnceWith( + EventTypes.UserDataChanged, + { + configId: 'configId1', + userData: 'something', + } + ); }); }); @@ -482,7 +486,7 @@ describe('User Service', () => { 'anything' ); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('with no userDataSub returns false', () => { @@ -495,12 +499,12 @@ describe('User Service', () => { '' ); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('with idTokenSub and userDataSub not match logs and returns false', () => { const serviceAsAny = userService as any; - const loggerSpy = spyOn(loggerService, 'logDebug'); + const loggerSpy = vi.spyOn(loggerService, 'logDebug'); const config = { configId: 'configId1' }; const result = serviceAsAny.validateUserDataSubIdToken( @@ -509,8 +513,8 @@ describe('User Service', () => { 'something2' ); - expect(result).toBeFalse(); - expect(loggerSpy).toHaveBeenCalledOnceWith( + expect(result).toBeFalsy(); + expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( config, 'validateUserDataSubIdToken failed', 'something', @@ -520,95 +524,108 @@ describe('User Service', () => { }); describe('getIdentityUserData', () => { - it('does nothing if no authWellKnownEndPoints are set', waitForAsync(() => { + it('does nothing if no authWellKnownEndPoints are set', async () => { const config = { configId: 'configId1' }; const serviceAsAny = userService as any; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'accessToken' ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(null); - serviceAsAny.getIdentityUserData(config).subscribe({ - error: (err: any) => { - expect(err).toBeTruthy(); - }, - }); - })); - it('does nothing if no userInfoEndpoint is set', waitForAsync(() => { + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => null + ); + try { + await lastValueFrom(serviceAsAny.getIdentityUserData(config)); + } catch (err: any) { + expect(err).toBeTruthy(); + } + }); + + it('does nothing if no userInfoEndpoint is set', async () => { const config = { configId: 'configId1' }; const serviceAsAny = userService as any; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'accessToken' ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ userInfoEndpoint: null }); - serviceAsAny.getIdentityUserData(config).subscribe({ - error: (err: any) => { - expect(err).toBeTruthy(); - }, - }); - })); - it('gets userData if authwell and userInfoEndpoint is set', waitForAsync(() => { + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ userInfoEndpoint: null }) + ); + + try { + await lastValueFrom(serviceAsAny.getIdentityUserData(config)); + } catch (err: any) { + expect(err).toBeTruthy(); + } + }); + + it('gets userData if authwell and userInfoEndpoint is set', async () => { const config = { configId: 'configId1' }; const serviceAsAny = userService as any; - const spy = spyOn(dataService, 'get').and.returnValue(of({})); + const spy = vi.spyOn(dataService, 'get').mockReturnValue(of({})); - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'accessToken' ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ userInfoEndpoint: 'userInfoEndpoint' }); - serviceAsAny.getIdentityUserData(config).subscribe(() => { - expect(spy).toHaveBeenCalledOnceWith( - 'userInfoEndpoint', - config, - 'accessToken' - ); - }); - })); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ userInfoEndpoint: 'userInfoEndpoint' }) + ); + + await lastValueFrom(serviceAsAny.getIdentityUserData(config)); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'userInfoEndpoint', + config, + 'accessToken' + ); + }); }); - it('should retry once', waitForAsync(() => { + it('should retry once', async () => { const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'accessToken' ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ userInfoEndpoint: 'userInfoEndpoint' }); - spyOn(dataService, 'get').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ userInfoEndpoint: 'userInfoEndpoint' }) + ); + vi.spyOn(dataService, 'get').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), of(DUMMY_USER_DATA) ) ); - (userService as any).getIdentityUserData(config).subscribe({ - next: (res: any) => { - expect(res).toBeTruthy(); - expect(res).toEqual(DUMMY_USER_DATA); - }, - }); - })); + const res = await lastValueFrom( + (userService as any).getIdentityUserData(config) + ); - it('should retry twice', waitForAsync(() => { + expect(res).toBeTruthy(); + expect(res).toEqual(DUMMY_USER_DATA); + }); + + it('should retry twice', async () => { const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'accessToken' ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ userInfoEndpoint: 'userInfoEndpoint' }); - spyOn(dataService, 'get').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ userInfoEndpoint: 'userInfoEndpoint' }) + ); + vi.spyOn(dataService, 'get').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), throwError(() => new Error('Error')), @@ -616,24 +633,25 @@ describe('User Service', () => { ) ); - (userService as any).getIdentityUserData(config).subscribe({ - next: (res: any) => { - expect(res).toBeTruthy(); - expect(res).toEqual(DUMMY_USER_DATA); - }, - }); - })); + const res = await lastValueFrom( + (userService as any).getIdentityUserData(config) + ); + expect(res).toBeTruthy(); + expect(res).toEqual(DUMMY_USER_DATA); + }); - it('should fail after three tries', waitForAsync(() => { + it('should fail after three tries', async () => { const config = { configId: 'configId1' }; - spyOn(storagePersistenceService, 'getAccessToken').and.returnValue( + vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue( 'accessToken' ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ userInfoEndpoint: 'userInfoEndpoint' }); - spyOn(dataService, 'get').and.returnValue( + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ userInfoEndpoint: 'userInfoEndpoint' }) + ); + vi.spyOn(dataService, 'get').mockReturnValue( createRetriableStream( throwError(() => new Error('Error')), throwError(() => new Error('Error')), @@ -642,10 +660,10 @@ describe('User Service', () => { ) ); - (userService as any).getIdentityUserData(config).subscribe({ - error: (err: any) => { - expect(err).toBeTruthy(); - }, - }); - })); + try { + await lastValueFrom((userService as any).getIdentityUserData(config)); + } catch (err: any) { + expect(err).toBeTruthy(); + } + }); }); diff --git a/src/user-data/user.service.ts b/src/user-data/user.service.ts index 2ef4653..4d2597c 100644 --- a/src/user-data/user.service.ts +++ b/src/user-data/user.service.ts @@ -1,15 +1,15 @@ -import { inject, Injectable } from 'injection-js'; -import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { BehaviorSubject, type Observable, of, throwError } from 'rxjs'; import { map, retry, switchMap } from 'rxjs/operators'; import { DataService } from '../api/data.service'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { LoggerService } from '../logging/logger.service'; import { EventTypes } from '../public-events/event-types'; import { PublicEventsService } from '../public-events/public-events.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { TokenHelperService } from '../utils/tokenHelper/token-helper.service'; -import { ConfigUserDataResult, UserDataResult } from './userdata-result'; +import type { ConfigUserDataResult, UserDataResult } from './userdata-result'; const DEFAULT_USERRESULT = { userData: null, allUserData: [] }; @@ -44,13 +44,13 @@ export class UserService { idToken?: string, decodedIdToken?: any ): Observable { - idToken = + const _idToken = idToken || this.storagePersistenceService.getIdToken(currentConfiguration); - decodedIdToken = + const _decodedIdToken = decodedIdToken || this.tokenHelperService.getPayloadFromToken( - idToken, + _idToken, false, currentConfiguration ); @@ -74,16 +74,20 @@ export class UserService { `authCallback idToken flow with accessToken ${accessToken}` ); - this.setUserDataToStore(decodedIdToken, currentConfiguration, allConfigs); + this.setUserDataToStore( + _decodedIdToken, + currentConfiguration, + allConfigs + ); - return of(decodedIdToken); + return of(_decodedIdToken); } const { renewUserInfoAfterTokenRenew } = currentConfiguration; if (!isRenewProcess || renewUserInfoAfterTokenRenew || !haveUserData) { return this.getUserDataOidcFlowAndSave( - decodedIdToken.sub, + _decodedIdToken.sub, currentConfiguration, allConfigs ).pipe( @@ -93,7 +97,7 @@ export class UserService { 'Received user data: ', userData ); - if (!!userData) { + if (userData) { this.loggerService.logDebug( currentConfiguration, 'accessToken: ', @@ -101,11 +105,10 @@ export class UserService { ); return of(userData); - } else { - return throwError( - () => new Error('Received no user data, request failed') - ); } + return throwError( + () => new Error('Received no user data, request failed') + ); }) ); } @@ -178,16 +181,15 @@ export class UserService { this.setUserDataToStore(data, currentConfiguration, allConfigs); return data; - } else { - // something went wrong, user data sub does not match that from id_token - this.loggerService.logWarning( - currentConfiguration, - `User data sub does not match sub in id_token, resetting` - ); - this.resetUserDataInStore(currentConfiguration, allConfigs); - - return null; } + // something went wrong, user data sub does not match that from id_token + this.loggerService.logWarning( + currentConfiguration, + 'User data sub does not match sub in id_token, resetting' + ); + this.resetUserDataInStore(currentConfiguration, allConfigs); + + return null; }) ); } diff --git a/src/utils/crypto/crypto.service.spec.ts b/src/utils/crypto/crypto.service.spec.ts index 42f91e5..5f1f6cb 100644 --- a/src/utils/crypto/crypto.service.spec.ts +++ b/src/utils/crypto/crypto.service.spec.ts @@ -1,5 +1,6 @@ +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { DOCUMENT } from '../../dom'; -import { TestBed } from '@angular/core/testing'; import { CryptoService } from './crypto.service'; describe('CryptoService', () => { diff --git a/src/utils/equality/equality.service.spec.ts b/src/utils/equality/equality.service.spec.ts index 98f18f7..a074c9b 100644 --- a/src/utils/equality/equality.service.spec.ts +++ b/src/utils/equality/equality.service.spec.ts @@ -1,4 +1,5 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { IFrameService } from '../../iframe/existing-iframe.service'; import { EqualityService } from './equality.service'; diff --git a/src/utils/flowHelper/flow-helper.service.spec.ts b/src/utils/flowHelper/flow-helper.service.spec.ts index 52849ab..b294924 100644 --- a/src/utils/flowHelper/flow-helper.service.spec.ts +++ b/src/utils/flowHelper/flow-helper.service.spec.ts @@ -1,4 +1,5 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { FlowHelper } from './flow-helper.service'; describe('Flow Helper Service', () => { @@ -21,25 +22,25 @@ describe('Flow Helper Service', () => { it('isCurrentFlowCodeFlow returns false if current flow is not code flow', () => { const config = { responseType: 'id_token token', configId: 'configId1' }; - expect(flowHelper.isCurrentFlowCodeFlow(config)).toBeFalse(); + expect(flowHelper.isCurrentFlowCodeFlow(config)).toBeFalsy(); }); it('isCurrentFlowCodeFlow returns true if current flow is code flow', () => { const config = { responseType: 'code' }; - expect(flowHelper.isCurrentFlowCodeFlow(config)).toBeTrue(); + expect(flowHelper.isCurrentFlowCodeFlow(config)).toBeTruthy(); }); it('currentFlowIs returns true if current flow is code flow', () => { const config = { responseType: 'code' }; - expect(flowHelper.currentFlowIs('code', config)).toBeTrue(); + expect(flowHelper.currentFlowIs('code', config)).toBeTruthy(); }); it('currentFlowIs returns true if current flow is code flow (array)', () => { const config = { responseType: 'code' }; - expect(flowHelper.currentFlowIs(['code'], config)).toBeTrue(); + expect(flowHelper.currentFlowIs(['code'], config)).toBeTruthy(); }); it('currentFlowIs returns true if current flow is id_token token or code (array)', () => { @@ -47,13 +48,13 @@ describe('Flow Helper Service', () => { expect( flowHelper.currentFlowIs(['id_token token', 'code'], config) - ).toBeTrue(); + ).toBeTruthy(); }); it('currentFlowIs returns true if current flow is code flow', () => { const config = { responseType: 'id_token token' }; - expect(flowHelper.currentFlowIs('code', config)).toBeFalse(); + expect(flowHelper.currentFlowIs('code', config)).toBeFalsy(); }); it('isCurrentFlowImplicitFlowWithAccessToken return true if flow is "id_token token"', () => { @@ -61,7 +62,7 @@ describe('Flow Helper Service', () => { const result = flowHelper.isCurrentFlowImplicitFlowWithAccessToken(config); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); it('isCurrentFlowImplicitFlowWithAccessToken return false if flow is not "id_token token"', () => { @@ -69,7 +70,7 @@ describe('Flow Helper Service', () => { const result = flowHelper.isCurrentFlowImplicitFlowWithAccessToken(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('isCurrentFlowImplicitFlowWithoutAccessToken return true if flow is "id_token"', () => { @@ -79,7 +80,7 @@ describe('Flow Helper Service', () => { flowHelper as any ).isCurrentFlowImplicitFlowWithoutAccessToken(config); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); it('isCurrentFlowImplicitFlowWithoutAccessToken return false if flow is not "id_token token"', () => { @@ -89,7 +90,7 @@ describe('Flow Helper Service', () => { flowHelper as any ).isCurrentFlowImplicitFlowWithoutAccessToken(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('isCurrentFlowCodeFlowWithRefreshTokens return false if flow is not code flow', () => { @@ -97,7 +98,7 @@ describe('Flow Helper Service', () => { const result = flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('isCurrentFlowCodeFlowWithRefreshTokens return false if useRefreshToken is set to false', () => { @@ -105,7 +106,7 @@ describe('Flow Helper Service', () => { const result = flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); it('isCurrentFlowCodeFlowWithRefreshTokens return true if useRefreshToken is set to true and code flow', () => { @@ -113,59 +114,59 @@ describe('Flow Helper Service', () => { const result = flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); describe('isCurrentFlowAnyImplicitFlow', () => { it('returns true if currentFlowIsImplicitFlowWithAccessToken is true', () => { - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowImplicitFlowWithAccessToken' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( flowHelper as any, 'isCurrentFlowImplicitFlowWithoutAccessToken' - ).and.returnValue(false); + ).mockReturnValue(false); const result = flowHelper.isCurrentFlowAnyImplicitFlow({ configId: 'configId1', }); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); it('returns true if isCurrentFlowImplicitFlowWithoutAccessToken is true', () => { - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowImplicitFlowWithAccessToken' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( flowHelper as any, 'isCurrentFlowImplicitFlowWithoutAccessToken' - ).and.returnValue(true); + ).mockReturnValue(true); const result = flowHelper.isCurrentFlowAnyImplicitFlow({ configId: 'configId1', }); - expect(result).toBeTrue(); + expect(result).toBeTruthy(); }); it('returns false it is not any implicit flow', () => { - spyOn( + vi.spyOn( flowHelper, 'isCurrentFlowImplicitFlowWithAccessToken' - ).and.returnValue(false); - spyOn( + ).mockReturnValue(false); + vi.spyOn( flowHelper as any, 'isCurrentFlowImplicitFlowWithoutAccessToken' - ).and.returnValue(false); + ).mockReturnValue(false); const result = flowHelper.isCurrentFlowAnyImplicitFlow({ configId: 'configId1', }); - expect(result).toBeFalse(); + expect(result).toBeFalsy(); }); }); }); diff --git a/src/utils/flowHelper/flow-helper.service.ts b/src/utils/flowHelper/flow-helper.service.ts index f9454b4..91075c1 100644 --- a/src/utils/flowHelper/flow-helper.service.ts +++ b/src/utils/flowHelper/flow-helper.service.ts @@ -1,5 +1,5 @@ import { Injectable } from 'injection-js'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; @Injectable() export class FlowHelper { diff --git a/src/utils/platform-provider/platform-provider.spec.ts b/src/utils/platform-provider/platform-provider.spec.ts index 979a91c..1ad3fea 100644 --- a/src/utils/platform-provider/platform-provider.spec.ts +++ b/src/utils/platform-provider/platform-provider.spec.ts @@ -1,5 +1,6 @@ +import { TestBed } from '@/testing'; import { PLATFORM_ID } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; +import { vi } from 'vitest'; import { PlatformProvider } from './platform.provider'; describe('PlatformProvider Tests', () => { diff --git a/src/utils/redirect/redirect.service.spec.ts b/src/utils/redirect/redirect.service.spec.ts index 786e5ad..ed498c7 100644 --- a/src/utils/redirect/redirect.service.spec.ts +++ b/src/utils/redirect/redirect.service.spec.ts @@ -1,5 +1,6 @@ +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { DOCUMENT } from '../../dom'; -import { TestBed } from '@angular/core/testing'; import { RedirectService } from './redirect.service'; describe('Redirect Service Tests', () => { @@ -38,9 +39,9 @@ describe('Redirect Service Tests', () => { }); it('redirectTo sets window location href', () => { - const spy = spyOnProperty(myDocument.location, 'href', 'set'); + const spy = vi.spyOnProperty(myDocument.location, 'href', 'set'); service.redirectTo('anyurl'); - expect(spy).toHaveBeenCalledOnceWith('anyurl'); + expect(spy).toHaveBeenCalledExactlyOnceWith('anyurl'); }); }); diff --git a/src/utils/tokenHelper/token-helper.service.spec.ts b/src/utils/tokenHelper/token-helper.service.spec.ts index d3ee296..437aff5 100644 --- a/src/utils/tokenHelper/token-helper.service.spec.ts +++ b/src/utils/tokenHelper/token-helper.service.spec.ts @@ -1,6 +1,7 @@ -import { TestBed } from '@angular/core/testing'; -import { mockProvider } from '../../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { LoggerService } from '../../logging/logger.service'; +import { mockProvider } from '../../testing/mock'; import { TokenHelperService } from './token-helper.service'; describe('Token Helper Service', () => { diff --git a/src/utils/url/current-url.service.spec.ts b/src/utils/url/current-url.service.spec.ts index cd05d81..3a9d21b 100644 --- a/src/utils/url/current-url.service.spec.ts +++ b/src/utils/url/current-url.service.spec.ts @@ -1,5 +1,6 @@ +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { DOCUMENT } from '../../dom'; -import { TestBed } from '@angular/core/testing'; import { CurrentUrlService } from './current-url.service'; describe('CurrentUrlService with existing Url', () => { @@ -36,7 +37,7 @@ describe('CurrentUrlService with existing Url', () => { describe('getStateParamFromCurrentUrl', () => { it('returns null if there is no current URL', () => { - spyOn(service, 'getCurrentUrl').and.returnValue(null); + vi.spyOn(service, 'getCurrentUrl').mockReturnValue(null); const stateParam = service.getStateParamFromCurrentUrl(''); diff --git a/src/utils/url/url.service.spec.ts b/src/utils/url/url.service.spec.ts index 09ba88f..0100a71 100644 --- a/src/utils/url/url.service.spec.ts +++ b/src/utils/url/url.service.spec.ts @@ -1,10 +1,11 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../../test/auto-mock'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import { vi } from 'vitest'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { FlowsDataService } from '../../flows/flows-data.service'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; +import { mockProvider } from '../../testing/mock'; import { JwtWindowCryptoService } from '../../validation/jwt-window-crypto.service'; import { FlowHelper } from '../flowHelper/flow-helper.service'; import { UrlService } from './url.service'; @@ -59,7 +60,7 @@ describe('UrlService Tests', () => { const sut = service.getUrlWithoutQueryParameters(url); params.forEach((p) => { - expect(sut.searchParams.has(p.key)).toBeFalse(); + expect(sut.searchParams.has(p.key)).toBeFalsy(); }); }); }); @@ -90,7 +91,7 @@ describe('UrlService Tests', () => { it(`should return true for ${mu.toString()}`, () => { expect( service.queryParametersExist(expected, mu.searchParams) - ).toBeTrue(); + ).toBeTruthy(); }); }); @@ -98,7 +99,7 @@ describe('UrlService Tests', () => { it(`should return false for ${nmu.toString()}`, () => { expect( service.queryParametersExist(expected, nmu.searchParams) - ).toBeFalse(); + ).toBeFalsy(); }); }); }); @@ -130,7 +131,7 @@ describe('UrlService Tests', () => { ]; nonMatchingUrls.forEach((nmu) => { - expect(service.isCallbackFromSts(nmu.url, nmu.config)).toBeFalse(); + expect(service.isCallbackFromSts(nmu.url, nmu.config)).toBeFalsy(); }); }); @@ -271,9 +272,11 @@ describe('UrlService Tests', () => { const config = { configId: 'configId1', clientId: '' }; const authorizationEndpoint = 'authorizationEndpoint'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -294,9 +297,11 @@ describe('UrlService Tests', () => { }; const authorizationEndpoint = 'authorizationEndpoint'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -318,9 +323,11 @@ describe('UrlService Tests', () => { }; const authorizationEndpoint = 'authorizationEndpoint'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -347,9 +354,11 @@ describe('UrlService Tests', () => { testcustom: 'customvalue', }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint: 'http://example' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example' }) + ); const value = (service as any).createAuthorizeUrl( 'codeChallenge', // Code Flow @@ -384,11 +393,13 @@ describe('UrlService Tests', () => { config.scope = 'openid email profile'; config.configId = 'configId1'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -423,11 +434,13 @@ describe('UrlService Tests', () => { config.scope = 'openid email profile'; config.configId = 'configId1'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -465,11 +478,13 @@ describe('UrlService Tests', () => { config.hdParam = 'myHdParam'; config.configId = 'configId1'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -507,11 +522,13 @@ describe('UrlService Tests', () => { testcustom: 'customvalue', }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -552,11 +569,13 @@ describe('UrlService Tests', () => { t1: ';,/?:@&=+$', }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -594,11 +613,13 @@ describe('UrlService Tests', () => { }, }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -635,11 +656,13 @@ describe('UrlService Tests', () => { configId: 'configId1', }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -674,11 +697,13 @@ describe('UrlService Tests', () => { configId: 'configId1', }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -713,12 +738,14 @@ describe('UrlService Tests', () => { config.responseType = 'id_token token'; config.scope = 'openid email profile'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -752,11 +779,13 @@ describe('UrlService Tests', () => { config.scope = 'openid email profile'; config.configId = 'configId1'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example', - }); + }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -792,9 +821,11 @@ describe('UrlService Tests', () => { prompt: 'select_account', }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint: 'http://example' }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'http://example' }) + ); const value = (service as any).createAuthorizeUrl( '', // Implicit Flow @@ -835,11 +866,13 @@ describe('UrlService Tests', () => { const revocationEndpoint = 'http://example?cod=ddd'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ revocationEndpoint, - }); + }) + ); const value = service.createRevocationEndpointBodyAccessToken( 'mytoken', @@ -880,11 +913,13 @@ describe('UrlService Tests', () => { const revocationEndpoint = 'http://example?cod=ddd'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ revocationEndpoint, - }); + }) + ); const value = service.createRevocationEndpointBodyRefreshToken( 'mytoken', @@ -925,11 +960,13 @@ describe('UrlService Tests', () => { const revocationEndpoint = 'http://example?cod=ddd'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ revocationEndpoint, - }); + }) + ); const value = service.getRevocationEndpointUrl(config); @@ -952,11 +989,13 @@ describe('UrlService Tests', () => { const revocationEndpoint = 'http://example'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ revocationEndpoint, - }); + }) + ); const value = service.getRevocationEndpointUrl(config); @@ -966,11 +1005,13 @@ describe('UrlService Tests', () => { }); it('getRevocationEndpointUrl returns null when there is not revociationendpoint given', () => { - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', {}) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', {}], + () => ({ revocationEndpoint: null, - }); + }) + ); const value = service.getRevocationEndpointUrl({}); expect(value).toBeNull(); @@ -1002,22 +1043,22 @@ describe('UrlService Tests', () => { }); describe('getAuthorizeUrl', () => { - it('returns null if no config is given', waitForAsync(() => { + it('returns null if no config is given', async () => { service.getAuthorizeUrl(null).subscribe((url) => { expect(url).toBeNull(); }); - })); + }); - it('returns null if current flow is code flow and no redirect url is defined', waitForAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); + it('returns null if current flow is code flow and no redirect url is defined', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); service.getAuthorizeUrl({ configId: 'configId1' }).subscribe((result) => { expect(result).toBeNull(); }); - })); + }); - it('returns empty string if current flow is code flow, config disabled pkce and there is a redirecturl', waitForAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); + it('returns empty string if current flow is code flow, config disabled pkce and there is a redirecturl', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); const config = { configId: 'configId1', disablePkce: true, @@ -1027,10 +1068,10 @@ describe('UrlService Tests', () => { service.getAuthorizeUrl(config).subscribe((result) => { expect(result).toBe(''); }); - })); + }); - it('returns url if current flow is code flow, config disabled pkce, there is a redirecturl and awkep are given', waitForAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); + it('returns url if current flow is code flow, config disabled pkce, there is a redirecturl and awkep are given', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); const config = { configId: 'configId1', disablePkce: false, @@ -1044,27 +1085,29 @@ describe('UrlService Tests', () => { const authorizationEndpoint = 'authorizationEndpoint'; - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of('some-code-challenge') ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint }) + ); service.getAuthorizeUrl(config).subscribe((result) => { expect(result).toBe( 'authorizationEndpoint?client_id=some-clientId&redirect_uri=some-redirectUrl&response_type=testResponseType&scope=testScope&nonce=undefined&state=undefined&code_challenge=some-code-challenge&code_challenge_method=S256' ); }); - })); + }); - it('calls createUrlImplicitFlowAuthorize if current flow is NOT code flow', waitForAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); - const spyCreateUrlCodeFlowAuthorize = spyOn( + it('calls createUrlImplicitFlowAuthorize if current flow is NOT code flow', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); + const spyCreateUrlCodeFlowAuthorize = vi.spyOn( service as any, 'createUrlCodeFlowAuthorize' ); - const spyCreateUrlImplicitFlowAuthorize = spyOn( + const spyCreateUrlImplicitFlowAuthorize = vi.spyOn( service as any, 'createUrlImplicitFlowAuthorize' ); @@ -1073,39 +1116,38 @@ describe('UrlService Tests', () => { expect(spyCreateUrlCodeFlowAuthorize).not.toHaveBeenCalled(); expect(spyCreateUrlImplicitFlowAuthorize).toHaveBeenCalled(); }); - })); + }); - it('return empty string if flow is not code flow and createUrlImplicitFlowAuthorize returns falsy', waitForAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); - const spy = spyOn( - service as any, - 'createUrlImplicitFlowAuthorize' - ).and.returnValue(''); + it('return empty string if flow is not code flow and createUrlImplicitFlowAuthorize returns falsy', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); + const spy = vi + .spyOn(service as any, 'createUrlImplicitFlowAuthorize') + .mockReturnValue(''); const resultObs$ = service.getAuthorizeUrl({ configId: 'configId1' }); resultObs$.subscribe((result) => { expect(spy).toHaveBeenCalled(); expect(result).toBe(''); }); - })); + }); }); describe('getRefreshSessionSilentRenewUrl', () => { it('calls createUrlCodeFlowWithSilentRenew if current flow is code flow', () => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true); - const spy = spyOn(service as any, 'createUrlCodeFlowWithSilentRenew'); + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true); + const spy = vi.spyOn(service as any, 'createUrlCodeFlowWithSilentRenew'); service.getRefreshSessionSilentRenewUrl({ configId: 'configId1' }); expect(spy).toHaveBeenCalled(); }); it('calls createUrlImplicitFlowWithSilentRenew if current flow is NOT code flow', () => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); - const spyCreateUrlCodeFlowWithSilentRenew = spyOn( + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); + const spyCreateUrlCodeFlowWithSilentRenew = vi.spyOn( service as any, 'createUrlCodeFlowWithSilentRenew' ); - const spyCreateUrlImplicitFlowWithSilentRenew = spyOn( + const spyCreateUrlImplicitFlowWithSilentRenew = vi.spyOn( service as any, 'createUrlImplicitFlowWithSilentRenew' ); @@ -1115,12 +1157,11 @@ describe('UrlService Tests', () => { expect(spyCreateUrlImplicitFlowWithSilentRenew).toHaveBeenCalled(); }); - it('return empty string if flow is not code flow and createUrlImplicitFlowWithSilentRenew returns falsy', waitForAsync(() => { - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); - const spy = spyOn( - service as any, - 'createUrlImplicitFlowWithSilentRenew' - ).and.returnValue(''); + it('return empty string if flow is not code flow and createUrlImplicitFlowWithSilentRenew returns falsy', async () => { + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); + const spy = vi + .spyOn(service as any, 'createUrlImplicitFlowWithSilentRenew') + .mockReturnValue(''); const resultObs$ = service.getRefreshSessionSilentRenewUrl({ configId: 'configId1', }); @@ -1129,12 +1170,12 @@ describe('UrlService Tests', () => { expect(spy).toHaveBeenCalled(); expect(result).toBe(''); }); - })); + }); }); describe('createBodyForCodeFlowCodeRequest', () => { it('returns null if no code verifier is set', () => { - spyOn(flowsDataService, 'getCodeVerifier').and.returnValue(null); + vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue(null); const result = service.createBodyForCodeFlowCodeRequest( 'notRelevantParam', { configId: 'configId1' } @@ -1146,7 +1187,9 @@ describe('UrlService Tests', () => { it('returns null if no clientId is set', () => { const codeVerifier = 'codeverifier'; - spyOn(flowsDataService, 'getCodeVerifier').and.returnValue(codeVerifier); + vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue( + codeVerifier + ); const clientId = ''; const result = service.createBodyForCodeFlowCodeRequest( 'notRelevantParam', @@ -1162,8 +1205,10 @@ describe('UrlService Tests', () => { const redirectUrl = ''; const clientId = 'clientId'; - spyOn(flowsDataService, 'getCodeVerifier').and.returnValue(codeVerifier); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); + vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue( + codeVerifier + ); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); const result = service.createBodyForCodeFlowCodeRequest(code, { clientId, @@ -1179,8 +1224,10 @@ describe('UrlService Tests', () => { const redirectUrl = 'redirectUrl'; const clientId = 'clientId'; - spyOn(flowsDataService, 'getCodeVerifier').and.returnValue(codeVerifier); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false); + vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue( + codeVerifier + ); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false); const result = service.createBodyForCodeFlowCodeRequest(code, { clientId, @@ -1197,8 +1244,10 @@ describe('UrlService Tests', () => { const silentRenewUrl = 'silentRenewUrl'; const clientId = 'clientId'; - spyOn(flowsDataService, 'getCodeVerifier').and.returnValue(codeVerifier); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); + vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue( + codeVerifier + ); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true); const result = service.createBodyForCodeFlowCodeRequest(code, { clientId, @@ -1216,8 +1265,10 @@ describe('UrlService Tests', () => { const clientId = 'clientId'; const customTokenParams = { foo: 'bar' }; - spyOn(flowsDataService, 'getCodeVerifier').and.returnValue(codeVerifier); - spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true); + vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue( + codeVerifier + ); + vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true); const result = service.createBodyForCodeFlowCodeRequest( code, @@ -1237,9 +1288,9 @@ describe('UrlService Tests', () => { disablePkce: false, }; - spyOn(flowsDataService, 'getCodeVerifier').and.returnValue(null); + vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue(null); - const loggerspy = spyOn(loggerService, 'logError'); + const loggerspy = vi.spyOn(loggerService, 'logError'); const result = service.createBodyForCodeFlowCodeRequest( code, config, @@ -1247,7 +1298,7 @@ describe('UrlService Tests', () => { ); expect(result).toBe(null); - expect(loggerspy).toHaveBeenCalledOnceWith( + expect(loggerspy).toHaveBeenCalledExactlyOnceWith( config, 'CodeVerifier is not set ', null @@ -1296,7 +1347,7 @@ describe('UrlService Tests', () => { }); describe('createBodyForParCodeFlowRequest', () => { - it('returns null redirectUrl is falsy', waitForAsync(() => { + it('returns null redirectUrl is falsy', async () => { const resultObs$ = service.createBodyForParCodeFlowRequest({ redirectUrl: '', }); @@ -1304,9 +1355,9 @@ describe('UrlService Tests', () => { resultObs$.subscribe((result) => { expect(result).toBe(null); }); - })); + }); - it('returns basic URL with no extras if properties are given', waitForAsync(() => { + it('returns basic URL with no extras if properties are given', async () => { const config = { clientId: 'testClientId', responseType: 'testResponseType', @@ -1316,15 +1367,15 @@ describe('UrlService Tests', () => { redirectUrl: 'testRedirectUrl', }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue('testState'); - spyOn(flowsDataService, 'createNonce').and.returnValue('testNonce'); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue('testState'); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue('testNonce'); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( 'testCodeVerifier' ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of('testCodeChallenge') ); @@ -1335,9 +1386,9 @@ describe('UrlService Tests', () => { `client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256` ); }); - })); + }); - it('returns basic URL with hdParam if properties are given', waitForAsync(() => { + it('returns basic URL with hdParam if properties are given', async () => { const config = { clientId: 'testClientId', responseType: 'testResponseType', @@ -1347,15 +1398,15 @@ describe('UrlService Tests', () => { redirectUrl: 'testRedirectUrl', }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue('testState'); - spyOn(flowsDataService, 'createNonce').and.returnValue('testNonce'); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue('testState'); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue('testNonce'); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( 'testCodeVerifier' ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of('testCodeChallenge') ); @@ -1366,9 +1417,9 @@ describe('UrlService Tests', () => { `client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam` ); }); - })); + }); - it('returns basic URL with hdParam and custom params if properties are given', waitForAsync(() => { + it('returns basic URL with hdParam and custom params if properties are given', async () => { const config = { clientId: 'testClientId', responseType: 'testResponseType', @@ -1378,15 +1429,15 @@ describe('UrlService Tests', () => { redirectUrl: 'testRedirectUrl', }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue('testState'); - spyOn(flowsDataService, 'createNonce').and.returnValue('testNonce'); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue('testState'); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue('testNonce'); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( 'testCodeVerifier' ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of('testCodeChallenge') ); @@ -1397,9 +1448,9 @@ describe('UrlService Tests', () => { `client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam&any=thing` ); }); - })); + }); - it('returns basic URL with hdParam and custom params and passed cutom params if properties are given', waitForAsync(() => { + it('returns basic URL with hdParam and custom params and passed cutom params if properties are given', async () => { const config = { clientId: 'testClientId', responseType: 'testResponseType', @@ -1409,15 +1460,15 @@ describe('UrlService Tests', () => { redirectUrl: 'testRedirectUrl', }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue('testState'); - spyOn(flowsDataService, 'createNonce').and.returnValue('testNonce'); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue('testState'); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue('testNonce'); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( 'testCodeVerifier' ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of('testCodeChallenge') ); @@ -1432,7 +1483,7 @@ describe('UrlService Tests', () => { `client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam&any=thing&any=otherThing` ); }); - })); + }); }); describe('createUrlImplicitFlowWithSilentRenew', () => { @@ -1441,11 +1492,11 @@ describe('UrlService Tests', () => { const nonce = 'testNonce'; const silentRenewUrl = null; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); const config = { silentRenewUrl, @@ -1473,17 +1524,19 @@ describe('UrlService Tests', () => { scope, }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint, - }); + }) + ); const serviceAsAny = service as any; @@ -1506,15 +1559,17 @@ describe('UrlService Tests', () => { responseType, }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(null); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => null + ); const serviceAsAny = service as any; @@ -1525,22 +1580,22 @@ describe('UrlService Tests', () => { }); describe('createUrlCodeFlowWithSilentRenew', () => { - it('returns empty string if silentrenewUrl is falsy', waitForAsync(() => { + it('returns empty string if silentrenewUrl is falsy', async () => { const state = 'testState'; const nonce = 'testNonce'; const silentRenewUrl = null; const codeVerifier = 'codeVerifier'; const codeChallenge = 'codeChallenge '; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( codeVerifier ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of(codeChallenge) ); @@ -1555,9 +1610,9 @@ describe('UrlService Tests', () => { resultObs$.subscribe((result: any) => { expect(result).toBe(''); }); - })); + }); - it('returns correct URL if wellknownendpoints are given', waitForAsync(() => { + it('returns correct URL if wellknownendpoints are given', async () => { const state = 'testState'; const nonce = 'testNonce'; const silentRenewUrl = 'http://any-url.com'; @@ -1574,21 +1629,23 @@ describe('UrlService Tests', () => { scope, }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( codeVerifier ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of(codeChallenge) ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint }) + ); const serviceAsAny = service as any; @@ -1599,9 +1656,9 @@ describe('UrlService Tests', () => { `authorizationEndpoint?client_id=${clientId}&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}&prompt=none` ); }); - })); + }); - it('returns empty string if no wellknownendpoints are given', waitForAsync(() => { + it('returns empty string if no wellknownendpoints are given', async () => { const state = 'testState'; const nonce = 'testNonce'; const silentRenewUrl = 'http://any-url.com'; @@ -1615,20 +1672,22 @@ describe('UrlService Tests', () => { responseType, }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( codeVerifier ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of(codeChallenge) ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(null); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => null + ); const serviceAsAny = service as any; @@ -1637,7 +1696,7 @@ describe('UrlService Tests', () => { resultObs$.subscribe((result: any) => { expect(result).toBe(''); }); - })); + }); }); describe('createUrlImplicitFlowAuthorize', () => { @@ -1656,15 +1715,17 @@ describe('UrlService Tests', () => { scope, }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint }) + ); const serviceAsAny = service as any; @@ -1683,15 +1744,17 @@ describe('UrlService Tests', () => { const responseType = 'responseType'; const config = { redirectUrl, clientId, responseType }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(null); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => null + ); const serviceAsAny = service as any; @@ -1708,14 +1771,16 @@ describe('UrlService Tests', () => { const responseType = 'responseType'; const config = { redirectUrl, clientId, responseType }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(null); + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => null + ); const serviceAsAny = service as any; @@ -1726,7 +1791,7 @@ describe('UrlService Tests', () => { }); describe('createUrlCodeFlowAuthorize', () => { - it('returns null if redirectUrl is falsy', waitForAsync(() => { + it('returns null if redirectUrl is falsy', async () => { const state = 'testState'; const nonce = 'testNonce'; const redirectUrl = null; @@ -1734,11 +1799,11 @@ describe('UrlService Tests', () => { redirectUrl, }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); const serviceAsAny = service as any; @@ -1747,9 +1812,9 @@ describe('UrlService Tests', () => { resultObs$.subscribe((result: any) => { expect(result).toBeNull(); }); - })); + }); - it('returns correct URL if wellknownendpoints are given', waitForAsync(() => { + it('returns correct URL if wellknownendpoints are given', async () => { const state = 'testState'; const nonce = 'testNonce'; const scope = 'testScope'; @@ -1766,20 +1831,22 @@ describe('UrlService Tests', () => { scope, }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( codeVerifier ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of(codeChallenge) ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint }) + ); const serviceAsAny = service as any; @@ -1790,9 +1857,9 @@ describe('UrlService Tests', () => { `authorizationEndpoint?client_id=clientId&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}` ); }); - })); + }); - it('returns correct URL if wellknownendpoints and custom params are given', waitForAsync(() => { + it('returns correct URL if wellknownendpoints and custom params are given', async () => { const state = 'testState'; const nonce = 'testNonce'; const scope = 'testScope'; @@ -1811,21 +1878,23 @@ describe('UrlService Tests', () => { configId, }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( codeVerifier ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of(codeChallenge) ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ authorizationEndpoint }); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint }) + ); const serviceAsAny = service as any; @@ -1839,9 +1908,9 @@ describe('UrlService Tests', () => { `&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}&to=add&as=well` ); }); - })); + }); - it('returns empty string if no wellknownendpoints are given', waitForAsync(() => { + it('returns empty string if no wellknownendpoints are given', async () => { const state = 'testState'; const nonce = 'testNonce'; const redirectUrl = 'http://any-url.com'; @@ -1851,20 +1920,22 @@ describe('UrlService Tests', () => { const codeChallenge = 'codeChallenge '; const config = { redirectUrl, clientId, responseType }; - spyOn( + vi.spyOn( flowsDataService, 'getExistingOrCreateAuthStateControl' - ).and.returnValue(state); - spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); - spyOn(flowsDataService, 'createCodeVerifier').and.returnValue( + ).mockReturnValue(state); + vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce); + vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue( codeVerifier ); - spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( + vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue( of(codeChallenge) ); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue(null); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => null + ); const serviceAsAny = service as any; @@ -1873,7 +1944,7 @@ describe('UrlService Tests', () => { resultObs$.subscribe((result: any) => { expect(result).toBe(''); }); - })); + }); }); describe('getEndSessionUrl', () => { @@ -1889,12 +1960,16 @@ describe('UrlService Tests', () => { postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', } as OpenIdConfiguration; - spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( + 'mytoken' + ); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ endSessionEndpoint: 'http://example', - }); + }) + ); // Act const value = service.getEndSessionUrl(config); @@ -1912,12 +1987,14 @@ describe('UrlService Tests', () => { postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', } as OpenIdConfiguration; - spyOn(storagePersistenceService, 'getIdToken').and.returnValue(''); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(''); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ endSessionEndpoint: 'http://example', - }); + }) + ); // Act const value = service.getEndSessionUrl(config); @@ -1935,12 +2012,16 @@ describe('UrlService Tests', () => { postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', } as OpenIdConfiguration; - spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( + 'mytoken' + ); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ endSessionEndpoint: 'http://example', - }); + }) + ); // Act const value = service.getEndSessionUrl(config, { param: 'to-add' }); @@ -1960,12 +2041,14 @@ describe('UrlService Tests', () => { const endSessionEndpoint = 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in'; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ endSessionEndpoint, - }); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue( + }) + ); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( 'UzI1NiIsImtpZCI6Il' ); @@ -1986,12 +2069,16 @@ describe('UrlService Tests', () => { postLogoutRedirectUri: '', } as OpenIdConfiguration; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ endSessionEndpoint: 'http://example', - }); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); + }) + ); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( + 'mytoken' + ); // Act const value = service.getEndSessionUrl(config); @@ -2004,12 +2091,16 @@ describe('UrlService Tests', () => { it('returns null if no wellknownEndpoints.endSessionEndpoint given', () => { // Arrange - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', {}) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', {}], + () => ({ endSessionEndpoint: null, - }); - spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); + }) + ); + vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue( + 'mytoken' + ); // Act const value = service.getEndSessionUrl({}); @@ -2038,9 +2129,11 @@ describe('UrlService Tests', () => { describe('getAuthorizeParUrl', () => { it('returns null if authWellKnownEndPoints is undefined', () => { - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue(null); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => null + ); const result = service.getAuthorizeParUrl('', { configId: 'configId1' }); @@ -2048,11 +2141,13 @@ describe('UrlService Tests', () => { }); it('returns null if authWellKnownEndPoints-authorizationEndpoint is undefined', () => { - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', { configId: 'configId1' }) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', { configId: 'configId1' }], + () => ({ notAuthorizationEndpoint: 'anything', - }); + }) + ); const result = service.getAuthorizeParUrl('', { configId: 'configId1' }); @@ -2062,11 +2157,13 @@ describe('UrlService Tests', () => { it('returns null if configurationProvider.openIDConfiguration has no clientId', () => { const config = { clientId: '' } as OpenIdConfiguration; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'anything', - }); + }) + ); const result = service.getAuthorizeParUrl('', config); @@ -2076,11 +2173,13 @@ describe('UrlService Tests', () => { it('returns correct URL when everything is given', () => { const config = { clientId: 'clientId' }; - spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .and.returnValue({ + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ authorizationEndpoint: 'anything', - }); + }) + ); const result = service.getAuthorizeParUrl('passedRequestUri', config); diff --git a/src/validation/jwk-window-crypto.service.spec.ts b/src/validation/jwk-window-crypto.service.spec.ts index f2737b9..9aa8489 100644 --- a/src/validation/jwk-window-crypto.service.spec.ts +++ b/src/validation/jwk-window-crypto.service.spec.ts @@ -1,5 +1,6 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { base64url } from 'rfc4648'; +import { vi } from 'vitest'; import { CryptoService } from '../utils/crypto/crypto.service'; import { JwkWindowCryptoService } from './jwk-window-crypto.service'; @@ -43,9 +44,9 @@ describe('JwkWindowCryptoService', () => { }); }); - beforeEach(waitForAsync(() => { + beforeEach(async () => { service = TestBed.inject(JwkWindowCryptoService); - })); + }); it('should create', () => { expect(service).toBeTruthy(); diff --git a/src/validation/jwt-window-crypto.service.spec.ts b/src/validation/jwt-window-crypto.service.spec.ts index ecc3f53..ec9eab0 100644 --- a/src/validation/jwt-window-crypto.service.spec.ts +++ b/src/validation/jwt-window-crypto.service.spec.ts @@ -1,4 +1,5 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; +import { vi } from 'vitest'; import { CryptoService } from '../utils/crypto/crypto.service'; import { JwtWindowCryptoService } from './jwt-window-crypto.service'; @@ -21,7 +22,7 @@ describe('JwtWindowCryptoService', () => { }); describe('generateCodeChallenge', () => { - it('returns good result with correct codeVerifier', waitForAsync(() => { + it('returns good result with correct codeVerifier', async () => { const outcome = 'R2TWD45Vtcf_kfAqjuE3LMSRF3JDE5fsFndnn6-a0nQ'; const observable = service.generateCodeChallenge( '44445543344242132145455aaabbdc3b4' @@ -30,6 +31,6 @@ describe('JwtWindowCryptoService', () => { observable.subscribe((value) => { expect(value).toBe(outcome); }); - })); + }); }); }); diff --git a/src/validation/state-validation.service.spec.ts b/src/validation/state-validation.service.spec.ts index 3314271..32e9e4f 100644 --- a/src/validation/state-validation.service.spec.ts +++ b/src/validation/state-validation.service.spec.ts @@ -1,12 +1,13 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; -import { AuthWellKnownEndpoints } from '../config/auth-well-known/auth-well-known-endpoints'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import { vi } from 'vitest'; +import type { AuthWellKnownEndpoints } from '../config/auth-well-known/auth-well-known-endpoints'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { LogLevel } from '../logging/log-level'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { mockProvider } from '../testing/mock'; import { EqualityService } from '../utils/equality/equality.service'; import { FlowHelper } from '../utils/flowHelper/flow-helper.service'; import { TokenHelperService } from '../utils/tokenHelper/token-helper.service'; @@ -112,10 +113,10 @@ describe('State Validation Service', () => { triggerRefreshWhenIdTokenExpired: true, }; - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); const callbackContext = { code: 'fdffsdfsdf', @@ -187,10 +188,10 @@ describe('State Validation Service', () => { triggerRefreshWhenIdTokenExpired: true, }; - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); const callbackContext = { code: 'fdffsdfsdf', @@ -262,10 +263,10 @@ describe('State Validation Service', () => { triggerRefreshWhenIdTokenExpired: true, }; - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); const callbackContext = { code: 'fdffsdfsdf', @@ -337,10 +338,10 @@ describe('State Validation Service', () => { triggerRefreshWhenIdTokenExpired: true, }; - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); const callbackContext = { code: 'fdffsdfsdf', @@ -412,10 +413,10 @@ describe('State Validation Service', () => { triggerRefreshWhenIdTokenExpired: true, }; - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); const callbackContext = { code: 'fdffsdfsdf', @@ -487,10 +488,10 @@ describe('State Validation Service', () => { triggerRefreshWhenIdTokenExpired: true, }; - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); const callbackContext = { code: 'fdffsdfsdf', @@ -562,10 +563,10 @@ describe('State Validation Service', () => { triggerRefreshWhenIdTokenExpired: true, }; - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); const callbackContext = { code: 'fdffsdfsdf', @@ -637,10 +638,10 @@ describe('State Validation Service', () => { triggerRefreshWhenIdTokenExpired: true, }; - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); const callbackContext = { code: 'fdffsdfsdf', @@ -686,7 +687,7 @@ describe('State Validation Service', () => { }); describe('getValidatedStateResult', () => { - it('should return authResponseIsValid false when null is passed', waitForAsync(() => { + it('should return authResponseIsValid false when null is passed', async () => { const isValidObs$ = stateValidationService.getValidatedStateResult( {} as CallbackContext, config @@ -695,18 +696,18 @@ describe('State Validation Service', () => { isValidObs$.subscribe((isValid) => { expect(isValid.authResponseIsValid).toBe(false); }); - })); + }); - it('should return invalid context error', waitForAsync(() => { - spyOn( + it('should return invalid context error', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; config.maxIdTokenIatOffsetAllowedInSeconds = 0; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue( + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( false ); @@ -732,70 +733,79 @@ describe('State Validation Service', () => { isValidObs$.subscribe((isValid) => { expect(isValid.authResponseIsValid).toBe(false); }); - })); + }); - it('should return invalid result if validateIdTokenExpNotExpired is false', waitForAsync(() => { - spyOn( + it('should return invalid result if validateIdTokenExpNotExpired is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'hasIdTokenExpired').and.returnValue(false); - spyOn( + vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockReturnValue( + false + ); + vi.spyOn( tokenValidationService, 'validateAccessTokenNotExpired' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenAzpExistsIfMoreThanOneAud' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAzpValid').and.returnValue( - true - ); - spyOn(tokenValidationService, 'validateIdTokenAtHash').and.returnValue( + ).mockReturnValue(true); + vi.spyOn( + tokenValidationService, + 'validateIdTokenAzpValid' + ).mockReturnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenAtHash').mockReturnValue( of(true) ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( + true + ); + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( + true + ); config.clientId = ''; - spyOn( + vi.spyOn( tokenValidationService, 'validateIdTokenExpNotExpired' - ).and.returnValue(false); - const readSpy = spyOn(storagePersistenceService, 'read'); + ).mockReturnValue(false); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -817,7 +827,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback id token expired' ); @@ -826,25 +836,25 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(false); }); - })); + }); - it('should return invalid result if validateStateFromHashCallback is false', waitForAsync(() => { - const readSpy = spyOn(storagePersistenceService, 'read'); + it('should return invalid result if validateStateFromHashCallback is false', async () => { + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - spyOn( + .mockReturnValue('authStateControl'); + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(false); + ).mockReturnValue(false); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -870,7 +880,7 @@ describe('State Validation Service', () => { ).toHaveBeenCalled(); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback incorrect state' ); @@ -879,48 +889,57 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBeDefined(); expect(state.idToken).toBe(''); }); - })); + }); - it('access_token should equal result.access_token and is valid if response_type is "id_token token"', waitForAsync(() => { - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + it('access_token should equal result.access_token and is valid if response_type is "id_token token"', async () => { + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) + ).mockReturnValue(true); + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockReturnValue( + false ); - spyOn(tokenValidationService, 'hasIdTokenExpired').and.returnValue(false); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateAccessTokenNotExpired' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenAzpExistsIfMoreThanOneAud' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAzpValid').and.returnValue( - true - ); - spyOn( + ).mockReturnValue(true); + vi.spyOn( + tokenValidationService, + 'validateIdTokenAzpValid' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( + true + ); + vi.spyOn( tokenValidationService, 'validateIdTokenExpNotExpired' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAtHash').and.returnValue( + ).mockReturnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( + true + ); + vi.spyOn(tokenValidationService, 'validateIdTokenAtHash').mockReturnValue( of(true) ); @@ -929,15 +948,15 @@ describe('State Validation Service', () => { config.autoCleanStateAfterAuthentication = false; config.responseType = 'id_token token'; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); const callbackContext = { code: 'fdffsdfsdf', @@ -964,32 +983,33 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(true); }); - })); + }); - it('should return invalid result if validateSignatureIdToken is false', waitForAsync(() => { - spyOn( + it('should return invalid result if validateSignatureIdToken is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(false) - ); + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(false)); - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - const logDebugSpy = spyOn(loggerService, 'logDebug').and.callFake( - () => undefined - ); + .mockReturnValue('authStateControl'); + const logDebugSpy = vi + .spyOn(loggerService, 'logDebug') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1012,7 +1032,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logDebugSpy.calls.allArgs()).toEqual([ + expect(logDebugSpy).toBeCalledWith([ [config, 'authCallback Signature validation failed id_token'], [config, 'authCallback token(s) invalid'], ]); @@ -1022,36 +1042,37 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(false); }); - })); + }); - it('should return invalid result if validateIdTokenNonce is false', waitForAsync(() => { - spyOn( + it('should return invalid result if validateIdTokenNonce is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( false ); - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1073,7 +1094,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback incorrect nonce, did you call the checkAuth() method multiple times?' ); @@ -1082,43 +1103,45 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(false); }); - })); + }); - it('should return invalid result if validateRequiredIdToken is false', waitForAsync(() => { - spyOn( + it('should return invalid result if validateRequiredIdToken is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - false - ); - const readSpy = spyOn(storagePersistenceService, 'read'); + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(false); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); - const logDebugSpy = spyOn(loggerService, 'logDebug').and.callFake( - () => undefined - ); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); + const logDebugSpy = vi + .spyOn(loggerService, 'logDebug') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1153,46 +1176,48 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(false); }); - })); + }); - it('should return invalid result if validateIdTokenIatMaxOffset is false', waitForAsync(() => { - spyOn( + it('should return invalid result if validateIdTokenIatMaxOffset is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(false); + ).mockReturnValue(false); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1214,7 +1239,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback Validation, iat rejected id_token was issued too far away from the current time' ); @@ -1223,53 +1248,55 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(false); }); - })); + }); - it('should return invalid result if validateIdTokenIss is false and has authWellKnownEndPoints', waitForAsync(() => { - spyOn( + it('should return invalid result if validateIdTokenIss is false and has authWellKnownEndPoints', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); - spyOn( + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue( + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( false ); - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1291,7 +1318,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback incorrect iss does not match authWellKnownEndpoints issuer' ); @@ -1300,41 +1327,43 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(false); }); - })); + }); - it('should return invalid result if validateIdTokenIss is false and has no authWellKnownEndPoints', waitForAsync(() => { - spyOn( + it('should return invalid result if validateIdTokenIss is false and has no authWellKnownEndPoints', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); - readSpy.withArgs('authWellKnownEndPoints', config).and.returnValue(null); + readSpy.withArgs('authWellKnownEndPoints', config).mockReturnValue(null); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1356,7 +1385,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authWellKnownEndpoints is undefined' ); @@ -1367,49 +1396,53 @@ describe('State Validation Service', () => { expect(state.authResponseIsValid).toBe(false); expect(state.state).toBe(ValidationResult.NoAuthWellKnownEndPoints); }); - })); + }); - it('should return invalid result if validateIdTokenAud is false', waitForAsync(() => { - spyOn( + it('should return invalid result if validateIdTokenAud is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue( + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( + true + ); + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( false ); config.clientId = ''; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1431,7 +1464,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback incorrect aud' ); @@ -1440,51 +1473,57 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(false); }); - })); + }); - it('should return invalid result if validateIdTokenAzpExistsIfMoreThanOneAud is false', waitForAsync(() => { - spyOn( + it('should return invalid result if validateIdTokenAzpExistsIfMoreThanOneAud is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue(true); - spyOn( + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( + true + ); + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( + true + ); + vi.spyOn( tokenValidationService, 'validateIdTokenAzpExistsIfMoreThanOneAud' - ).and.returnValue(false); + ).mockReturnValue(false); config.clientId = ''; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1506,7 +1545,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback missing azp' ); @@ -1516,54 +1555,61 @@ describe('State Validation Service', () => { expect(state.authResponseIsValid).toBe(false); expect(state.state).toBe(ValidationResult.IncorrectAzp); }); - })); + }); - it('should return invalid result if validateIdTokenAzpValid is false', waitForAsync(() => { - spyOn( + it('should return invalid result if validateIdTokenAzpValid is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue(true); - spyOn( + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( + true + ); + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( + true + ); + vi.spyOn( tokenValidationService, 'validateIdTokenAzpExistsIfMoreThanOneAud' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAzpValid').and.returnValue( - false - ); + ).mockReturnValue(true); + vi.spyOn( + tokenValidationService, + 'validateIdTokenAzpValid' + ).mockReturnValue(false); config.clientId = ''; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1585,7 +1631,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback incorrect azp' ); @@ -1595,58 +1641,65 @@ describe('State Validation Service', () => { expect(state.authResponseIsValid).toBe(false); expect(state.state).toBe(ValidationResult.IncorrectAzp); }); - })); + }); - it('should return invalid result if isIdTokenAfterRefreshTokenRequestValid is false', waitForAsync(() => { - spyOn( + it('should return invalid result if isIdTokenAfterRefreshTokenRequestValid is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue(true); - spyOn( - tokenValidationService, - 'validateIdTokenAzpExistsIfMoreThanOneAud' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAzpValid').and.returnValue( + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( true ); - spyOn( + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( + true + ); + vi.spyOn( + tokenValidationService, + 'validateIdTokenAzpExistsIfMoreThanOneAud' + ).mockReturnValue(true); + vi.spyOn( + tokenValidationService, + 'validateIdTokenAzpValid' + ).mockReturnValue(true); + vi.spyOn( stateValidationService as any, 'isIdTokenAfterRefreshTokenRequestValid' - ).and.returnValue(false); + ).mockReturnValue(false); config.clientId = ''; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1668,7 +1721,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback pre, post id_token claims do not match in refresh' ); @@ -1680,67 +1733,76 @@ describe('State Validation Service', () => { ValidationResult.IncorrectIdTokenClaimsAfterRefresh ); }); - })); + }); - it('Reponse is valid if authConfiguration.response_type does not equal "id_token token"', waitForAsync(() => { - spyOn(tokenValidationService, 'hasIdTokenExpired').and.returnValue(false); - spyOn( + it('Reponse is valid if authConfiguration.response_type does not equal "id_token token"', async () => { + vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockReturnValue( + false + ); + vi.spyOn( tokenValidationService, 'validateAccessTokenNotExpired' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenAzpExistsIfMoreThanOneAud' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAzpValid').and.returnValue( - true - ); - spyOn(tokenValidationService, 'validateIdTokenAtHash').and.returnValue( + ).mockReturnValue(true); + vi.spyOn( + tokenValidationService, + 'validateIdTokenAzpValid' + ).mockReturnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenAtHash').mockReturnValue( of(true) ); - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + ).mockReturnValue(true); + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( + true + ); + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( + true + ); config.clientId = ''; - spyOn( + vi.spyOn( tokenValidationService, 'validateIdTokenExpNotExpired' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'NOT id_token token'; config.autoCleanStateAfterAuthentication = false; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); - const logDebugSpy = spyOn(loggerService, 'logDebug').and.callFake( - () => undefined - ); + const logDebugSpy = vi + .spyOn(loggerService, 'logDebug') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1776,69 +1838,78 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(true); }); - })); + }); - it('Response is invalid if validateIdTokenAtHash is false', waitForAsync(() => { - spyOn( + it('Response is invalid if validateIdTokenAtHash is false', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + ).mockReturnValue(true); + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( + true + ); + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( + true + ); config.clientId = ''; - spyOn( + vi.spyOn( tokenValidationService, 'validateIdTokenExpNotExpired' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'id_token token'; config.autoCleanStateAfterAuthentication = false; - spyOn(tokenValidationService, 'validateIdTokenAtHash').and.returnValue( + vi.spyOn(tokenValidationService, 'validateIdTokenAtHash').mockReturnValue( of(false) ); - spyOn(tokenValidationService, 'hasIdTokenExpired').and.returnValue(false); - spyOn( + vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockReturnValue( + false + ); + vi.spyOn( tokenValidationService, 'validateAccessTokenNotExpired' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenAzpExistsIfMoreThanOneAud' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAzpValid').and.returnValue( - true - ); + ).mockReturnValue(true); + vi.spyOn( + tokenValidationService, + 'validateIdTokenAzpValid' + ).mockReturnValue(true); - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); - const logWarningSpy = spyOn(loggerService, 'logWarning').and.callFake( - () => undefined - ); + const logWarningSpy = vi + .spyOn(loggerService, 'logWarning') + .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1860,7 +1931,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logWarningSpy).toHaveBeenCalledOnceWith( + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( config, 'authCallback incorrect at_hash' ); @@ -1869,67 +1940,74 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBe('decoded_id_token'); expect(state.authResponseIsValid).toBe(false); }); - })); + }); - it('should return valid result if validateIdTokenIss is false and iss_validation_off is true', waitForAsync(() => { + it('should return valid result if validateIdTokenIss is false and iss_validation_off is true', async () => { config.issValidationOff = true; - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue( + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( false ); - spyOn(tokenValidationService, 'hasIdTokenExpired').and.returnValue(false); - spyOn( + vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockReturnValue( + false + ); + vi.spyOn( tokenValidationService, 'validateAccessTokenNotExpired' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenAzpExistsIfMoreThanOneAud' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAzpValid').and.returnValue( - true - ); + ).mockReturnValue(true); + vi.spyOn( + tokenValidationService, + 'validateIdTokenAzpValid' + ).mockReturnValue(true); - spyOn( + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + ).mockReturnValue(true); + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( - true - ); - spyOn( + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( + true + ); + vi.spyOn( tokenValidationService, 'validateIdTokenExpNotExpired' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAtHash').and.returnValue( + ).mockReturnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenAtHash').mockReturnValue( of(true) ); config.responseType = 'id_token token'; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); - const logDebugSpy = spyOn(loggerService, 'logDebug'); // .and.callFake(() => undefined); + const logDebugSpy = vi.spyOn(loggerService, 'logDebug'); // .mockImplementation(() => undefined); const callbackContext = { code: 'fdffsdfsdf', @@ -1951,7 +2029,7 @@ describe('State Validation Service', () => { ); stateObs$.subscribe((state) => { - expect(logDebugSpy.calls.allArgs()).toEqual([ + expect(logDebugSpy).toBeCalledWith([ [config, 'iss validation is turned off, this is not recommended!'], [config, 'authCallback token(s) validated, continue'], ]); @@ -1961,54 +2039,60 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBeDefined(); expect(state.idToken).toBe('id_tokenTEST'); }); - })); + }); - it('should return valid if there is no id_token', waitForAsync(() => { - spyOn( + it('should return valid if there is no id_token', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); + ).mockReturnValue(true); config.responseType = 'code'; - spyOn(tokenHelperService, 'getPayloadFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getPayloadFromToken').mockReturnValue( 'decoded_id_token' ); - spyOn(tokenValidationService, 'validateSignatureIdToken').and.returnValue( - of(true) - ); - spyOn(tokenValidationService, 'validateIdTokenNonce').and.returnValue( - true - ); - spyOn(tokenValidationService, 'validateRequiredIdToken').and.returnValue( + vi.spyOn( + tokenValidationService, + 'validateSignatureIdToken' + ).mockReturnValue(of(true)); + vi.spyOn(tokenValidationService, 'validateIdTokenNonce').mockReturnValue( true ); + vi.spyOn( + tokenValidationService, + 'validateRequiredIdToken' + ).mockReturnValue(true); config.maxIdTokenIatOffsetAllowedInSeconds = 0; config.clientId = ''; - spyOn( + vi.spyOn( tokenValidationService, 'validateIdTokenIatMaxOffset' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAud').and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenAud').mockReturnValue( + true + ); + vi.spyOn( tokenValidationService, 'validateIdTokenExpNotExpired' - ).and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenIss').and.returnValue(true); - spyOn(tokenValidationService, 'validateIdTokenAtHash').and.returnValue( + ).mockReturnValue(true); + vi.spyOn(tokenValidationService, 'validateIdTokenIss').mockReturnValue( + true + ); + vi.spyOn(tokenValidationService, 'validateIdTokenAtHash').mockReturnValue( of(true) ); config.autoCleanStateAfterAuthentication = false; - const readSpy = spyOn(storagePersistenceService, 'read'); + const readSpy = vi.spyOn(storagePersistenceService, 'read'); readSpy .withArgs('authWellKnownEndPoints', config) - .and.returnValue(authWellKnownEndpoints); + .mockReturnValue(authWellKnownEndpoints); readSpy .withArgs('authStateControl', config) - .and.returnValue('authStateControl'); - readSpy.withArgs('authNonce', config).and.returnValue('authNonce'); + .mockReturnValue('authStateControl'); + readSpy.withArgs('authNonce', config).mockReturnValue('authNonce'); const callbackContext = { code: 'fdffsdfsdf', @@ -2036,18 +2120,18 @@ describe('State Validation Service', () => { expect(state.decodedIdToken).toBeDefined(); expect(state.authResponseIsValid).toBe(true); }); - })); + }); - it('should return OK if disableIdTokenValidation is true', waitForAsync(() => { - spyOn( + it('should return OK if disableIdTokenValidation is true', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( flowHelper, 'isCurrentFlowImplicitFlowWithAccessToken' - ).and.returnValue(false); - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); + ).mockReturnValue(false); + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); config.responseType = 'id_token token'; config.maxIdTokenIatOffsetAllowedInSeconds = 0; @@ -2074,18 +2158,18 @@ describe('State Validation Service', () => { expect(isValid.state).toBe(ValidationResult.Ok); expect(isValid.authResponseIsValid).toBe(true); }); - })); + }); - it('should return OK if disableIdTokenValidation is true', waitForAsync(() => { - spyOn( + it('should return OK if disableIdTokenValidation is true', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( flowHelper, 'isCurrentFlowImplicitFlowWithAccessToken' - ).and.returnValue(false); - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); + ).mockReturnValue(false); + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); config.responseType = 'id_token token'; config.maxIdTokenIatOffsetAllowedInSeconds = 0; @@ -2112,18 +2196,18 @@ describe('State Validation Service', () => { expect(isValid.state).toBe(ValidationResult.Ok); expect(isValid.authResponseIsValid).toBe(true); }); - })); + }); - it('should return OK if disableIdTokenValidation is false but inrefreshtokenflow and no id token is returned', waitForAsync(() => { - spyOn( + it('should return OK if disableIdTokenValidation is false but inrefreshtokenflow and no id token is returned', async () => { + vi.spyOn( tokenValidationService, 'validateStateFromHashCallback' - ).and.returnValue(true); - spyOn( + ).mockReturnValue(true); + vi.spyOn( flowHelper, 'isCurrentFlowImplicitFlowWithAccessToken' - ).and.returnValue(false); - spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false); + ).mockReturnValue(false); + vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false); config.responseType = 'id_token token'; config.maxIdTokenIatOffsetAllowedInSeconds = 0; @@ -2150,6 +2234,6 @@ describe('State Validation Service', () => { expect(isValid.state).toBe(ValidationResult.Ok); expect(isValid.authResponseIsValid).toBe(true); }); - })); + }); }); }); diff --git a/src/validation/token-validation.service.spec.ts b/src/validation/token-validation.service.spec.ts index f2abacb..8dfcb8f 100644 --- a/src/validation/token-validation.service.spec.ts +++ b/src/validation/token-validation.service.spec.ts @@ -1,8 +1,9 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { of } from 'rxjs'; -import { mockProvider } from '../../test/auto-mock'; +import { TestBed } from '@/testing'; +import { lastValueFrom, of } from 'rxjs'; +import { vi } from 'vitest'; import { JwkExtractor } from '../extractors/jwk.extractor'; import { LoggerService } from '../logging/logger.service'; +import { mockProvider } from '../testing/mock'; import { CryptoService } from '../utils/crypto/crypto.service'; import { TokenHelperService } from '../utils/tokenHelper/token-helper.service'; import { JwkWindowCryptoService } from './jwk-window-crypto.service'; @@ -498,7 +499,7 @@ describe('TokenValidationService', () => { }); describe('validateSignatureIdToken', () => { - it('returns false if no kwtKeys are passed', waitForAsync(() => { + it('returns false if no kwtKeys are passed', async () => { const valueFalse$ = tokenValidationService.validateSignatureIdToken( 'some-id-token', null, @@ -508,9 +509,9 @@ describe('TokenValidationService', () => { valueFalse$.subscribe((valueFalse) => { expect(valueFalse).toEqual(false); }); - })); + }); - it('returns true if no idToken is passed', waitForAsync(() => { + it('returns true if no idToken is passed', async () => { const valueFalse$ = tokenValidationService.validateSignatureIdToken( null as any, 'some-jwt-keys', @@ -520,9 +521,9 @@ describe('TokenValidationService', () => { valueFalse$.subscribe((valueFalse) => { expect(valueFalse).toEqual(true); }); - })); + }); - it('returns false if jwtkeys has no keys-property', waitForAsync(() => { + it('returns false if jwtkeys has no keys-property', async () => { const valueFalse$ = tokenValidationService.validateSignatureIdToken( 'some-id-token', { notKeys: '' }, @@ -532,10 +533,10 @@ describe('TokenValidationService', () => { valueFalse$.subscribe((valueFalse) => { expect(valueFalse).toEqual(false); }); - })); + }); - it('returns false if header data has no header data', waitForAsync(() => { - spyOn(tokenHelperService, 'getHeaderFromToken').and.returnValue({}); + it('returns false if header data has no header data', async () => { + vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({}); const jwtKeys = { keys: 'someThing', @@ -550,10 +551,10 @@ describe('TokenValidationService', () => { valueFalse$.subscribe((valueFalse) => { expect(valueFalse).toEqual(false); }); - })); + }); - it('returns false if header data alg property does not exist in keyalgorithms', waitForAsync(() => { - spyOn(tokenHelperService, 'getHeaderFromToken').and.returnValue({ + it('returns false if header data alg property does not exist in keyalgorithms', async () => { + vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({ alg: 'NOT SUPPORTED ALG', }); @@ -570,16 +571,16 @@ describe('TokenValidationService', () => { valueFalse$.subscribe((valueFalse) => { expect(valueFalse).toEqual(false); }); - })); + }); it('returns false if header data has kid property and jwtKeys has same kid property but they are not valid with the token', (done) => { const kid = '5626CE6A8F4F5FCD79C6642345282CA76D337548'; - spyOn(tokenHelperService, 'getHeaderFromToken').and.returnValue({ + vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({ alg: 'RS256', kid, }); - spyOn(tokenHelperService, 'getSignatureFromToken').and.returnValue(''); + vi.spyOn(tokenHelperService, 'getSignatureFromToken').mockReturnValue(''); const jwtKeys = { keys: [ @@ -626,14 +627,14 @@ describe('TokenValidationService', () => { keys: [key], }; - spyOn(tokenHelperService, 'getHeaderFromToken').and.returnValue({ + vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({ alg: 'RS256', typ: 'JWT', }); - spyOn(tokenHelperService, 'getSigningInputFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getSigningInputFromToken').mockReturnValue( [idTokenParts[0], idTokenParts[1]].join('.') ); - spyOn(tokenHelperService, 'getSignatureFromToken').and.returnValue( + vi.spyOn(tokenHelperService, 'getSignatureFromToken').mockReturnValue( idTokenParts[2] ); @@ -668,22 +669,21 @@ describe('TokenValidationService', () => { }); }); - it('returns false if sha is sha256 and generated hash does not equal atHash param', (done) => { + it('returns false if sha is sha256 and generated hash does not equal atHash param', async () => { const accessToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg'; const atHash = 'bad'; - const result$ = tokenValidationService.validateIdTokenAtHash( - accessToken, - atHash, - '256', - { configId: 'configId1' } + const result = await lastValueFrom( + tokenValidationService.validateIdTokenAtHash( + accessToken, + atHash, + '256', + { configId: 'configId1' } + ) ); - result$.subscribe((result) => { - expect(result).toEqual(false); - done(); - }); + expect(result).toEqual(false); }); it('returns true if sha is sha256 and generated hash does equal atHash param', (done) => { @@ -691,7 +691,7 @@ describe('TokenValidationService', () => { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg'; const atHash = 'good'; - spyOn(jwtWindowCryptoService, 'generateAtHash').and.returnValues( + vi.spyOn(jwtWindowCryptoService, 'generateAtHash').mockReturnValues( of('notEqualsGood'), of('good') ); @@ -770,7 +770,7 @@ describe('TokenValidationService', () => { describe('validateIdTokenExpNotExpired', () => { it('returns false when getTokenExpirationDate returns null', () => { - spyOn(tokenHelperService, 'getTokenExpirationDate').and.returnValue( + vi.spyOn(tokenHelperService, 'getTokenExpirationDate').mockReturnValue( null as unknown as Date ); const notExpired = tokenValidationService.validateIdTokenExpNotExpired( @@ -848,7 +848,7 @@ describe('TokenValidationService', () => { const idToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTMxMTY5NTAsImV4cCI6MjUyODI2NTc1MCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.GHxRo23NghUTTeZx6VIzTSf05JEeEn7z9YYyFLxWv6M'; - spyOn(tokenHelperService, 'getTokenExpirationDate').and.returnValue( + vi.spyOn(tokenHelperService, 'getTokenExpirationDate').mockReturnValue( tokenExpires ); diff --git a/tsconfig.json b/tsconfig.json index 79ead0d..95f8a1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,24 +3,28 @@ "forceConsistentCasingInFileNames": true, "esModuleInterop": true, "strict": true, - "noEmit": true, + "declaration": true, + "declarationMap": true, "sourceMap": true, - "declaration": false, + "skipLibCheck": true, "noFallthroughCasesInSwitch": true, "experimentalDecorators": true, - "skipLibCheck": true, + "emitDecoratorMetadata": true, "moduleResolution": "bundler", - "target": "ES2015", + "target": "ES2022", "module": "ESNext", "lib": ["ES2021", "DOM", "DOM.Iterable"], "useDefineForClassFields": true, - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "paths": { - "injection-js": [ - "./node_modules/injection-js/lib" - ] - } + "resolveJsonModule": true }, - "include": ["src"] + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] } diff --git a/tsconfig.lib.json b/tsconfig.lib.json index 6c61a1f..8d6cadb 100644 --- a/tsconfig.lib.json +++ b/tsconfig.lib.json @@ -1,12 +1,13 @@ { - "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "./dist/lib", - "declaration": true, - "declarationMap": true, - "inlineSources": true, - "types": ["node"], - "lib": ["dom", "es2018"] + "composite": true, + "rootDir": ".", + "outDir": "./dist/tsc-lib", + "lib": ["dom", "es2018"], + "paths": { + "injection-js": ["./node_modules/injection-js/lib/index.ts"] + } }, - "exclude": ["src/test.ts", "**/*.spec.ts"] + "include": ["src"], + "exclude": ["src/testing/**/*", "**/*.spec.ts"] } diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 5473296..aa0cca3 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -1,10 +1,24 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine", "node"] + "rootDir": ".", + "composite": true, + "noUncheckedIndexedAccess": true, + "outDir": "./dist/tsc-test", + "types": ["vitest/globals", "node"], + "paths": { + "@/testing": ["./src/testing"], + "@/testing/*": ["./src/testing/*"], + "injection-js": ["./node_modules/injection-js/lib/index.ts"], + "oidc-client-rx": ["./src"], + "oidc-client-rx/*": ["./src/*"] + } }, - "files": ["src/test.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts"] + "files": [], + "include": ["src/**/*.spec.ts", "src/testing"], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] }