fix: fix all biome

This commit is contained in:
master 2025-01-31 05:57:51 +08:00
parent 316361bd3c
commit c9d0066d64
114 changed files with 2255 additions and 2068 deletions

View File

@ -4,19 +4,24 @@
"linter": { "linter": {
"rules": { "rules": {
"style": { "style": {
"noNonNullAssertion": "off" "noNonNullAssertion": "off",
"noParameterAssign": "off",
"useFilenamingConvention": "warn"
}, },
"suspicious": { "suspicious": {
"noExplicitAny": "off" "noExplicitAny": "off"
}, },
"complexity": { "complexity": {
"noForEach": "info" "noForEach": "off"
}, },
"correctness": { "correctness": {
"noUnusedImports": { "noUnusedImports": {
"fix": "none", "fix": "none",
"level": "warn" "level": "warn"
} }
},
"nursery": {
"noEnum": "off"
} }
} }
}, },

View File

@ -3,8 +3,8 @@ import { describe, it } from 'node:test';
import { Biome, Distribution } from '@biomejs/js-api'; import { Biome, Distribution } from '@biomejs/js-api';
import { rewriteObservableSubscribeToLastValueFrom } from './code-transform'; import { rewriteObservableSubscribeToLastValueFrom } from './code-transform';
describe('writeAllSpecObservableSubscribeToLastValueFrom', () => { describe('rewriteSpecObservableSubscribeToLastValueFrom', () => {
it('should transform valid string', async () => { it('should transform simple example valid string', async () => {
const actual = await rewriteObservableSubscribeToLastValueFrom( const actual = await rewriteObservableSubscribeToLastValueFrom(
'index.ts', 'index.ts',
`refreshSessionIframeService `refreshSessionIframeService
@ -32,4 +32,51 @@ describe('writeAllSpecObservableSubscribeToLastValueFrom', () => {
biome.formatContent(expect, { filePath: 'index.ts' }).content biome.formatContent(expect, { filePath: 'index.ts' }).content
); );
}); });
it('should rewrite complex exmaple to valid string', async () => {
const actual = await rewriteObservableSubscribeToLastValueFrom(
'index.ts',
`codeFlowCallbackService
.authenticatedCallbackWithCode('some-url4', config, [config])
.subscribe({
error: (err: any) => {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
expect(err).toBeTruthy();
},
next: (abc) => {
expect(abc).toBeTruthy();
},
complete () {
expect.fail('complete')
}
});`
);
const expect = `
try {
const abc = await lastValueFrom(codeFlowCallbackService.authenticatedCallbackWithCode('some-url4', config, [config]));
expect(abc).toBeTruthy();
} catch (err: any) {
if (err instanceof EmptyError) {
expect.fail('complete')
} else {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
expect(err).toBeTruthy();
}
}
`;
const biome = await Biome.create({
distribution: Distribution.NODE,
});
assert.equal(
biome.formatContent(actual, { filePath: 'index.ts' }).content,
biome.formatContent(expect, { filePath: 'index.ts' }).content
);
});
}); });

View File

@ -1,11 +1,18 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import fsp from 'node:fs/promises'; import fsp from 'node:fs/promises';
import { type MagicString, type Statement, parseSync } from 'oxc-parser'; import {
import { type Node, walk } from 'oxc-walker'; type ArrowFunctionExpression,
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
type Function,
type MagicString,
type Statement,
parseSync,
} from 'oxc-parser';
import { walk } from 'oxc-walker';
function sourceTextFromNode( function sourceTextFromNode(
context: { magicString?: MagicString }, context: { magicString?: MagicString },
node: Node node: { start: number; end: number }
): string { ): string {
const magicString = context.magicString; const magicString = context.magicString;
assert(magicString, 'magicString should be defined'); assert(magicString, 'magicString should be defined');
@ -33,53 +40,101 @@ export async function rewriteObservableSubscribeToLastValueFrom(
child.type === 'ExpressionStatement' && child.type === 'ExpressionStatement' &&
child.expression.type === 'CallExpression' && child.expression.type === 'CallExpression' &&
child.expression.callee.type === 'StaticMemberExpression' && child.expression.callee.type === 'StaticMemberExpression' &&
child.expression.callee.property.name === 'subscribe' && child.expression.callee.property.name === 'subscribe'
child.expression.arguments.length === 0
) { ) {
const newContent = `await lastValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});`; let next: ArrowFunctionExpression | Function | undefined;
let error: ArrowFunctionExpression | Function | undefined;
let complete: ArrowFunctionExpression | Function | undefined;
const newStatements = parseSync('index.ts', newContent).program if (child.expression.arguments[0]?.type === 'ObjectExpression') {
.body as any[]; const obj = child.expression.arguments[0];
for (const prop of obj.properties) {
magicString.remove(child.start, child.end); if (
magicString.appendRight(child.start, newContent); prop.type === 'ObjectProperty' &&
prop.key.type === 'Identifier' &&
newChildren.push(...newStatements); (prop.value.type === 'FunctionExpression' ||
prop.value.type === 'ArrowFunctionExpression')
) {
if (prop.key.name === 'next') {
next = prop.value;
} else if (prop.key.name === 'error') {
error = prop.value;
} else if (prop.key.name === 'complete') {
complete = prop.value;
}
}
}
} else if ( } else if (
child.type === 'ExpressionStatement' && child.expression.arguments.find(
child.expression.type === 'CallExpression' && (arg) =>
child.expression.callee.type === 'StaticMemberExpression' && arg.type === 'FunctionExpression' ||
child.expression.callee.property.name === 'subscribe' && arg.type === 'ArrowFunctionExpression'
child.expression.arguments[0]?.type === 'ArrowFunctionExpression' && )
child.expression.arguments[0].body.type === 'FunctionBody'
) { ) {
const awaited = const args: Array<
child.expression.arguments[0].params.kind === Function | ArrowFunctionExpression | undefined
'ArrowFormalParameters' && > = child.expression.arguments.map((arg) =>
child.expression.arguments[0].params.items[0]?.type === arg.type === 'FunctionExpression' ||
'FormalParameter' && arg.type === 'ArrowFunctionExpression'
child.expression.arguments[0].params.items[0].pattern.type === ? arg
'Identifier' : undefined
? child.expression.arguments[0].params.items[0].pattern.name );
: undefined; next = args[0];
const newContent = error = args[1];
(awaited complete = args[2];
? `const ${awaited} = await lastValueFrom(${sourceTextFromNode( }
context, let newContent = `await lastValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});`;
child.expression.callee.object
)});\n`
: `await lastValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});\n`) +
child.expression.arguments[0].body.statements
.map((s) => sourceTextFromNode(context, s))
.join(';\n');
const newStatements = parseSync('index.ts', newContent).program if (next) {
.body as any[]; const nextParam =
next?.params?.items?.[0]?.type === 'FormalParameter'
? sourceTextFromNode(context, next.params.items[0])
: undefined;
if (nextParam) {
newContent = `const ${nextParam} = ${newContent}`;
}
newContent += (next.body?.statements || [])
.map((s) => sourceTextFromNode(context, s))
.join('\n');
}
if (error || complete) {
const errorParam =
error?.params?.items?.[0]?.type === 'FormalParameter' &&
error.params.items[0].pattern.type === 'Identifier'
? sourceTextFromNode(context, error.params.items[0])
: 'err';
const errorParamName =
error?.params?.items?.[0]?.type === 'FormalParameter' &&
error.params.items[0].pattern.type === 'Identifier'
? error.params.items[0].pattern.name
: 'err';
let errorBody = '';
if (error) {
errorBody += (error.body?.statements || [])
.map((s) => sourceTextFromNode(context, s))
.join('\n');
}
if (complete) {
const completBody = `if (${errorParamName} instanceof EmptyError) { ${(complete.body?.statements || []).map((s) => sourceTextFromNode(context, s)).join('\n')}}`;
if (errorBody) {
errorBody = `${completBody} else { ${errorBody} }`;
} else {
errorBody = completBody;
}
}
newContent = `try { ${newContent} } catch (${errorParam}) { ${errorBody} }`;
}
const newNodes = parseSync('index.html', newContent).program.body;
magicString.remove(child.start, child.end); magicString.remove(child.start, child.end);
magicString.appendRight(child.start, newContent); magicString.appendLeft(child.start, newContent);
newChildren.push(...newStatements); newChildren.push(...newNodes);
} else { } else {
newChildren.push(child as any); newChildren.push(child as any);
} }

View File

@ -1,7 +1,7 @@
import { HttpHeaders, HttpParams } from '@ngify/http'; import { HttpHeaders, HttpParams } from '@ngify/http';
import { Injectable, inject } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable } from 'rxjs'; import type { Observable } from 'rxjs';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { HttpBaseService } from './http-base.service'; import { HttpBaseService } from './http-base.service';
const NGSW_CUSTOM_PARAM = 'ngsw-bypass'; const NGSW_CUSTOM_PARAM = 'ngsw-bypass';
@ -41,10 +41,10 @@ export class DataService {
headers = headers.set('Accept', 'application/json'); headers = headers.set('Accept', 'application/json');
if (!!token) { if (token) {
headers = headers.set( headers = headers.set(
'Authorization', 'Authorization',
'Bearer ' + decodeURIComponent(token) `Bearer ${decodeURIComponent(token)}`
); );
} }

View File

@ -1,11 +1,9 @@
import { HttpClient } from '@ngify/http'; import { HttpClient } from '@ngify/http';
import { Injectable, inject } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable } from 'rxjs'; import type { Observable } from 'rxjs';
@Injectable() @Injectable()
export class HttpBaseService { export class HttpBaseService {
constructor() {}
private readonly http = inject(HttpClient); private readonly http = inject(HttpClient);
get<T>(url: string, params?: { [key: string]: unknown }): Observable<T> { get<T>(url: string, params?: { [key: string]: unknown }): Observable<T> {

View File

@ -38,7 +38,7 @@ describe('Auth State Service', () => {
expect(authStateService).toBeTruthy(); expect(authStateService).toBeTruthy();
}); });
it('public authorize$ is observable$', () => { it('authorize$ is observable$', () => {
expect(authStateService.authenticated$).toBeInstanceOf(Observable); expect(authStateService.authenticated$).toBeInstanceOf(Observable);
}); });
@ -269,6 +269,7 @@ describe('Auth State Service', () => {
it('does not crash and store accessToken when authResult is null', () => { it('does not crash and store accessToken when authResult is null', () => {
const spy = vi.spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
// biome-ignore lint/suspicious/noEvolvingTypes: <explanation>
const authResult = null; const authResult = null;
authStateService.setAuthorizationData( authStateService.setAuthorizationData(

View File

@ -257,9 +257,8 @@ export class AuthStateService {
private decodeURIComponentSafely(token: string): string { private decodeURIComponentSafely(token: string): string {
if (token) { if (token) {
return decodeURIComponent(token); return decodeURIComponent(token);
} else {
return '';
} }
return '';
} }
private persistAccessTokenExpirationTime( private persistAccessTokenExpirationTime(

View File

@ -1,4 +1,4 @@
import { ValidationResult } from '../validation/validation-result'; import type { ValidationResult } from '../validation/validation-result';
export interface AuthStateResult { export interface AuthStateResult {
isAuthenticated: boolean; isAuthenticated: boolean;

View File

@ -135,12 +135,14 @@ describe('CheckAuthService', () => {
); );
const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig');
checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe({ try {
error: (err) => { await lastValueFrom(
checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
}, }
});
}); });
it('uses first/default config when no param is passed', async () => { it('uses first/default config when no param is passed', async () => {
@ -153,8 +155,10 @@ describe('CheckAuthService', () => {
]; ];
const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig');
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(spy).toHaveBeenCalledExactlyOnceWith( checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(spy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1', authority: 'some-authority' }, { configId: 'configId1', authority: 'some-authority' },
allConfigs, allConfigs,
undefined undefined
@ -180,17 +184,18 @@ expect(spy).toHaveBeenCalledExactlyOnceWith(
vi.spyOn(popUpService, 'isCurrentlyInPopup').mockReturnValue(true); vi.spyOn(popUpService, 'isCurrentlyInPopup').mockReturnValue(true);
const popupSpy = vi.spyOn(popUpService, 'sendMessageToMainWindow'); const popupSpy = vi.spyOn(popUpService, 'sendMessageToMainWindow');
const result = await lastValueFrom(checkAuthService const result = await lastValueFrom(
.checkAuth(allConfigs[0]!, allConfigs)); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
expect(result).toEqual({ );
expect(result).toEqual({
isAuthenticated: false, isAuthenticated: false,
errorMessage: '', errorMessage: '',
userData: null, userData: null,
idToken: '', idToken: '',
accessToken: '', accessToken: '',
configId: '', configId: '',
});; });
expect(popupSpy).toHaveBeenCalled(); expect(popupSpy).toHaveBeenCalled();
}); });
it('returns isAuthenticated: false with error message in case handleCallbackAndFireEvents throws an error', async () => { it('returns isAuthenticated: false with error message in case handleCallbackAndFireEvents throws an error', async () => {
@ -211,17 +216,18 @@ expect(popupSpy).toHaveBeenCalled();
'http://localhost:4200' 'http://localhost:4200'
); );
const result = await lastValueFrom(checkAuthService const result = await lastValueFrom(
.checkAuth(allConfigs[0]!, allConfigs)); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
expect(result).toEqual({ );
expect(result).toEqual({
isAuthenticated: false, isAuthenticated: false,
errorMessage: 'ERROR', errorMessage: 'ERROR',
configId: 'configId1', configId: 'configId1',
idToken: '', idToken: '',
userData: null, userData: null,
accessToken: '', accessToken: '',
});; });
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
}); });
it('calls callbackService.handlePossibleStsCallback with current url when callback is true', async () => { it('calls callbackService.handlePossibleStsCallback with current url when callback is true', async () => {
@ -243,16 +249,17 @@ expect(spy).toHaveBeenCalled();
.spyOn(callBackService, 'handleCallbackAndFireEvents') .spyOn(callBackService, 'handleCallbackAndFireEvents')
.mockReturnValue(of({} as CallbackContext)); .mockReturnValue(of({} as CallbackContext));
const result = await lastValueFrom(checkAuthService const result = await lastValueFrom(
.checkAuth(allConfigs[0]!, allConfigs)); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
expect(result).toEqual({ );
expect(result).toEqual({
isAuthenticated: true, isAuthenticated: true,
userData: undefined, userData: undefined,
accessToken: 'at', accessToken: 'at',
configId: 'configId1', configId: 'configId1',
idToken: 'idt', idToken: 'idt',
});; });
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
}); });
it('does NOT call handleCallbackAndFireEvents with current url when callback is false', async () => { it('does NOT call handleCallbackAndFireEvents with current url when callback is false', async () => {
@ -275,16 +282,17 @@ expect(spy).toHaveBeenCalled();
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('at'); vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('at');
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idt'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idt');
const result = await lastValueFrom(checkAuthService const result = await lastValueFrom(
.checkAuth(allConfigs[0]!, allConfigs)); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
expect(result).toEqual({ );
expect(result).toEqual({
isAuthenticated: true, isAuthenticated: true,
userData: undefined, userData: undefined,
accessToken: 'at', accessToken: 'at',
configId: 'configId1', configId: 'configId1',
idToken: 'idt', idToken: 'idt',
});; });
expect(spy).not.toHaveBeenCalled(); 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', async () => { it('does fire the auth and user data events when it is not a callback from the security token service and is authenticated', async () => {
@ -314,9 +322,10 @@ expect(spy).not.toHaveBeenCalled();
); );
const userServiceSpy = vi.spyOn(userService, 'publishUserDataIfExists'); const userServiceSpy = vi.spyOn(userService, 'publishUserDataIfExists');
const result = await lastValueFrom(checkAuthService const result = await lastValueFrom(
.checkAuth(allConfigs[0]!, allConfigs)); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
expect(result).toEqual({ );
expect(result).toEqual({
isAuthenticated: true, isAuthenticated: true,
userData: { userData: {
some: 'user-data', some: 'user-data',
@ -324,9 +333,9 @@ expect(result).toEqual({
accessToken: 'at', accessToken: 'at',
configId: 'configId1', configId: 'configId1',
idToken: 'idt', idToken: 'idt',
});; });
expect(setAuthorizedAndFireEventSpy).toHaveBeenCalled();; expect(setAuthorizedAndFireEventSpy).toHaveBeenCalled();
expect(userServiceSpy).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', async () => { 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 () => {
@ -353,17 +362,18 @@ expect(userServiceSpy).toHaveBeenCalled();
); );
const userServiceSpy = vi.spyOn(userService, 'publishUserDataIfExists'); const userServiceSpy = vi.spyOn(userService, 'publishUserDataIfExists');
const result = await lastValueFrom(checkAuthService const result = await lastValueFrom(
.checkAuth(allConfigs[0]!, allConfigs)); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
expect(result).toEqual({ );
expect(result).toEqual({
isAuthenticated: false, isAuthenticated: false,
userData: undefined, userData: undefined,
accessToken: 'at', accessToken: 'at',
configId: 'configId1', configId: 'configId1',
idToken: 'it', idToken: 'it',
});; });
expect(setAuthorizedAndFireEventSpy).not.toHaveBeenCalled();; expect(setAuthorizedAndFireEventSpy).not.toHaveBeenCalled();
expect(userServiceSpy).not.toHaveBeenCalled(); expect(userServiceSpy).not.toHaveBeenCalled();
}); });
it('if authenticated return true', async () => { it('if authenticated return true', async () => {
@ -383,9 +393,10 @@ expect(userServiceSpy).not.toHaveBeenCalled();
true true
); );
const result = await lastValueFrom(checkAuthService const result = await lastValueFrom(
.checkAuth(allConfigs[0]!, allConfigs)); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
expect(result).toEqual({ );
expect(result).toEqual({
isAuthenticated: true, isAuthenticated: true,
userData: undefined, userData: undefined,
accessToken: 'at', accessToken: 'at',
@ -409,8 +420,10 @@ expect(result).toEqual({
const spy = vi.spyOn(authStateService, 'setAuthenticatedAndFireEvent'); const spy = vi.spyOn(authStateService, 'setAuthenticatedAndFireEvent');
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(spy).toHaveBeenCalled(); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(spy).toHaveBeenCalled();
}); });
it('if authenticated publishUserdataIfExists', async () => { it('if authenticated publishUserdataIfExists', async () => {
@ -430,8 +443,10 @@ expect(spy).toHaveBeenCalled();
const spy = vi.spyOn(userService, 'publishUserDataIfExists'); const spy = vi.spyOn(userService, 'publishUserDataIfExists');
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(spy).toHaveBeenCalled(); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(spy).toHaveBeenCalled();
}); });
it('if authenticated callbackService startTokenValidationPeriodically', async () => { it('if authenticated callbackService startTokenValidationPeriodically', async () => {
@ -455,8 +470,10 @@ expect(spy).toHaveBeenCalled();
'startTokenValidationPeriodically' 'startTokenValidationPeriodically'
); );
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(spy).toHaveBeenCalled(); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(spy).toHaveBeenCalled();
}); });
it('if isCheckSessionConfigured call checkSessionService.start()', async () => { it('if isCheckSessionConfigured call checkSessionService.start()', async () => {
@ -478,8 +495,10 @@ expect(spy).toHaveBeenCalled();
); );
const spy = vi.spyOn(checkSessionService, 'start'); const spy = vi.spyOn(checkSessionService, 'start');
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(spy).toHaveBeenCalled(); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(spy).toHaveBeenCalled();
}); });
it('if isSilentRenewConfigured call getOrCreateIframe()', async () => { it('if isSilentRenewConfigured call getOrCreateIframe()', async () => {
@ -501,8 +520,10 @@ expect(spy).toHaveBeenCalled();
); );
const spy = vi.spyOn(silentRenewService, 'getOrCreateIframe'); const spy = vi.spyOn(silentRenewService, 'getOrCreateIframe');
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(spy).toHaveBeenCalled(); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(spy).toHaveBeenCalled();
}); });
it('calls checkSavedRedirectRouteAndNavigate if authenticated', async () => { it('calls checkSavedRedirectRouteAndNavigate if authenticated', async () => {
@ -524,9 +545,11 @@ expect(spy).toHaveBeenCalled();
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(spy).toHaveBeenCalledTimes(1);; checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
expect(spy).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); );
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledExactlyOnceWith(allConfigs[0]);
}); });
it('does not call checkSavedRedirectRouteAndNavigate if not authenticated', async () => { it('does not call checkSavedRedirectRouteAndNavigate if not authenticated', async () => {
@ -545,8 +568,10 @@ expect(spy).toHaveBeenCalledExactlyOnceWith(allConfigs[0]);
'checkSavedRedirectRouteAndNavigate' 'checkSavedRedirectRouteAndNavigate'
); );
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(spy).toHaveBeenCalledTimes(0); checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(spy).toHaveBeenCalledTimes(0);
}); });
it('fires CheckingAuth-Event on start and finished event on end', async () => { it('fires CheckingAuth-Event on start and finished event on end', async () => {
@ -563,8 +588,10 @@ expect(spy).toHaveBeenCalledTimes(0);
const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent'); const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent');
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(fireEventSpy).toHaveBeenCalledWith([ checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(fireEventSpy).toHaveBeenCalledWith([
[EventTypes.CheckingAuth], [EventTypes.CheckingAuth],
[EventTypes.CheckingAuthFinished], [EventTypes.CheckingAuthFinished],
]); ]);
@ -584,8 +611,10 @@ expect(fireEventSpy).toHaveBeenCalledWith([
'http://localhost:4200' 'http://localhost:4200'
); );
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(fireEventSpy).toHaveBeenCalledWith([ checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(fireEventSpy).toHaveBeenCalledWith([
[EventTypes.CheckingAuth], [EventTypes.CheckingAuth],
[EventTypes.CheckingAuthFinishedWithError, 'ERROR'], [EventTypes.CheckingAuthFinishedWithError, 'ERROR'],
]); ]);
@ -605,8 +634,10 @@ expect(fireEventSpy).toHaveBeenCalledWith([
const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent'); const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent');
await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); await lastValueFrom(
expect(fireEventSpy).toBeCalledWith([ checkAuthService.checkAuth(allConfigs[0]!, allConfigs)
);
expect(fireEventSpy).toBeCalledWith([
[EventTypes.CheckingAuth], [EventTypes.CheckingAuth],
[EventTypes.CheckingAuthFinished], [EventTypes.CheckingAuthFinished],
]); ]);
@ -634,9 +665,10 @@ expect(fireEventSpy).toBeCalledWith([
); );
const spy = vi.spyOn(silentRenewService, 'getOrCreateIframe'); const spy = vi.spyOn(silentRenewService, 'getOrCreateIframe');
await lastValueFrom(checkAuthService await lastValueFrom(
.checkAuthIncludingServer(allConfigs[0]!, allConfigs)); checkAuthService.checkAuthIncludingServer(allConfigs[0]!, allConfigs)
expect(spy).toHaveBeenCalled(); );
expect(spy).toHaveBeenCalled();
}); });
it('does forceRefreshSession get called and is NOT authenticated', async () => { it('does forceRefreshSession get called and is NOT authenticated', async () => {
@ -662,9 +694,10 @@ expect(spy).toHaveBeenCalled();
}) })
); );
const result = await lastValueFrom(checkAuthService const result = await lastValueFrom(
.checkAuthIncludingServer(allConfigs[0]!, allConfigs)); checkAuthService.checkAuthIncludingServer(allConfigs[0]!, allConfigs)
expect(result).toBeTruthy(); );
expect(result).toBeTruthy();
}); });
it('should start check session and validation after forceRefreshSession has been called and is authenticated after forcing with silentrenew', async () => { it('should start check session and validation after forceRefreshSession has been called and is authenticated after forcing with silentrenew', async () => {
@ -709,13 +742,14 @@ expect(result).toBeTruthy();
}) })
); );
await lastValueFrom(checkAuthService await lastValueFrom(
.checkAuthIncludingServer(allConfigs[0]!, allConfigs)); checkAuthService.checkAuthIncludingServer(allConfigs[0]!, allConfigs)
expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith( );
expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith(
allConfigs[0] allConfigs[0]
);; );
expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1);; expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1);
expect(getOrCreateIframeSpy).toHaveBeenCalledExactlyOnceWith( expect(getOrCreateIframeSpy).toHaveBeenCalledExactlyOnceWith(
allConfigs[0] allConfigs[0]
); );
}); });
@ -762,13 +796,14 @@ expect(getOrCreateIframeSpy).toHaveBeenCalledExactlyOnceWith(
}) })
); );
await lastValueFrom(checkAuthService await lastValueFrom(
.checkAuthIncludingServer(allConfigs[0]!, allConfigs)); checkAuthService.checkAuthIncludingServer(allConfigs[0]!, allConfigs)
expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith( );
expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith(
allConfigs[0] allConfigs[0]
);; );
expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1);; expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1);
expect(getOrCreateIframeSpy).not.toHaveBeenCalled(); expect(getOrCreateIframeSpy).not.toHaveBeenCalled();
}); });
}); });
@ -790,15 +825,17 @@ expect(getOrCreateIframeSpy).not.toHaveBeenCalled();
); );
const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig');
const result = await lastValueFrom(checkAuthService.checkAuthMultiple(allConfigs)); const result = await lastValueFrom(
expect(Array.isArray(result)).toBe(true);; checkAuthService.checkAuthMultiple(allConfigs)
expect(spy).toHaveBeenCalledTimes(2);; );
expect(vi.mocked(spy).mock.calls[0]).toEqual([ expect(Array.isArray(result)).toBe(true);
expect(spy).toHaveBeenCalledTimes(2);
expect(vi.mocked(spy).mock.calls[0]).toEqual([
allConfigs[0]!, allConfigs[0]!,
allConfigs, allConfigs,
undefined, undefined,
]);; ]);
expect(vi.mocked(spy).mock.calls[1]).toEqual([ expect(vi.mocked(spy).mock.calls[1]).toEqual([
allConfigs[1], allConfigs[1],
allConfigs, allConfigs,
undefined, undefined,
@ -818,9 +855,11 @@ expect(vi.mocked(spy).mock.calls[1]).toEqual([
const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig');
const result = await lastValueFrom(checkAuthService.checkAuthMultiple(allConfigs)); const result = await lastValueFrom(
expect(Array.isArray(result)).toBe(true);; checkAuthService.checkAuthMultiple(allConfigs)
expect(spy).toBeCalledWith([ );
expect(Array.isArray(result)).toBe(true);
expect(spy).toBeCalledWith([
[ [
{ configId: 'configId1', authority: 'some-authority1' }, { configId: 'configId1', authority: 'some-authority1' },
allConfigs, allConfigs,
@ -847,15 +886,17 @@ expect(spy).toBeCalledWith([
const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig');
const result = await lastValueFrom(checkAuthService.checkAuthMultiple(allConfigs)); const result = await lastValueFrom(
expect(Array.isArray(result)).toBe(true);; checkAuthService.checkAuthMultiple(allConfigs)
expect(spy).toHaveBeenCalledTimes(2);; );
expect(vi.mocked(spy).mock.calls[0]).toEqual([ expect(Array.isArray(result)).toBe(true);
expect(spy).toHaveBeenCalledTimes(2);
expect(vi.mocked(spy).mock.calls[0]).toEqual([
{ configId: 'configId1', authority: 'some-authority1' }, { configId: 'configId1', authority: 'some-authority1' },
allConfigs, allConfigs,
undefined, undefined,
]);; ]);
expect(vi.mocked(spy).mock.calls[1]).toEqual([ expect(vi.mocked(spy).mock.calls[1]).toEqual([
{ configId: 'configId2', authority: 'some-authority2' }, { configId: 'configId2', authority: 'some-authority2' },
allConfigs, allConfigs,
undefined, undefined,
@ -870,13 +911,13 @@ expect(vi.mocked(spy).mock.calls[1]).toEqual([
const allConfigs: OpenIdConfiguration[] = []; const allConfigs: OpenIdConfiguration[] = [];
checkAuthService.checkAuthMultiple(allConfigs).subscribe({ try {
error: (error) => { await lastValueFrom(checkAuthService.checkAuthMultiple(allConfigs));
} catch (error: any) {
expect(error.message).toEqual( expect(error.message).toEqual(
'could not find matching config for state the-state-param' 'could not find matching config for state the-state-param'
); );
}, }
});
}); });
}); });
}); });

View File

@ -57,7 +57,7 @@ export class CheckAuthService {
const stateParamFromUrl = const stateParamFromUrl =
this.currentUrlService.getStateParamFromCurrentUrl(url); this.currentUrlService.getStateParamFromCurrentUrl(url);
return Boolean(stateParamFromUrl) return stateParamFromUrl
? this.getConfigurationWithUrlState([configuration], stateParamFromUrl) ? this.getConfigurationWithUrlState([configuration], stateParamFromUrl)
: configuration; : configuration;
} }

View File

@ -1,6 +1,5 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { vi } from 'vitest';
import { PASSED_CONFIG } from './auth-config'; import { PASSED_CONFIG } from './auth-config';
import { AuthModule } from './auth.module'; import { AuthModule } from './auth.module';
import { ConfigurationService } from './config/config.service'; import { ConfigurationService } from './config/config.service';

View File

@ -4,7 +4,7 @@ import {
type ActivatedRouteSnapshot, type ActivatedRouteSnapshot,
type RouterStateSnapshot, type RouterStateSnapshot,
} from 'oidc-client-rx'; } from 'oidc-client-rx';
import { of } from 'rxjs'; import { lastValueFrom, of } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { CheckAuthService } from '../auth-state/check-auth.service'; import { CheckAuthService } from '../auth-state/check-auth.service';

View File

@ -16,9 +16,6 @@ describe('AutoLoginService ', () => {
imports: [RouterTestingModule], imports: [RouterTestingModule],
providers: [AutoLoginService, mockProvider(StoragePersistenceService)], providers: [AutoLoginService, mockProvider(StoragePersistenceService)],
}); });
});
beforeEach(() => {
router = TestBed.inject(Router); router = TestBed.inject(Router);
autoLoginService = TestBed.inject(AutoLoginService); autoLoginService = TestBed.inject(AutoLoginService);
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);

View File

@ -1,6 +1,6 @@
import { inject, Injectable } from 'injection-js'; import { inject, Injectable } from 'injection-js';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
const STORAGE_KEY = 'redirect'; const STORAGE_KEY = 'redirect';

View File

@ -1,6 +1,6 @@
import { TestBed, mockRouterProvider } from '@/testing'; import { TestBed, mockRouterProvider } from '@/testing';
import { AbstractRouter } from 'oidc-client-rx'; import { AbstractRouter } from 'oidc-client-rx';
import { of, throwError } from 'rxjs'; import { lastValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import type { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
@ -85,13 +85,18 @@ describe('CodeFlowCallbackService ', () => {
triggerAuthorizationResultEvent: true, triggerAuthorizationResultEvent: true,
}; };
await lastValueFrom(codeFlowCallbackService await lastValueFrom(
.authenticatedCallbackWithCode('some-url2', config, [config])); codeFlowCallbackService.authenticatedCallbackWithCode(
expect(spy).toHaveBeenCalledExactlyOnceWith('some-url2', config, [ 'some-url2',
config, config,
]);; [config]
expect(routerSpy).not.toHaveBeenCalled();; )
expect(flowsDataSpy).toHaveBeenCalled(); );
expect(spy).toHaveBeenCalledExactlyOnceWith('some-url2', config, [
config,
]);
expect(routerSpy).not.toHaveBeenCalled();
expect(flowsDataSpy).toHaveBeenCalled();
}); });
it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => { it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => {
@ -120,13 +125,18 @@ expect(flowsDataSpy).toHaveBeenCalled();
postLoginRoute: 'postLoginRoute', postLoginRoute: 'postLoginRoute',
}; };
await lastValueFrom(codeFlowCallbackService await lastValueFrom(
.authenticatedCallbackWithCode('some-url3', config, [config])); codeFlowCallbackService.authenticatedCallbackWithCode(
expect(spy).toHaveBeenCalledExactlyOnceWith('some-url3', config, [ 'some-url3',
config, config,
]);; [config]
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');; )
expect(flowsDataSpy).toHaveBeenCalled(); );
expect(spy).toHaveBeenCalledExactlyOnceWith('some-url3', config, [
config,
]);
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
expect(flowsDataSpy).toHaveBeenCalled();
}); });
it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', async () => { it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', async () => {
@ -152,16 +162,20 @@ expect(flowsDataSpy).toHaveBeenCalled();
postLoginRoute: 'postLoginRoute', postLoginRoute: 'postLoginRoute',
}; };
codeFlowCallbackService try {
.authenticatedCallbackWithCode('some-url4', config, [config]) await lastValueFrom(
.subscribe({ codeFlowCallbackService.authenticatedCallbackWithCode(
error: (err) => { 'some-url4',
config,
[config]
)
);
} catch (err: any) {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled(); expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it(`navigates to unauthorizedRoute in case of error and in case of error and it(`navigates to unauthorizedRoute in case of error and in case of error and
@ -186,18 +200,20 @@ expect(flowsDataSpy).toHaveBeenCalled();
unauthorizedRoute: 'unauthorizedRoute', unauthorizedRoute: 'unauthorizedRoute',
}; };
codeFlowCallbackService try {
.authenticatedCallbackWithCode('some-url5', config, [config]) await lastValueFrom(
.subscribe({ codeFlowCallbackService.authenticatedCallbackWithCode(
error: (err) => { 'some-url5',
config,
[config]
)
);
} catch (err: any) {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(routerSpy).toHaveBeenCalledExactlyOnceWith( expect(routerSpy).toHaveBeenCalledExactlyOnceWith('unauthorizedRoute');
'unauthorizedRoute' }
);
},
});
}); });
}); });
}); });

View File

@ -1,6 +1,6 @@
import { TestBed, mockRouterProvider } from '@/testing'; import { TestBed, mockRouterProvider } from '@/testing';
import { AbstractRouter } from 'oidc-client-rx'; import { AbstractRouter } from 'oidc-client-rx';
import { of, throwError } from 'rxjs'; import { lastValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import type { CallbackContext } from '../flows/callback-context'; import type { CallbackContext } from '../flows/callback-context';
import { FlowsDataService } from '../flows/flows-data.service'; import { FlowsDataService } from '../flows/flows-data.service';
@ -81,14 +81,19 @@ describe('ImplicitFlowCallbackService ', () => {
triggerAuthorizationResultEvent: true, triggerAuthorizationResultEvent: true,
}; };
await lastValueFrom(implicitFlowCallbackService await lastValueFrom(
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')); implicitFlowCallbackService.authenticatedImplicitFlowCallback(
expect(spy).toHaveBeenCalledExactlyOnceWith(
config, config,
[config], [config],
'some-hash' 'some-hash'
);; )
expect(routerSpy).not.toHaveBeenCalled(); );
expect(spy).toHaveBeenCalledExactlyOnceWith(
config,
[config],
'some-hash'
);
expect(routerSpy).not.toHaveBeenCalled();
}); });
it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => { it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => {
@ -113,14 +118,19 @@ expect(routerSpy).not.toHaveBeenCalled();
postLoginRoute: 'postLoginRoute', postLoginRoute: 'postLoginRoute',
}; };
await lastValueFrom(implicitFlowCallbackService await lastValueFrom(
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')); implicitFlowCallbackService.authenticatedImplicitFlowCallback(
expect(spy).toHaveBeenCalledExactlyOnceWith(
config, config,
[config], [config],
'some-hash' 'some-hash'
);; )
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute'); );
expect(spy).toHaveBeenCalledExactlyOnceWith(
config,
[config],
'some-hash'
);
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
}); });
it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', async () => { it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', async () => {
@ -141,15 +151,19 @@ expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
postLoginRoute: 'postLoginRoute', postLoginRoute: 'postLoginRoute',
}; };
implicitFlowCallbackService try {
.authenticatedImplicitFlowCallback(config, [config], 'some-hash') await lastValueFrom(
.subscribe({ implicitFlowCallbackService.authenticatedImplicitFlowCallback(
error: (err) => { config,
[config],
'some-hash'
)
);
} catch (err: any) {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled(); expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it(`navigates to unauthorizedRoute in case of error and in case of error and it(`navigates to unauthorizedRoute in case of error and in case of error and
@ -173,18 +187,20 @@ expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
unauthorizedRoute: 'unauthorizedRoute', unauthorizedRoute: 'unauthorizedRoute',
}; };
implicitFlowCallbackService try {
.authenticatedImplicitFlowCallback(config, [config], 'some-hash') await lastValueFrom(
.subscribe({ implicitFlowCallbackService.authenticatedImplicitFlowCallback(
error: (err) => { config,
[config],
'some-hash'
)
);
} catch (err: any) {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(routerSpy).toHaveBeenCalledExactlyOnceWith( expect(routerSpy).toHaveBeenCalledExactlyOnceWith('unauthorizedRoute');
'unauthorizedRoute' }
);
},
});
}); });
}); });
}); });

View File

@ -1,4 +1,4 @@
import { TestBed, fakeAsync, tick } from '@/testing'; import { TestBed } from '@/testing';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { IntervalService } from './interval.service'; import { IntervalService } from './interval.service';
@ -19,9 +19,6 @@ describe('IntervalService', () => {
}, },
], ],
}); });
});
beforeEach(() => {
intervalService = TestBed.inject(IntervalService); intervalService = TestBed.inject(IntervalService);
}); });
@ -60,15 +57,15 @@ describe('IntervalService', () => {
describe('startPeriodicTokenCheck', () => { describe('startPeriodicTokenCheck', () => {
it('starts check after correct milliseconds', async () => { it('starts check after correct milliseconds', async () => {
const periodicCheck = intervalService.startPeriodicTokenCheck(0.5); const periodicCheck = intervalService.startPeriodicTokenCheck(0.5);
const spy = jasmine.createSpy(); const spy = vi.fn();
const sub = periodicCheck.subscribe(() => { const sub = periodicCheck.subscribe(() => {
spy(); spy();
}); });
tick(500); await vi.advanceTimersByTimeAsync(500);
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
tick(500); await vi.advanceTimersByTimeAsync(500);
expect(spy).toHaveBeenCalledTimes(2); expect(spy).toHaveBeenCalledTimes(2);
sub.unsubscribe(); sub.unsubscribe();

View File

@ -62,16 +62,17 @@ describe('RefreshSessionRefreshTokenService', () => {
'resetAuthorizationData' 'resetAuthorizationData'
); );
refreshSessionRefreshTokenService try {
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [ await lastValueFrom(
refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(
{ configId: 'configId1' }, { configId: 'configId1' },
]) [{ configId: 'configId1' }]
.subscribe({ )
error: (err) => { );
} catch (err: any) {
expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('finalize with stopPeriodicTokenCheck in case of error', async () => { it('finalize with stopPeriodicTokenCheck in case of error', async () => {
@ -83,15 +84,16 @@ describe('RefreshSessionRefreshTokenService', () => {
'stopPeriodicTokenCheck' 'stopPeriodicTokenCheck'
); );
refreshSessionRefreshTokenService try {
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [ await lastValueFrom(
refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(
{ configId: 'configId1' }, { configId: 'configId1' },
]) [{ configId: 'configId1' }]
.subscribe({ )
error: (err) => { );
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled(); expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
}); });

View File

@ -1,5 +1,5 @@
import { TestBed, spyOnProperty } from '@/testing'; import { TestBed, spyOnProperty } from '@/testing';
import { lastValueFrom, of, throwError } from 'rxjs'; import { EmptyError, lastValueFrom, of, throwError } from 'rxjs';
import { delay } from 'rxjs/operators'; import { delay } from 'rxjs/operators';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
@ -216,18 +216,22 @@ describe('RefreshSessionService ', () => {
}, },
]; ];
refreshSessionService try {
.userForceRefreshSession(allConfigs[0]!, allConfigs) await lastValueFrom(
.subscribe({ refreshSessionService.userForceRefreshSession(
error: () => { allConfigs[0]!,
expect.fail('It should not return any error.'); allConfigs
}, )
complete: () => { );
} catch (err: any) {
if (err instanceof EmptyError) {
expect( expect(
flowsDataService.resetSilentRenewRunning flowsDataService.resetSilentRenewRunning
).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); ).toHaveBeenCalledExactlyOnceWith(allConfigs[0]);
}, } else {
}); expect.fail('It should not return any error.');
}
}
}); });
}); });
@ -448,18 +452,16 @@ describe('RefreshSessionService ', () => {
'resetSilentRenewRunning' 'resetSilentRenewRunning'
); );
refreshSessionService try {
.forceRefreshSession(allConfigs[0]!, allConfigs) await lastValueFrom(
.subscribe({ refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
next: () => { );
expect.fail('It should not return any result.'); expect.fail('It should not return any result.');
}, } catch (error: any) {
error: (error) => {
expect(error).toBeInstanceOf(Error); expect(error).toBeInstanceOf(Error);
expect(error.message).toEqual(`Error: ${expectedErrorMessage}`); expect(error.message).toEqual(`Error: ${expectedErrorMessage}`);
expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled(); expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled();
}, }
});
}); });
describe('NOT isCurrentFlowCodeFlowWithRefreshTokens', () => { describe('NOT isCurrentFlowCodeFlowWithRefreshTokens', () => {

View File

@ -39,9 +39,6 @@ describe('AuthWellKnownDataService', () => {
mockProvider(LoggerService), mockProvider(LoggerService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(AuthWellKnownDataService); service = TestBed.inject(AuthWellKnownDataService);
loggerService = TestBed.inject(LoggerService); loggerService = TestBed.inject(LoggerService);
dataService = TestBed.inject(DataService); dataService = TestBed.inject(DataService);
@ -73,7 +70,7 @@ describe('AuthWellKnownDataService', () => {
const dataServiceSpy = vi const dataServiceSpy = vi
.spyOn(dataService, 'get') .spyOn(dataService, 'get')
.mockReturnValue(of(null)); .mockReturnValue(of(null));
const urlWithSuffix = `myUrl/.well-known/openid-configuration`; const urlWithSuffix = 'myUrl/.well-known/openid-configuration';
await lastValueFrom( await lastValueFrom(
(service as any).getWellKnownDocument(urlWithSuffix, { (service as any).getWellKnownDocument(urlWithSuffix, {
@ -89,7 +86,8 @@ describe('AuthWellKnownDataService', () => {
const dataServiceSpy = vi const dataServiceSpy = vi
.spyOn(dataService, 'get') .spyOn(dataService, 'get')
.mockReturnValue(of(null)); .mockReturnValue(of(null));
const urlWithSuffix = `myUrl/.well-known/openid-configuration/and/some/more/stuff`; const urlWithSuffix =
'myUrl/.well-known/openid-configuration/and/some/more/stuff';
await lastValueFrom( await lastValueFrom(
(service as any).getWellKnownDocument(urlWithSuffix, { (service as any).getWellKnownDocument(urlWithSuffix, {
@ -105,7 +103,7 @@ describe('AuthWellKnownDataService', () => {
const dataServiceSpy = vi const dataServiceSpy = vi
.spyOn(dataService, 'get') .spyOn(dataService, 'get')
.mockReturnValue(of(null)); .mockReturnValue(of(null));
const urlWithoutSuffix = `myUrl`; const urlWithoutSuffix = 'myUrl';
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`; const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`;
await lastValueFrom( await lastValueFrom(
@ -128,14 +126,13 @@ describe('AuthWellKnownDataService', () => {
) )
); );
(service as any) const res: unknown = await lastValueFrom(
.getWellKnownDocument('anyurl', { configId: 'configId1' }) (service as any).getWellKnownDocument('anyurl', {
.subscribe({ configId: 'configId1',
next: (res: unknown) => { })
);
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
},
});
}); });
it('should retry twice', async () => { it('should retry twice', async () => {
@ -147,14 +144,13 @@ describe('AuthWellKnownDataService', () => {
) )
); );
(service as any) const res: any = await lastValueFrom(
.getWellKnownDocument('anyurl', { configId: 'configId1' }) (service as any).getWellKnownDocument('anyurl', {
.subscribe({ configId: 'configId1',
next: (res: any) => { })
);
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
},
});
}); });
it('should fail after three tries', async () => { it('should fail after three tries', async () => {
@ -167,11 +163,13 @@ describe('AuthWellKnownDataService', () => {
) )
); );
(service as any).getWellKnownDocument('anyurl', 'configId').subscribe({ try {
error: (err: unknown) => { await lastValueFrom(
(service as any).getWellKnownDocument('anyurl', 'configId')
);
} catch (err: unknown) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
}); });
@ -181,7 +179,7 @@ describe('AuthWellKnownDataService', () => {
of({ jwks_uri: 'jwks_uri' }) of({ jwks_uri: 'jwks_uri' })
); );
const spy = vi.spyOn(service as any, 'getWellKnownDocument')(); const spy = vi.spyOn(service as any, 'getWellKnownDocument');
const result = await lastValueFrom( const result = await lastValueFrom(
service.getWellKnownEndPointsForConfig({ service.getWellKnownEndPointsForConfig({
@ -201,15 +199,15 @@ describe('AuthWellKnownDataService', () => {
authWellknownEndpointUrl: undefined, authWellknownEndpointUrl: undefined,
}; };
service.getWellKnownEndPointsForConfig(config).subscribe({ try {
error: (error) => { await lastValueFrom(service.getWellKnownEndPointsForConfig(config));
} catch (error: any) {
expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
config, config,
'no authWellknownEndpoint given!' 'no authWellknownEndpoint given!'
); );
expect(error.message).toEqual('no authWellknownEndpoint given!'); expect(error.message).toEqual('no authWellknownEndpoint given!');
}, }
});
}); });
it('should merge the mapped endpoints with the provided endpoints', async () => { it('should merge the mapped endpoints with the provided endpoints', async () => {
@ -233,7 +231,7 @@ describe('AuthWellKnownDataService', () => {
}, },
}) })
); );
expect(result).toEqual(jasmine.objectContaining(expected)); expect(result).toEqual(expect.objectContaining(expected));
}); });
}); });
}); });

View File

@ -1,12 +1,12 @@
import { inject, Injectable } from 'injection-js'; import { inject, Injectable } from 'injection-js';
import { Observable, throwError } from 'rxjs'; import { type Observable, throwError } from 'rxjs';
import { map, retry } from 'rxjs/operators'; import { map, retry } from 'rxjs/operators';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { OpenIdConfiguration } from '../openid-configuration'; import type { OpenIdConfiguration } from '../openid-configuration';
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints'; import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
const WELL_KNOWN_SUFFIX = `/.well-known/openid-configuration`; const WELL_KNOWN_SUFFIX = '/.well-known/openid-configuration';
@Injectable() @Injectable()
export class AuthWellKnownDataService { export class AuthWellKnownDataService {

View File

@ -35,15 +35,15 @@ describe('AuthWellKnownService', () => {
describe('getAuthWellKnownEndPoints', () => { describe('getAuthWellKnownEndPoints', () => {
it('getAuthWellKnownEndPoints throws an error if not config provided', async () => { it('getAuthWellKnownEndPoints throws an error if not config provided', async () => {
service.queryAndStoreAuthWellKnownEndPoints(null).subscribe({ try {
error: (error) => { await lastValueFrom(service.queryAndStoreAuthWellKnownEndPoints(null));
} catch (error) {
expect(error).toEqual( expect(error).toEqual(
new Error( new Error(
'Please provide a configuration before setting up the module' 'Please provide a configuration before setting up the module'
) )
); );
}, }
});
}); });
it('getAuthWellKnownEndPoints calls always dataservice', async () => { it('getAuthWellKnownEndPoints calls always dataservice', async () => {
@ -91,18 +91,18 @@ describe('AuthWellKnownService', () => {
); );
const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent'); const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent');
service try {
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' }) await lastValueFrom(
.subscribe({ service.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
error: (err) => { );
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1); expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1);
expect(publicEventsServiceSpy).toHaveBeenCalledExactlyOnceWith( expect(publicEventsServiceSpy).toHaveBeenCalledExactlyOnceWith(
EventTypes.ConfigLoadingFailed, EventTypes.ConfigLoadingFailed,
null null
); );
}, }
});
}); });
}); });
}); });

View File

@ -1,12 +1,12 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, throwError } from 'rxjs'; import { type Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators'; import { catchError, tap } from 'rxjs/operators';
import { EventTypes } from '../../public-events/event-types'; import { EventTypes } from '../../public-events/event-types';
import { PublicEventsService } from '../../public-events/public-events.service'; import { PublicEventsService } from '../../public-events/public-events.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { OpenIdConfiguration } from '../openid-configuration'; import type { OpenIdConfiguration } from '../openid-configuration';
import { AuthWellKnownDataService } from './auth-well-known-data.service'; import { AuthWellKnownDataService } from './auth-well-known-data.service';
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints'; import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
@Injectable() @Injectable()
export class AuthWellKnownService { export class AuthWellKnownService {

View File

@ -276,7 +276,9 @@ describe('Configuration Service', () => {
false false
); );
await lastValueFrom(configService.getOpenIDConfigurations()); const { allConfigs, currentConfig } = await lastValueFrom(
configService.getOpenIDConfigurations()
);
expect(allConfigs).toEqual([]); expect(allConfigs).toEqual([]);
expect(currentConfig).toBeNull(); expect(currentConfig).toBeNull();
}); });

View File

@ -1,5 +1,5 @@
import { LogLevel } from '../logging/log-level'; import { LogLevel } from '../logging/log-level';
import { OpenIdConfiguration } from './openid-configuration'; import type { OpenIdConfiguration } from './openid-configuration';
export const DEFAULT_CONFIG: OpenIdConfiguration = { export const DEFAULT_CONFIG: OpenIdConfiguration = {
authority: 'https://please_set', authority: 'https://please_set',

View File

@ -1,4 +1,3 @@
import { waitForAsync } from '@/testing';
import { lastValueFrom, of } from 'rxjs'; import { lastValueFrom, of } from 'rxjs';
import type { OpenIdConfiguration } from '../openid-configuration'; import type { OpenIdConfiguration } from '../openid-configuration';
import { StsConfigHttpLoader, StsConfigStaticLoader } from './config-loader'; import { StsConfigHttpLoader, StsConfigStaticLoader } from './config-loader';
@ -46,8 +45,8 @@ describe('ConfigLoader', () => {
const result = await lastValueFrom(result$); const result = await lastValueFrom(result$);
expect(Array.isArray(result)).toBeTruthy(); expect(Array.isArray(result)).toBeTruthy();
expect(result[0].configId).toBe('configId1'); expect(result[0]!.configId).toBe('configId1');
expect(result[1].configId).toBe('configId2'); expect(result[1]!.configId).toBe('configId2');
}); });
it('returns an array if an observable with a config array is passed', async () => { it('returns an array if an observable with a config array is passed', async () => {
@ -61,8 +60,8 @@ describe('ConfigLoader', () => {
const result = await lastValueFrom(result$); const result = await lastValueFrom(result$);
expect(Array.isArray(result)).toBeTruthy(); expect(Array.isArray(result)).toBeTruthy();
expect(result[0].configId).toBe('configId1'); expect(result[0]!.configId).toBe('configId1');
expect(result[1].configId).toBe('configId2'); expect(result[1]!.configId).toBe('configId2');
}); });
it('returns an array if only one config is passed', async () => { it('returns an array if only one config is passed', async () => {
@ -74,7 +73,7 @@ describe('ConfigLoader', () => {
const result = await lastValueFrom(result$); const result = await lastValueFrom(result$);
expect(Array.isArray(result)).toBeTruthy(); expect(Array.isArray(result)).toBeTruthy();
expect(result[0].configId).toBe('configId1'); expect(result[0]!.configId).toBe('configId1');
}); });
}); });
}); });

View File

@ -1,7 +1,7 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { OpenIdConfiguration } from '../openid-configuration'; import type { OpenIdConfiguration } from '../openid-configuration';
import { Level, RuleValidationResult } from './rule'; import type { Level, RuleValidationResult } from './rule';
import { allMultipleConfigRules, allRules } from './rules'; import { allMultipleConfigRules, allRules } from './rules';
@Injectable() @Injectable()
@ -35,14 +35,14 @@ export class ConfigValidationService {
let overallErrorCount = 0; let overallErrorCount = 0;
passedConfigs.forEach((passedConfig) => { for (const passedConfig of passedConfigs) {
const errorCount = this.processValidationResultsAndGetErrorCount( const errorCount = this.processValidationResultsAndGetErrorCount(
allValidationResults, allValidationResults,
passedConfig passedConfig
); );
overallErrorCount += errorCount; overallErrorCount += errorCount;
}); }
return overallErrorCount === 0; return overallErrorCount === 0;
} }
@ -75,12 +75,12 @@ export class ConfigValidationService {
const allErrorMessages = this.getAllMessagesOfType('error', allMessages); const allErrorMessages = this.getAllMessagesOfType('error', allMessages);
const allWarnings = this.getAllMessagesOfType('warning', allMessages); const allWarnings = this.getAllMessagesOfType('warning', allMessages);
allErrorMessages.forEach((message) => for (const message of allErrorMessages) {
this.loggerService.logError(config, message) this.loggerService.logError(config, message);
); }
allWarnings.forEach((message) => for (const message of allWarnings) {
this.loggerService.logWarning(config, message) this.loggerService.logWarning(config, message);
); }
return allErrorMessages.length; return allErrorMessages.length;
} }

View File

@ -1,4 +1,4 @@
import { OpenIdConfiguration } from '../openid-configuration'; import type { OpenIdConfiguration } from '../openid-configuration';
export interface Rule { export interface Rule {
validate(passedConfig: OpenIdConfiguration): RuleValidationResult; validate(passedConfig: OpenIdConfiguration): RuleValidationResult;

View File

@ -1,5 +1,5 @@
import { OpenIdConfiguration } from '../../openid-configuration'; import type { OpenIdConfiguration } from '../../openid-configuration';
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
export const ensureAuthority = ( export const ensureAuthority = (
passedConfig: OpenIdConfiguration passedConfig: OpenIdConfiguration

View File

@ -1,5 +1,5 @@
import { OpenIdConfiguration } from '../../openid-configuration'; import type { OpenIdConfiguration } from '../../openid-configuration';
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
export const ensureClientId = ( export const ensureClientId = (
passedConfig: OpenIdConfiguration passedConfig: OpenIdConfiguration

View File

@ -1,5 +1,5 @@
import { OpenIdConfiguration } from '../../openid-configuration'; import type { OpenIdConfiguration } from '../../openid-configuration';
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
const createIdentifierToCheck = (passedConfig: OpenIdConfiguration): string => { const createIdentifierToCheck = (passedConfig: OpenIdConfiguration): string => {
if (!passedConfig) { if (!passedConfig) {

View File

@ -1,5 +1,5 @@
import { OpenIdConfiguration } from '../../openid-configuration'; import type { OpenIdConfiguration } from '../../openid-configuration';
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
export const ensureRedirectRule = ( export const ensureRedirectRule = (
passedConfig: OpenIdConfiguration passedConfig: OpenIdConfiguration

View File

@ -1,5 +1,5 @@
import { OpenIdConfiguration } from '../../openid-configuration'; import type { OpenIdConfiguration } from '../../openid-configuration';
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
export const ensureSilentRenewUrlWhenNoRefreshTokenUsed = ( export const ensureSilentRenewUrlWhenNoRefreshTokenUsed = (
passedConfig: OpenIdConfiguration passedConfig: OpenIdConfiguration

View File

@ -1,5 +1,5 @@
import { OpenIdConfiguration } from '../../openid-configuration'; import type { OpenIdConfiguration } from '../../openid-configuration';
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
export const useOfflineScopeWithSilentRenew = ( export const useOfflineScopeWithSilentRenew = (
passedConfig: OpenIdConfiguration passedConfig: OpenIdConfiguration

View File

@ -1,5 +1,4 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { CryptoService } from '../utils/crypto/crypto.service'; import { CryptoService } from '../utils/crypto/crypto.service';
import { import {
JwkExtractor, JwkExtractor,
@ -94,9 +93,6 @@ describe('JwkExtractor', () => {
imports: [], imports: [],
providers: [JwkExtractor, CryptoService], providers: [JwkExtractor, CryptoService],
}); });
});
beforeEach(() => {
service = TestBed.inject(JwkExtractor); service = TestBed.inject(JwkExtractor);
}); });

View File

@ -7,20 +7,20 @@ export class JwkExtractor {
spec?: { kid?: string; use?: string; kty?: string }, spec?: { kid?: string; use?: string; kty?: string },
throwOnEmpty = true throwOnEmpty = true
): JsonWebKey[] { ): JsonWebKey[] {
if (0 === keys.length) { if (keys.length === 0) {
throw JwkExtractorInvalidArgumentError; throw JwkExtractorInvalidArgumentError;
} }
const foundKeys = keys const foundKeys = keys
.filter((k) => (spec?.kid ? (k as any)['kid'] === spec.kid : true)) .filter((k) => (spec?.kid ? (k as any).kid === spec.kid : true))
.filter((k) => (spec?.use ? k['use'] === spec.use : true)) .filter((k) => (spec?.use ? k.use === spec.use : true))
.filter((k) => (spec?.kty ? k['kty'] === spec.kty : true)); .filter((k) => (spec?.kty ? k.kty === spec.kty : true));
if (foundKeys.length === 0 && throwOnEmpty) { if (foundKeys.length === 0 && throwOnEmpty) {
throw JwkExtractorNoMatchingKeysError; throw JwkExtractorNoMatchingKeysError;
} }
if (foundKeys.length > 1 && (null === spec || undefined === spec)) { if (foundKeys.length > 1 && (spec === null || undefined === spec)) {
throw JwkExtractorSeveralMatchingKeysError; throw JwkExtractorSeveralMatchingKeysError;
} }
@ -29,7 +29,7 @@ export class JwkExtractor {
} }
function buildErrorName(name: string): string { function buildErrorName(name: string): string {
return JwkExtractor.name + ': ' + name; return `${JwkExtractor.name}: ${name}`;
} }
export const JwkExtractorInvalidArgumentError = { export const JwkExtractorInvalidArgumentError = {

View File

@ -1,5 +1,5 @@
import { JwtKeys } from '../validation/jwtkeys'; import type { JwtKeys } from '../validation/jwtkeys';
import { StateValidationResult } from '../validation/state-validation-result'; import type { StateValidationResult } from '../validation/state-validation-result';
export interface CallbackContext { export interface CallbackContext {
code: string; code: string;

View File

@ -1,6 +1,6 @@
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { HttpErrorResponse, HttpHeaders } from '@ngify/http'; import { HttpErrorResponse, HttpHeaders } from '@ngify/http';
import { of, throwError } from 'rxjs'; import { lastValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
@ -32,9 +32,6 @@ describe('CodeFlowCallbackHandlerService', () => {
mockProvider(DataService), mockProvider(DataService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(CodeFlowCallbackHandlerService); service = TestBed.inject(CodeFlowCallbackHandlerService);
dataService = TestBed.inject(DataService); dataService = TestBed.inject(DataService);
urlService = TestBed.inject(UrlService); urlService = TestBed.inject(UrlService);
@ -58,13 +55,13 @@ describe('CodeFlowCallbackHandlerService', () => {
() => '' () => ''
); );
service try {
.codeFlowCallback('test-url', { configId: 'configId1' }) await lastValueFrom(
.subscribe({ service.codeFlowCallback('test-url', { configId: 'configId1' })
error: (err) => { );
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('throws error if no code is given', async () => { it('throws error if no code is given', async () => {
@ -72,15 +69,19 @@ describe('CodeFlowCallbackHandlerService', () => {
.spyOn(urlService, 'getUrlParameter') .spyOn(urlService, 'getUrlParameter')
.mockReturnValue('params'); .mockReturnValue('params');
getUrlParameterSpy.withArgs('test-url', 'code').mockReturnValue(''); mockImplementationWhenArgsEqual(
getUrlParameterSpy,
['test-url', 'code'],
() => ''
);
service try {
.codeFlowCallback('test-url', { configId: 'configId1' }) await lastValueFrom(
.subscribe({ service.codeFlowCallback('test-url', { configId: 'configId1' })
error: (err) => { );
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('returns callbackContext if all params are good', async () => { it('returns callbackContext if all params are good', async () => {
@ -98,9 +99,10 @@ describe('CodeFlowCallbackHandlerService', () => {
existingIdToken: null, existingIdToken: null,
} as CallbackContext; } as CallbackContext;
const callbackContext = await lastValueFrom(service const callbackContext = await lastValueFrom(
.codeFlowCallback('test-url', { configId: 'configId1' })); service.codeFlowCallback('test-url', { configId: 'configId1' })
expect(callbackContext).toEqual(expectedCallbackContext); );
expect(callbackContext).toEqual(expectedCallbackContext);
}); });
}); });
@ -119,13 +121,15 @@ expect(callbackContext).toEqual(expectedCallbackContext);
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).mockReturnValue(false); ).mockReturnValue(false);
service try {
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) await lastValueFrom(
.subscribe({ service.codeFlowCodeRequest({} as CallbackContext, {
error: (err) => { configId: 'configId1',
})
);
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('throws error if authWellknownEndpoints is null is given', async () => { it('throws error if authWellknownEndpoints is null is given', async () => {
@ -139,13 +143,15 @@ expect(callbackContext).toEqual(expectedCallbackContext);
() => null () => null
); );
service try {
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) await lastValueFrom(
.subscribe({ service.codeFlowCodeRequest({} as CallbackContext, {
error: (err) => { configId: 'configId1',
})
);
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('throws error if tokenendpoint is null is given', async () => { it('throws error if tokenendpoint is null is given', async () => {
@ -159,13 +165,15 @@ expect(callbackContext).toEqual(expectedCallbackContext);
() => ({ tokenEndpoint: null }) () => ({ tokenEndpoint: null })
); );
service try {
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) await lastValueFrom(
.subscribe({ service.codeFlowCodeRequest({} as CallbackContext, {
error: (err) => { configId: 'configId1',
})
);
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('calls dataService if all params are good', async () => { it('calls dataService if all params are good', async () => {
@ -182,9 +190,12 @@ expect(callbackContext).toEqual(expectedCallbackContext);
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).mockReturnValue(true); ).mockReturnValue(true);
await lastValueFrom(service await lastValueFrom(
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })); service.codeFlowCodeRequest({} as CallbackContext, {
expect(postSpy).toHaveBeenCalledExactlyOnceWith( configId: 'configId1',
})
);
expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'tokenEndpoint', 'tokenEndpoint',
undefined, undefined,
{ configId: 'configId1' }, { configId: 'configId1' },
@ -215,12 +226,13 @@ expect(postSpy).toHaveBeenCalledExactlyOnceWith(
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({})); const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
await lastValueFrom(service await lastValueFrom(
.codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config)); service.codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config)
expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith('foo', config, { );
expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith('foo', config, {
foo: 'bar', foo: 'bar',
});; });
expect(postSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledTimes(1);
}); });
it('calls dataService with correct headers if all params are good', async () => { it('calls dataService with correct headers if all params are good', async () => {
@ -241,11 +253,12 @@ expect(postSpy).toHaveBeenCalledTimes(1);
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).mockReturnValue(true); ).mockReturnValue(true);
await lastValueFrom(service await lastValueFrom(
.codeFlowCodeRequest({} as CallbackContext, config)); service.codeFlowCodeRequest({} as CallbackContext, config)
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; );
expect(httpHeaders.has('Content-Type')).toBeTruthy();; const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
@ -266,11 +279,13 @@ expect(httpHeaders.get('Content-Type')).toBe(
() => ({ tokenEndpoint: 'tokenEndpoint' }) () => ({ tokenEndpoint: 'tokenEndpoint' })
); );
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ try {
error: (err) => { await lastValueFrom(
service.codeFlowCodeRequest({} as CallbackContext, config)
);
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('retries request in case of no connection http error and succeeds', async () => { it('retries request in case of no connection http error and succeeds', async () => {
@ -297,16 +312,15 @@ expect(httpHeaders.get('Content-Type')).toBe(
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).mockReturnValue(true); ).mockReturnValue(true);
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ try {
next: (res) => { const res = await lastValueFrom(
service.codeFlowCodeRequest({} as CallbackContext, config)
);
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(postSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledTimes(1);
}, } catch (err: any) {
error: (err) => {
// fails if there should be a result
expect(err).toBeFalsy(); expect(err).toBeFalsy();
}, }
});
}); });
it('retries request in case of no connection http error and fails because of http error afterwards', async () => { it('retries request in case of no connection http error and fails because of http error afterwards', async () => {
@ -333,16 +347,15 @@ expect(httpHeaders.get('Content-Type')).toBe(
'validateStateFromHashCallback' 'validateStateFromHashCallback'
).mockReturnValue(true); ).mockReturnValue(true);
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ try {
next: (res) => { const res = await lastValueFrom(
// fails if there should be a result service.codeFlowCodeRequest({} as CallbackContext, config)
);
expect(res).toBeFalsy(); expect(res).toBeFalsy();
}, } catch (err: any) {
error: (err) => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(postSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledTimes(1);
}, }
});
}); });
}); });
}); });

View File

@ -1,14 +1,14 @@
import { HttpHeaders } from '@ngify/http'; import { HttpHeaders } from '@ngify/http';
import { inject, Injectable } from 'injection-js'; import { inject, Injectable } from 'injection-js';
import { Observable, of, throwError, timer } from 'rxjs'; import { type Observable, of, throwError, timer } from 'rxjs';
import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators'; import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { UrlService } from '../../utils/url/url.service'; import { UrlService } from '../../utils/url/url.service';
import { TokenValidationService } from '../../validation/token-validation.service'; import { TokenValidationService } from '../../validation/token-validation.service';
import { AuthResult, CallbackContext } from '../callback-context'; import type { AuthResult, CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
import { isNetworkError } from './error-helper'; import { isNetworkError } from './error-helper';

View File

@ -1,5 +1,5 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { of, throwError } from 'rxjs'; import { lastValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
@ -83,17 +83,18 @@ describe('HistoryJwtKeysCallbackHandlerService', () => {
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys) of({ keys: [] } as JwtKeys)
); );
await lastValueFrom(service await lastValueFrom(
.callbackHistoryAndResetJwtKeys( service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
)); )
expect(storagePersistenceServiceSpy).toBeCalledWith([ );
expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['jwtKeys', { keys: [] }, allConfigs[0]], ['jwtKeys', { keys: [] }, allConfigs[0]],
]);; ]);
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
}); });
it('writes refresh_token into the storage without reuse (refresh token rotation)', async () => { it('writes refresh_token into the storage without reuse (refresh token rotation)', async () => {
@ -120,17 +121,18 @@ expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
of({ keys: [] } as JwtKeys) of({ keys: [] } as JwtKeys)
); );
await lastValueFrom(service await lastValueFrom(
.callbackHistoryAndResetJwtKeys( service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
)); )
expect(storagePersistenceServiceSpy).toBeCalledWith([ );
expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['jwtKeys', { keys: [] }, allConfigs[0]], ['jwtKeys', { keys: [] }, allConfigs[0]],
]);; ]);
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
}); });
it('writes refresh_token into the storage with reuse (without refresh token rotation)', async () => { it('writes refresh_token into the storage with reuse (without refresh token rotation)', async () => {
@ -157,18 +159,19 @@ expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys) of({ keys: [] } as JwtKeys)
); );
await lastValueFrom(service await lastValueFrom(
.callbackHistoryAndResetJwtKeys( service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
)); )
expect(storagePersistenceServiceSpy).toBeCalledWith([ );
expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['reusable_refresh_token', 'dummy_refresh_token', allConfigs[0]], ['reusable_refresh_token', 'dummy_refresh_token', allConfigs[0]],
['jwtKeys', { keys: [] }, allConfigs[0]], ['jwtKeys', { keys: [] }, allConfigs[0]],
]);; ]);
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3); expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3);
}); });
it('resetBrowserHistory if historyCleanup is turned on and is not in a renewProcess', async () => { it('resetBrowserHistory if historyCleanup is turned on and is not in a renewProcess', async () => {
@ -191,13 +194,14 @@ expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3);
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [] } as JwtKeys) of({ keys: [] } as JwtKeys)
); );
await lastValueFrom(service await lastValueFrom(
.callbackHistoryAndResetJwtKeys( service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
)); )
expect(windowSpy).toHaveBeenCalledTimes(1); );
expect(windowSpy).toHaveBeenCalledTimes(1);
}); });
it('returns callbackContext with jwtkeys filled if everything works fine', async () => { it('returns callbackContext with jwtkeys filled if everything works fine', async () => {
@ -219,13 +223,14 @@ expect(windowSpy).toHaveBeenCalledTimes(1);
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({ keys: [{ kty: 'henlo' } as JwtKey] } as JwtKeys) of({ keys: [{ kty: 'henlo' } as JwtKey] } as JwtKeys)
); );
const result = await lastValueFrom(service const result = await lastValueFrom(
.callbackHistoryAndResetJwtKeys( service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
)); )
expect(result).toEqual({ );
expect(result).toEqual({
isRenewProcess: false, isRenewProcess: false,
authResult: DUMMY_AUTH_RESULT, authResult: DUMMY_AUTH_RESULT,
jwtKeys: { keys: [{ kty: 'henlo' }] }, jwtKeys: { keys: [{ kty: 'henlo' }] },
@ -251,19 +256,19 @@ expect(result).toEqual({
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
of({} as JwtKeys) of({} as JwtKeys)
); );
service try {
.callbackHistoryAndResetJwtKeys( await lastValueFrom(
service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
error: (err) => { } catch (err: any) {
expect(err.message).toEqual( expect(err.message).toEqual(
'Failed to retrieve signing key with error: Error: Failed to retrieve signing key' 'Failed to retrieve signing key with error: Error: Failed to retrieve signing key'
); );
}, }
});
}); });
it('returns error if no jwtKeys have been in the call --> keys throw an error', async () => { it('returns error if no jwtKeys have been in the call --> keys throw an error', async () => {
@ -284,19 +289,19 @@ expect(result).toEqual({
vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue(
throwError(() => new Error('error')) throwError(() => new Error('error'))
); );
service try {
.callbackHistoryAndResetJwtKeys( await lastValueFrom(
service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
error: (err) => { } catch (err: any) {
expect(err.message).toEqual( expect(err.message).toEqual(
'Failed to retrieve signing key with error: Error: Error: error' 'Failed to retrieve signing key with error: Error: Error: error'
); );
}, }
});
}); });
it('returns error if callbackContext.authresult has an error property filled', async () => { it('returns error if callbackContext.authresult has an error property filled', async () => {
@ -310,19 +315,19 @@ expect(result).toEqual({
}, },
]; ];
service try {
.callbackHistoryAndResetJwtKeys( await lastValueFrom(
service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
error: (err) => { } catch (err: any) {
expect(err.message).toEqual( expect(err.message).toEqual(
'AuthCallback AuthResult came with error: someError' 'AuthCallback AuthResult came with error: someError'
); );
}, }
});
}); });
it('calls resetAuthorizationData, resets nonce and authStateService in case of an error', async () => { it('calls resetAuthorizationData, resets nonce and authStateService in case of an error', async () => {
@ -347,25 +352,23 @@ expect(result).toEqual({
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
service try {
.callbackHistoryAndResetJwtKeys( await lastValueFrom(
service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
error: () => { } catch {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect(setNonceSpy).toHaveBeenCalledTimes(1); expect(setNonceSpy).toHaveBeenCalledTimes(1);
expect( expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
validationResult: ValidationResult.SecureTokenServerError, validationResult: ValidationResult.SecureTokenServerError,
isRenewProcess: false, isRenewProcess: false,
}); });
}, }
});
}); });
it('calls authStateService.updateAndPublishAuthState with login required if the error is `login_required`', async () => { it('calls authStateService.updateAndPublishAuthState with login required if the error is `login_required`', async () => {
@ -390,25 +393,23 @@ expect(result).toEqual({
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
service try {
.callbackHistoryAndResetJwtKeys( await lastValueFrom(
service.callbackHistoryAndResetJwtKeys(
callbackContext, callbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
error: () => { } catch {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect(setNonceSpy).toHaveBeenCalledTimes(1); expect(setNonceSpy).toHaveBeenCalledTimes(1);
expect( expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
validationResult: ValidationResult.LoginRequired, validationResult: ValidationResult.LoginRequired,
isRenewProcess: false, isRenewProcess: false,
}); });
}, }
});
}); });
it('should store jwtKeys', async () => { it('should store jwtKeys', async () => {
@ -434,26 +435,23 @@ expect(result).toEqual({
of(DUMMY_JWT_KEYS) of(DUMMY_JWT_KEYS)
); );
service try {
.callbackHistoryAndResetJwtKeys( const callbackContext: CallbackContext = await lastValueFrom(
service.callbackHistoryAndResetJwtKeys(
initialCallbackContext, initialCallbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
next: (callbackContext: CallbackContext) => {
expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2);
expect(storagePersistenceServiceSpy).toBeCalledWith([ expect(storagePersistenceServiceSpy).toBeCalledWith([
['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]],
['jwtKeys', DUMMY_JWT_KEYS, allConfigs[0]], ['jwtKeys', DUMMY_JWT_KEYS, allConfigs[0]],
]); ]);
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
}, } catch (err: any) {
error: (err) => {
expect(err).toBeFalsy(); expect(err).toBeFalsy();
}, }
});
}); });
it('should not store jwtKeys on error', async () => { it('should not store jwtKeys on error', async () => {
@ -480,29 +478,23 @@ expect(result).toEqual({
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
service try {
.callbackHistoryAndResetJwtKeys( const callbackContext: CallbackContext = await lastValueFrom(
service.callbackHistoryAndResetJwtKeys(
initialCallbackContext, initialCallbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
next: (callbackContext: CallbackContext) => {
expect(callbackContext).toBeFalsy(); expect(callbackContext).toBeFalsy();
}, } catch (err: any) {
error: (err) => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith(
// storagePersistenceService.write() should not have been called with jwtKeys
expect(
storagePersistenceServiceSpy
).toHaveBeenCalledExactlyOnceWith(
'authnResult', 'authnResult',
authResult, authResult,
allConfigs[0] allConfigs[0]
); );
}, }
});
}); });
it('should fallback to stored jwtKeys on error', async () => { it('should fallback to stored jwtKeys on error', async () => {
@ -530,23 +522,22 @@ expect(result).toEqual({
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
service try {
.callbackHistoryAndResetJwtKeys( const callbackContext: CallbackContext = await lastValueFrom(
service.callbackHistoryAndResetJwtKeys(
initialCallbackContext, initialCallbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
next: (callbackContext: CallbackContext) => { expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith(
expect( 'jwtKeys',
storagePersistenceServiceSpy allConfigs[0]
).toHaveBeenCalledExactlyOnceWith('jwtKeys', allConfigs[0]); );
expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS);
}, } catch (err: any) {
error: (err) => {
expect(err).toBeFalsy(); expect(err).toBeFalsy();
}, }
});
}); });
it('should throw error if no jwtKeys are stored', async () => { it('should throw error if no jwtKeys are stored', async () => {
@ -568,20 +559,18 @@ expect(result).toEqual({
throwError(() => new Error('Error')) throwError(() => new Error('Error'))
); );
service try {
.callbackHistoryAndResetJwtKeys( const callbackContext: CallbackContext = await lastValueFrom(
service.callbackHistoryAndResetJwtKeys(
initialCallbackContext, initialCallbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
next: (callbackContext: CallbackContext) => {
expect(callbackContext).toBeFalsy(); expect(callbackContext).toBeFalsy();
}, } catch (err: any) {
error: (err) => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
}); });

View File

@ -98,10 +98,10 @@ export class HistoryJwtKeysCallbackHandlerService {
// fallback: try to load jwtKeys from storage // fallback: try to load jwtKeys from storage
const storedJwtKeys = this.readSigningKeys(config); const storedJwtKeys = this.readSigningKeys(config);
if (!!storedJwtKeys) { if (storedJwtKeys) {
this.loggerService.logWarning( this.loggerService.logWarning(
config, config,
`Failed to retrieve signing keys, fallback to stored keys` 'Failed to retrieve signing keys, fallback to stored keys'
); );
return of(storedJwtKeys); return of(storedJwtKeys);
@ -116,7 +116,7 @@ export class HistoryJwtKeysCallbackHandlerService {
return of(callbackContext); return of(callbackContext);
} }
const errorMessage = `Failed to retrieve signing key`; const errorMessage = 'Failed to retrieve signing key';
this.loggerService.logWarning(config, errorMessage); this.loggerService.logWarning(config, errorMessage);

View File

@ -1,4 +1,5 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { lastValueFrom } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { DOCUMENT } from '../../dom'; import { DOCUMENT } from '../../dom';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
@ -57,9 +58,10 @@ describe('ImplicitFlowCallbackHandlerService', () => {
}, },
]; ];
await lastValueFrom(service await lastValueFrom(
.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')); service.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')
expect(resetAuthorizationDataSpy).toHaveBeenCalled(); );
expect(resetAuthorizationDataSpy).toHaveBeenCalled();
}); });
it('does NOT calls "resetAuthorizationData" if silent renew is running', async () => { it('does NOT calls "resetAuthorizationData" if silent renew is running', async () => {
@ -74,9 +76,10 @@ expect(resetAuthorizationDataSpy).toHaveBeenCalled();
}, },
]; ];
await lastValueFrom(service await lastValueFrom(
.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')); service.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')
expect(resetAuthorizationDataSpy).not.toHaveBeenCalled(); );
expect(resetAuthorizationDataSpy).not.toHaveBeenCalled();
}); });
it('returns callbackContext if all params are good', async () => { it('returns callbackContext if all params are good', async () => {
@ -99,9 +102,10 @@ expect(resetAuthorizationDataSpy).not.toHaveBeenCalled();
}, },
]; ];
const callbackContext = await lastValueFrom(service const callbackContext = await lastValueFrom(
.implicitFlowCallback(allConfigs[0]!, allConfigs, 'anyHash')); service.implicitFlowCallback(allConfigs[0]!, allConfigs, 'anyHash')
expect(callbackContext).toEqual(expectedCallbackContext); );
expect(callbackContext).toEqual(expectedCallbackContext);
}); });
it('uses window location hash if no hash is passed', async () => { it('uses window location hash if no hash is passed', async () => {
@ -124,9 +128,10 @@ expect(callbackContext).toEqual(expectedCallbackContext);
}, },
]; ];
const callbackContext = await lastValueFrom(service const callbackContext = await lastValueFrom(
.implicitFlowCallback(allConfigs[0]!, allConfigs)); service.implicitFlowCallback(allConfigs[0]!, allConfigs)
expect(callbackContext).toEqual(expectedCallbackContext); );
expect(callbackContext).toEqual(expectedCallbackContext);
}); });
}); });
}); });

View File

@ -1,4 +1,5 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { lastValueFrom } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
@ -21,9 +22,6 @@ describe('RefreshSessionCallbackHandlerService', () => {
mockProvider(FlowsDataService), mockProvider(FlowsDataService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(RefreshSessionCallbackHandlerService); service = TestBed.inject(RefreshSessionCallbackHandlerService);
flowsDataService = TestBed.inject(FlowsDataService); flowsDataService = TestBed.inject(FlowsDataService);
authStateService = TestBed.inject(AuthStateService); authStateService = TestBed.inject(AuthStateService);
@ -56,9 +54,10 @@ describe('RefreshSessionCallbackHandlerService', () => {
existingIdToken: 'henlo-legger', existingIdToken: 'henlo-legger',
} as CallbackContext; } as CallbackContext;
const callbackContext = await lastValueFrom(service const callbackContext = await lastValueFrom(
.refreshSessionWithRefreshTokens({ configId: 'configId1' })); service.refreshSessionWithRefreshTokens({ configId: 'configId1' })
expect(callbackContext).toEqual(expectedCallbackContext); );
expect(callbackContext).toEqual(expectedCallbackContext);
}); });
it('throws error if no refresh token is given', async () => { it('throws error if no refresh token is given', async () => {
@ -69,13 +68,13 @@ expect(callbackContext).toEqual(expectedCallbackContext);
vi.spyOn(authStateService, 'getRefreshToken').mockReturnValue(''); vi.spyOn(authStateService, 'getRefreshToken').mockReturnValue('');
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('henlo-legger'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('henlo-legger');
service try {
.refreshSessionWithRefreshTokens({ configId: 'configId1' }) await lastValueFrom(
.subscribe({ service.refreshSessionWithRefreshTokens({ configId: 'configId1' })
error: (err) => { );
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
}); });
}); });

View File

@ -1,10 +1,10 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { Observable, of, throwError } from 'rxjs'; import { type Observable, of, throwError } from 'rxjs';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { TokenValidationService } from '../../validation/token-validation.service'; import { TokenValidationService } from '../../validation/token-validation.service';
import { CallbackContext } from '../callback-context'; import type { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service'; import { FlowsDataService } from '../flows-data.service';
@Injectable() @Injectable()
@ -24,7 +24,7 @@ export class RefreshSessionCallbackHandlerService {
this.loggerService.logDebug( this.loggerService.logDebug(
config, config,
'RefreshSession created. Adding myautostate: ' + stateData `RefreshSession created. Adding myautostate: ${stateData}`
); );
const refreshToken = this.authStateService.getRefreshToken(config); const refreshToken = this.authStateService.getRefreshToken(config);
const idToken = this.authStateService.getIdToken(config); const idToken = this.authStateService.getIdToken(config);
@ -53,12 +53,11 @@ export class RefreshSessionCallbackHandlerService {
); );
return of(callbackContext); return of(callbackContext);
} else { }
const errorMessage = 'no refresh token found, please login'; const errorMessage = 'no refresh token found, please login';
this.loggerService.logError(config, errorMessage); this.loggerService.logError(config, errorMessage);
return throwError(() => new Error(errorMessage)); return throwError(() => new Error(errorMessage));
} }
}
} }

View File

@ -1,6 +1,6 @@
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { HttpErrorResponse, HttpHeaders } from '@ngify/http'; import { HttpErrorResponse, HttpHeaders } from '@ngify/http';
import { of, throwError } from 'rxjs'; import { lastValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
@ -26,9 +26,6 @@ describe('RefreshTokenCallbackHandlerService', () => {
mockProvider(StoragePersistenceService), mockProvider(StoragePersistenceService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(RefreshTokenCallbackHandlerService); service = TestBed.inject(RefreshTokenCallbackHandlerService);
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
dataService = TestBed.inject(DataService); dataService = TestBed.inject(DataService);
@ -48,13 +45,13 @@ describe('RefreshTokenCallbackHandlerService', () => {
}); });
it('throws error if no tokenEndpoint is given', async () => { it('throws error if no tokenEndpoint is given', async () => {
(service as any) try {
.refreshTokensRequestTokens({} as CallbackContext) await lastValueFrom(
.subscribe({ (service as any).refreshTokensRequestTokens({} as CallbackContext)
error: (err: unknown) => { );
} catch (err: unknown) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('calls data service if all params are good', async () => { it('calls data service if all params are good', async () => {
@ -66,19 +63,20 @@ describe('RefreshTokenCallbackHandlerService', () => {
() => ({ tokenEndpoint: 'tokenEndpoint' }) () => ({ tokenEndpoint: 'tokenEndpoint' })
); );
await lastValueFrom(service await lastValueFrom(
.refreshTokensRequestTokens({} as CallbackContext, { service.refreshTokensRequestTokens({} as CallbackContext, {
configId: 'configId1', configId: 'configId1',
})); })
expect(postSpy).toHaveBeenCalledExactlyOnceWith( );
expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'tokenEndpoint', 'tokenEndpoint',
undefined, undefined,
{ configId: 'configId1' }, { configId: 'configId1' },
expect.any(HttpHeaders) expect.any(HttpHeaders)
);; );
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTruthy();; expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
@ -92,13 +90,14 @@ expect(httpHeaders.get('Content-Type')).toBe(
() => ({ tokenEndpoint: 'tokenEndpoint' }) () => ({ tokenEndpoint: 'tokenEndpoint' })
); );
await lastValueFrom(service await lastValueFrom(
.refreshTokensRequestTokens({} as CallbackContext, { service.refreshTokensRequestTokens({} as CallbackContext, {
configId: 'configId1', configId: 'configId1',
})); })
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; );
expect(httpHeaders.has('Content-Type')).toBeTruthy();; const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
@ -115,13 +114,13 @@ expect(httpHeaders.get('Content-Type')).toBe(
() => ({ tokenEndpoint: 'tokenEndpoint' }) () => ({ tokenEndpoint: 'tokenEndpoint' })
); );
service try {
.refreshTokensRequestTokens({} as CallbackContext, config) await lastValueFrom(
.subscribe({ service.refreshTokensRequestTokens({} as CallbackContext, config)
error: (err) => { );
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('retries request in case of no connection http error and succeeds', async () => { it('retries request in case of no connection http error and succeeds', async () => {
@ -139,18 +138,15 @@ expect(httpHeaders.get('Content-Type')).toBe(
() => ({ tokenEndpoint: 'tokenEndpoint' }) () => ({ tokenEndpoint: 'tokenEndpoint' })
); );
service try {
.refreshTokensRequestTokens({} as CallbackContext, config) const res = await lastValueFrom(
.subscribe({ service.refreshTokensRequestTokens({} as CallbackContext, config)
next: (res) => { );
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(postSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledTimes(1);
}, } catch (err: any) {
error: (err) => {
// fails if there should be a result
expect(err).toBeFalsy(); expect(err).toBeFalsy();
}, }
});
}); });
it('retries request in case of no connection http error and fails because of http error afterwards', async () => { it('retries request in case of no connection http error and fails because of http error afterwards', async () => {
@ -168,18 +164,15 @@ expect(httpHeaders.get('Content-Type')).toBe(
() => ({ tokenEndpoint: 'tokenEndpoint' }) () => ({ tokenEndpoint: 'tokenEndpoint' })
); );
service try {
.refreshTokensRequestTokens({} as CallbackContext, config) const res = await lastValueFrom(
.subscribe({ service.refreshTokensRequestTokens({} as CallbackContext, config)
next: (res) => { );
// fails if there should be a result
expect(res).toBeFalsy(); expect(res).toBeFalsy();
}, } catch (err: any) {
error: (err) => {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(postSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledTimes(1);
}, }
});
}); });
}); });
}); });

View File

@ -1,13 +1,13 @@
import { HttpHeaders } from '@ngify/http'; import { HttpHeaders } from '@ngify/http';
import { inject, Injectable } from 'injection-js'; import { inject, Injectable } from 'injection-js';
import { Observable, of, throwError, timer } from 'rxjs'; import { type Observable, of, throwError, timer } from 'rxjs';
import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators'; import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { UrlService } from '../../utils/url/url.service'; import { UrlService } from '../../utils/url/url.service';
import { AuthResult, CallbackContext } from '../callback-context'; import type { AuthResult, CallbackContext } from '../callback-context';
import { isNetworkError } from './error-helper'; import { isNetworkError } from './error-helper';
@Injectable() @Injectable()

View File

@ -1,5 +1,5 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { lastValueFrom, of } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { DOCUMENT } from '../../dom'; import { DOCUMENT } from '../../dom';
@ -42,9 +42,6 @@ describe('StateValidationCallbackHandlerService', () => {
}, },
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(StateValidationCallbackHandlerService); service = TestBed.inject(StateValidationCallbackHandlerService);
stateValidationService = TestBed.inject(StateValidationService); stateValidationService = TestBed.inject(StateValidationService);
loggerService = TestBed.inject(LoggerService); loggerService = TestBed.inject(LoggerService);
@ -69,13 +66,14 @@ describe('StateValidationCallbackHandlerService', () => {
); );
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
const newCallbackContext = await lastValueFrom(service const newCallbackContext = await lastValueFrom(
.callbackStateValidation( service.callbackStateValidation(
{} as CallbackContext, {} as CallbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
)); )
expect(newCallbackContext).toEqual({ );
expect(newCallbackContext).toEqual({
validationResult: { validationResult: {
idToken: 'idTokenJustForTesting', idToken: 'idTokenJustForTesting',
authResponseIsValid: true, authResponseIsValid: true,
@ -96,20 +94,20 @@ expect(newCallbackContext).toEqual({
const loggerSpy = vi.spyOn(loggerService, 'logWarning'); const loggerSpy = vi.spyOn(loggerService, 'logWarning');
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
service try {
.callbackStateValidation( await lastValueFrom(
service.callbackStateValidation(
{} as CallbackContext, {} as CallbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
error: () => { } catch {
expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
allConfigs[0]!, allConfigs[0]!,
'authorizedCallback, token(s) validation failed, resetting. Hash: &anyFakeHash' 'authorizedCallback, token(s) validation failed, resetting. Hash: &anyFakeHash'
); );
}, }
});
}); });
it('calls resetAuthDataService.resetAuthorizationData and authStateService.updateAndPublishAuthState in case of an error', async () => { it('calls resetAuthDataService.resetAuthorizationData and authStateService.updateAndPublishAuthState in case of an error', async () => {
@ -133,24 +131,22 @@ expect(newCallbackContext).toEqual({
); );
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
service try {
.callbackStateValidation( await lastValueFrom(
service.callbackStateValidation(
{ isRenewProcess: true } as CallbackContext, { isRenewProcess: true } as CallbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
) )
.subscribe({ );
error: () => { } catch {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect( expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
updateAndPublishAuthStateSpy
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
validationResult: ValidationResult.LoginRequired, validationResult: ValidationResult.LoginRequired,
isRenewProcess: true, isRenewProcess: true,
}); });
}, }
});
}); });
}); });
}); });

View File

@ -43,7 +43,7 @@ export class StateValidationCallbackHandlerService {
); );
return callbackContext; return callbackContext;
} else { }
const errorMessage = `authorizedCallback, token(s) validation failed, resetting. Hash: ${this.document.location.hash}`; const errorMessage = `authorizedCallback, token(s) validation failed, resetting. Hash: ${this.document.location.hash}`;
this.loggerService.logWarning(configuration, errorMessage); this.loggerService.logWarning(configuration, errorMessage);
@ -57,7 +57,6 @@ export class StateValidationCallbackHandlerService {
); );
throw new Error(errorMessage); throw new Error(errorMessage);
}
}) })
); );
} }

View File

@ -1,5 +1,5 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { lastValueFrom, of } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { AuthStateService } from '../../auth-state/auth-state.service'; import { AuthStateService } from '../../auth-state/auth-state.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
@ -30,9 +30,6 @@ describe('UserCallbackHandlerService', () => {
mockProvider(ResetAuthDataService), mockProvider(ResetAuthDataService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(UserCallbackHandlerService); service = TestBed.inject(UserCallbackHandlerService);
flowsDataService = TestBed.inject(FlowsDataService); flowsDataService = TestBed.inject(FlowsDataService);
authStateService = TestBed.inject(AuthStateService); authStateService = TestBed.inject(AuthStateService);
@ -73,10 +70,11 @@ describe('UserCallbackHandlerService', () => {
const spy = vi.spyOn(flowsDataService, 'setSessionState'); const spy = vi.spyOn(flowsDataService, 'setSessionState');
const resultCallbackContext = await lastValueFrom(service const resultCallbackContext = await lastValueFrom(
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)); service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
expect(spy).toHaveBeenCalledExactlyOnceWith('mystate', allConfigs[0]);; );
expect(resultCallbackContext).toEqual(callbackContext); 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', async () => { it('does NOT call flowsDataService.setSessionState if autoUserInfo is false, isRenewProcess is true and refreshToken is null', async () => {
@ -105,10 +103,11 @@ expect(resultCallbackContext).toEqual(callbackContext);
]; ];
const spy = vi.spyOn(flowsDataService, 'setSessionState'); const spy = vi.spyOn(flowsDataService, 'setSessionState');
const resultCallbackContext = await lastValueFrom(service const resultCallbackContext = await lastValueFrom(
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)); service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
expect(spy).not.toHaveBeenCalled();; );
expect(resultCallbackContext).toEqual(callbackContext); expect(spy).not.toHaveBeenCalled();
expect(resultCallbackContext).toEqual(callbackContext);
}); });
it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value', async () => { it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value', async () => {
@ -137,10 +136,11 @@ expect(resultCallbackContext).toEqual(callbackContext);
]; ];
const spy = vi.spyOn(flowsDataService, 'setSessionState'); const spy = vi.spyOn(flowsDataService, 'setSessionState');
const resultCallbackContext = await lastValueFrom(service const resultCallbackContext = await lastValueFrom(
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)); service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
expect(spy).not.toHaveBeenCalled();; );
expect(resultCallbackContext).toEqual(callbackContext); 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', async () => { it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value, id_token is false', async () => {
@ -165,10 +165,11 @@ expect(resultCallbackContext).toEqual(callbackContext);
const spy = vi.spyOn(flowsDataService, 'setSessionState'); const spy = vi.spyOn(flowsDataService, 'setSessionState');
const resultCallbackContext = await lastValueFrom(service const resultCallbackContext = await lastValueFrom(
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)); service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
expect(spy).not.toHaveBeenCalled();; );
expect(resultCallbackContext).toEqual(callbackContext); expect(spy).not.toHaveBeenCalled();
expect(resultCallbackContext).toEqual(callbackContext);
}); });
it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is false', async () => { it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is false', async () => {
@ -202,14 +203,15 @@ expect(resultCallbackContext).toEqual(callbackContext);
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
const resultCallbackContext = await lastValueFrom(service const resultCallbackContext = await lastValueFrom(
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)); service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ );
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: true, isAuthenticated: true,
validationResult: ValidationResult.NotSet, validationResult: ValidationResult.NotSet,
isRenewProcess: false, isRenewProcess: false,
});; });
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
it('calls userService.getAndPersistUserDataInStore with correct params if autoUserInfo is true', async () => { it('calls userService.getAndPersistUserDataInStore with correct params if autoUserInfo is true', async () => {
@ -242,18 +244,17 @@ expect(resultCallbackContext).toEqual(callbackContext);
.spyOn(userService, 'getAndPersistUserDataInStore') .spyOn(userService, 'getAndPersistUserDataInStore')
.mockReturnValue(of({ user: 'some_data' })); .mockReturnValue(of({ user: 'some_data' }));
const resultCallbackContext = await lastValueFrom(service const resultCallbackContext = await lastValueFrom(
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)); service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
expect( );
getAndPersistUserDataInStoreSpy expect(getAndPersistUserDataInStoreSpy).toHaveBeenCalledExactlyOnceWith(
).toHaveBeenCalledExactlyOnceWith(
allConfigs[0]!, allConfigs[0]!,
allConfigs, allConfigs,
false, false,
'idtoken', 'idtoken',
'decoded' 'decoded'
);; );
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is true', async () => { it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is true', async () => {
@ -291,14 +292,15 @@ expect(resultCallbackContext).toEqual(callbackContext);
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
const resultCallbackContext = await lastValueFrom(service const resultCallbackContext = await lastValueFrom(
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)); service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ );
expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: true, isAuthenticated: true,
validationResult: ValidationResult.MaxOffsetExpired, validationResult: ValidationResult.MaxOffsetExpired,
isRenewProcess: false, isRenewProcess: false,
});; });
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
it('calls flowsDataService.setSessionState with correct params if user data is present and NOT refresh token', async () => { it('calls flowsDataService.setSessionState with correct params if user data is present and NOT refresh token', async () => {
@ -333,13 +335,14 @@ expect(resultCallbackContext).toEqual(callbackContext);
); );
const setSessionStateSpy = vi.spyOn(flowsDataService, 'setSessionState'); const setSessionStateSpy = vi.spyOn(flowsDataService, 'setSessionState');
const resultCallbackContext = await lastValueFrom(service const resultCallbackContext = await lastValueFrom(
.callbackUser(callbackContext, allConfigs[0]!, allConfigs)); service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
expect(setSessionStateSpy).toHaveBeenCalledExactlyOnceWith( );
expect(setSessionStateSpy).toHaveBeenCalledExactlyOnceWith(
'mystate', 'mystate',
allConfigs[0] allConfigs[0]
);; );
expect(resultCallbackContext).toEqual(callbackContext); expect(resultCallbackContext).toEqual(callbackContext);
}); });
it('calls authStateService.publishUnauthorizedState with correct params if user info which are coming back are null', async () => { it('calls authStateService.publishUnauthorizedState with correct params if user info which are coming back are null', async () => {
@ -377,13 +380,12 @@ expect(resultCallbackContext).toEqual(callbackContext);
'updateAndPublishAuthState' 'updateAndPublishAuthState'
); );
service try {
.callbackUser(callbackContext, allConfigs[0]!, allConfigs) await lastValueFrom(
.subscribe({ service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
error: (err) => { );
expect( } catch (err: any) {
updateAndPublishAuthStateSpy expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({
).toHaveBeenCalledExactlyOnceWith({
isAuthenticated: false, isAuthenticated: false,
validationResult: ValidationResult.MaxOffsetExpired, validationResult: ValidationResult.MaxOffsetExpired,
isRenewProcess: false, isRenewProcess: false,
@ -391,8 +393,7 @@ expect(resultCallbackContext).toEqual(callbackContext);
expect(err.message).toEqual( expect(err.message).toEqual(
'Failed to retrieve user info with error: Error: Called for userData but they were null' 'Failed to retrieve user info with error: Error: Called for userData but they were null'
); );
}, }
});
}); });
it('calls resetAuthDataService.resetAuthorizationData if user info which are coming back are null', async () => { it('calls resetAuthDataService.resetAuthorizationData if user info which are coming back are null', async () => {
@ -430,16 +431,16 @@ expect(resultCallbackContext).toEqual(callbackContext);
'resetAuthorizationData' 'resetAuthorizationData'
); );
service try {
.callbackUser(callbackContext, allConfigs[0]!, allConfigs) await lastValueFrom(
.subscribe({ service.callbackUser(callbackContext, allConfigs[0]!, allConfigs)
error: (err) => { );
} catch (err: any) {
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
expect(err.message).toEqual( expect(err.message).toEqual(
'Failed to retrieve user info with error: Error: Called for userData but they were null' 'Failed to retrieve user info with error: Error: Called for userData but they were null'
); );
}, }
});
}); });
}); });
}); });

View File

@ -35,6 +35,7 @@ export class UserCallbackHandlerService {
if (!autoUserInfo) { if (!autoUserInfo) {
if (!isRenewProcess || renewUserInfoAfterTokenRenew) { if (!isRenewProcess || renewUserInfoAfterTokenRenew) {
// userData is set to the id_token decoded, auto get user data set to false // userData is set to the id_token decoded, auto get user data set to false
// biome-ignore lint/nursery/useCollapsedIf: <explanation>
if (validationResult?.decodedIdToken) { if (validationResult?.decodedIdToken) {
this.userService.setUserDataToStore( this.userService.setUserDataToStore(
validationResult.decodedIdToken, validationResult.decodedIdToken,
@ -66,7 +67,7 @@ export class UserCallbackHandlerService {
) )
.pipe( .pipe(
switchMap((userData) => { switchMap((userData) => {
if (!!userData) { if (userData) {
if (!refreshToken) { if (!refreshToken) {
this.flowsDataService.setSessionState( this.flowsDataService.setSessionState(
authResult?.session_state, authResult?.session_state,
@ -77,7 +78,7 @@ export class UserCallbackHandlerService {
this.publishAuthState(validationResult, isRenewProcess); this.publishAuthState(validationResult, isRenewProcess);
return of(callbackContext); return of(callbackContext);
} else { }
this.resetAuthDataService.resetAuthorizationData( this.resetAuthDataService.resetAuthorizationData(
configuration, configuration,
allConfigs allConfigs
@ -88,7 +89,6 @@ export class UserCallbackHandlerService {
this.loggerService.logWarning(configuration, errorMessage); this.loggerService.logWarning(configuration, errorMessage);
return throwError(() => new Error(errorMessage)); return throwError(() => new Error(errorMessage));
}
}), }),
catchError((err) => { catchError((err) => {
const errorMessage = `Failed to retrieve user info with error: ${err}`; const errorMessage = `Failed to retrieve user info with error: ${err}`;

View File

@ -1,4 +1,4 @@
import { TestBed } from '@/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
@ -21,15 +21,13 @@ describe('Flows Data Service', () => {
mockProvider(StoragePersistenceService), mockProvider(StoragePersistenceService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(FlowsDataService); service = TestBed.inject(FlowsDataService);
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
}); });
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
afterEach(() => { afterEach(() => {
jasmine.clock().uninstall(); vi.useRealTimers();
}); });
it('should create', () => { it('should create', () => {
@ -141,10 +139,11 @@ describe('Flows Data Service', () => {
describe('codeVerifier', () => { describe('codeVerifier', () => {
it('getCodeVerifier returns value from the store', () => { it('getCodeVerifier returns value from the store', () => {
const spy = vi const spy = mockImplementationWhenArgsEqual(
.spyOn(storagePersistenceService, 'read') vi.spyOn(storagePersistenceService, 'read'),
.withArgs('codeVerifier', { configId: 'configId1' }) ['codeVerifier', { configId: 'configId1' }],
.mockReturnValue('Genesis'); () => 'Genesis'
);
const result = service.getCodeVerifier({ configId: 'configId1' }); const result = service.getCodeVerifier({ configId: 'configId1' });
@ -173,11 +172,12 @@ describe('Flows Data Service', () => {
configId: 'configId1', configId: 'configId1',
}; };
jasmine.clock().uninstall(); vi.useRealTimers();
jasmine.clock().install(); vi.useFakeTimers();
const baseTime = new Date(); const baseTime = new Date();
jasmine.clock().mockDate(baseTime); vi.setSystemTime(baseTime);
mockImplementationWhenArgsEqual( mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'), vi.spyOn(storagePersistenceService, 'read'),
@ -212,11 +212,11 @@ describe('Flows Data Service', () => {
describe('setCodeFlowInProgress', () => { describe('setCodeFlowInProgress', () => {
it('set setCodeFlowInProgress to `in progress` when called', () => { it('set setCodeFlowInProgress to `in progress` when called', () => {
jasmine.clock().uninstall(); vi.useRealTimers();
jasmine.clock().install(); vi.useFakeTimers();
const baseTime = new Date(); const baseTime = new Date();
jasmine.clock().mockDate(baseTime); vi.setSystemTime(baseTime);
const spy = vi.spyOn(storagePersistenceService, 'write'); const spy = vi.spyOn(storagePersistenceService, 'write');
@ -253,23 +253,27 @@ describe('Flows Data Service', () => {
configId: 'configId1', configId: 'configId1',
}; };
jasmine.clock().uninstall(); vi.useRealTimers();
jasmine.clock().install(); vi.useFakeTimers();
const baseTime = new Date(); const baseTime = new Date();
jasmine.clock().mockDate(baseTime); vi.setSystemTime(baseTime);
const storageObject = { const storageObject = {
state: 'running', state: 'running',
dateOfLaunchedProcessUtc: baseTime.toISOString(), dateOfLaunchedProcessUtc: baseTime.toISOString(),
}; };
vi.spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('storageSilentRenewRunning', config) vi.spyOn(storagePersistenceService, 'read'),
.mockReturnValue(JSON.stringify(storageObject)); ['storageSilentRenewRunning', config],
() => JSON.stringify(storageObject)
);
const spyWrite = vi.spyOn(storagePersistenceService, 'write'); const spyWrite = vi.spyOn(storagePersistenceService, 'write');
jasmine.clock().tick((config.silentRenewTimeoutInSeconds + 1) * 1000); vi.advanceTimersByTimeAsync(
(config.silentRenewTimeoutInSeconds + 1) * 1000
);
const isSilentRenewRunningResult = service.isSilentRenewRunning(config); const isSilentRenewRunningResult = service.isSilentRenewRunning(config);
@ -287,20 +291,22 @@ describe('Flows Data Service', () => {
configId: 'configId1', configId: 'configId1',
}; };
jasmine.clock().uninstall(); vi.useRealTimers();
jasmine.clock().install(); vi.useFakeTimers();
const baseTime = new Date(); const baseTime = new Date();
jasmine.clock().mockDate(baseTime); vi.setSystemTime(baseTime);
const storageObject = { const storageObject = {
state: 'running', state: 'running',
dateOfLaunchedProcessUtc: baseTime.toISOString(), dateOfLaunchedProcessUtc: baseTime.toISOString(),
}; };
vi.spyOn(storagePersistenceService, 'read') mockImplementationWhenArgsEqual(
.withArgs('storageSilentRenewRunning', config) vi.spyOn(storagePersistenceService, 'read'),
.mockReturnValue(JSON.stringify(storageObject)); ['storageSilentRenewRunning', config],
() => JSON.stringify(storageObject)
);
const spyWrite = vi.spyOn(storagePersistenceService, 'write'); const spyWrite = vi.spyOn(storagePersistenceService, 'write');
const isSilentRenewRunningResult = service.isSilentRenewRunning(config); const isSilentRenewRunningResult = service.isSilentRenewRunning(config);
@ -326,11 +332,11 @@ describe('Flows Data Service', () => {
describe('setSilentRenewRunning', () => { describe('setSilentRenewRunning', () => {
it('set setSilentRenewRunning to `running` with lauched time when called', () => { it('set setSilentRenewRunning to `running` with lauched time when called', () => {
jasmine.clock().uninstall(); vi.useRealTimers();
jasmine.clock().install(); vi.useFakeTimers();
const baseTime = new Date(); const baseTime = new Date();
jasmine.clock().mockDate(baseTime); vi.setSystemTime(baseTime);
const storageObject = { const storageObject = {
state: 'running', state: 'running',

View File

@ -1,5 +1,5 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { lastValueFrom, of } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { mockProvider } from '../testing/mock'; import { mockProvider } from '../testing/mock';
import type { CallbackContext } from './callback-context'; import type { CallbackContext } from './callback-context';
@ -35,9 +35,6 @@ describe('Flows Service', () => {
mockProvider(RefreshTokenCallbackHandlerService), mockProvider(RefreshTokenCallbackHandlerService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(FlowsService); service = TestBed.inject(FlowsService);
codeFlowCallbackHandlerService = TestBed.inject( codeFlowCallbackHandlerService = TestBed.inject(
CodeFlowCallbackHandlerService CodeFlowCallbackHandlerService
@ -90,17 +87,22 @@ describe('Flows Service', () => {
}, },
]; ];
const value = await lastValueFrom(service const value = await lastValueFrom(
.processCodeFlowCallback('some-url1234', allConfigs[0]!, allConfigs)); service.processCodeFlowCallback(
expect(value).toEqual({} as CallbackContext);; 'some-url1234',
expect(codeFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith( allConfigs[0]!,
allConfigs
)
);
expect(value).toEqual({} as CallbackContext);
expect(codeFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith(
'some-url1234', 'some-url1234',
allConfigs[0] allConfigs[0]
);; );
expect(codeFlowCodeRequestSpy).toHaveBeenCalledTimes(1);; expect(codeFlowCodeRequestSpy).toHaveBeenCalledTimes(1);
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalledTimes(1);; expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalledTimes(1);
expect(callbackStateValidationSpy).toHaveBeenCalledTimes(1);; expect(callbackStateValidationSpy).toHaveBeenCalledTimes(1);
expect(callbackUserSpy).toHaveBeenCalledTimes(1); expect(callbackUserSpy).toHaveBeenCalledTimes(1);
}); });
}); });
@ -127,17 +129,18 @@ expect(callbackUserSpy).toHaveBeenCalledTimes(1);
}, },
]; ];
const value = await lastValueFrom(service const value = await lastValueFrom(
.processSilentRenewCodeFlowCallback( service.processSilentRenewCodeFlowCallback(
{} as CallbackContext, {} as CallbackContext,
allConfigs[0]!, allConfigs[0]!,
allConfigs allConfigs
)); )
expect(value).toEqual({} as CallbackContext);; );
expect(codeFlowCodeRequestSpy).toHaveBeenCalled();; expect(value).toEqual({} as CallbackContext);
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();; expect(codeFlowCodeRequestSpy).toHaveBeenCalled();
expect(callbackStateValidationSpy).toHaveBeenCalled();; expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();
expect(callbackUserSpy).toHaveBeenCalled(); expect(callbackStateValidationSpy).toHaveBeenCalled();
expect(callbackUserSpy).toHaveBeenCalled();
}); });
}); });
@ -164,13 +167,18 @@ expect(callbackUserSpy).toHaveBeenCalled();
}, },
]; ];
const value = await lastValueFrom(service const value = await lastValueFrom(
.processImplicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')); service.processImplicitFlowCallback(
expect(value).toEqual({} as CallbackContext);; allConfigs[0]!,
expect(implicitFlowCallbackSpy).toHaveBeenCalled();; allConfigs,
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();; 'any-hash'
expect(callbackStateValidationSpy).toHaveBeenCalled();; )
expect(callbackUserSpy).toHaveBeenCalled(); );
expect(value).toEqual({} as CallbackContext);
expect(implicitFlowCallbackSpy).toHaveBeenCalled();
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();
expect(callbackStateValidationSpy).toHaveBeenCalled();
expect(callbackUserSpy).toHaveBeenCalled();
}); });
}); });
@ -203,14 +211,15 @@ expect(callbackUserSpy).toHaveBeenCalled();
}, },
]; ];
const value = await lastValueFrom(service const value = await lastValueFrom(
.processRefreshToken(allConfigs[0]!, allConfigs)); service.processRefreshToken(allConfigs[0]!, allConfigs)
expect(value).toEqual({} as CallbackContext);; );
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();; expect(value).toEqual({} as CallbackContext);
expect(refreshTokensRequestTokensSpy).toHaveBeenCalled();; expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();; expect(refreshTokensRequestTokensSpy).toHaveBeenCalled();
expect(callbackStateValidationSpy).toHaveBeenCalled();; expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();
expect(callbackUserSpy).toHaveBeenCalled(); expect(callbackStateValidationSpy).toHaveBeenCalled();
expect(callbackUserSpy).toHaveBeenCalled();
}); });
}); });
}); });

View File

@ -1,5 +1,4 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { mockProvider } from '../../testing/mock'; import { mockProvider } from '../../testing/mock';
import { CryptoService } from '../../utils/crypto/crypto.service'; import { CryptoService } from '../../utils/crypto/crypto.service';
@ -12,9 +11,6 @@ describe('RandomService Tests', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [RandomService, mockProvider(LoggerService), CryptoService], providers: [RandomService, mockProvider(LoggerService), CryptoService],
}); });
});
beforeEach(() => {
randomService = TestBed.inject(RandomService); randomService = TestBed.inject(RandomService);
}); });

View File

@ -1,5 +1,5 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { CryptoService } from '../../utils/crypto/crypto.service'; import { CryptoService } from '../../utils/crypto/crypto.service';
@ -37,7 +37,7 @@ export class RandomService {
} }
private toHex(dec: number): string { private toHex(dec: number): string {
return ('0' + dec.toString(16)).substr(-2); return `0${dec.toString(16)}`.substr(-2);
} }
private randomString(length: number): string { private randomString(length: number): string {

View File

@ -23,9 +23,6 @@ describe('ResetAuthDataService', () => {
mockProvider(LoggerService), mockProvider(LoggerService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(ResetAuthDataService); service = TestBed.inject(ResetAuthDataService);
userService = TestBed.inject(UserService); userService = TestBed.inject(UserService);
flowsDataService = TestBed.inject(FlowsDataService); flowsDataService = TestBed.inject(FlowsDataService);

View File

@ -1,6 +1,6 @@
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { HttpResponse } from '@ngify/http'; import { HttpResponse } from '@ngify/http';
import { isObservable, of, throwError } from 'rxjs'; import { EmptyError, isObservable, lastValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { DataService } from '../api/data.service'; import { DataService } from '../api/data.service';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
@ -40,9 +40,6 @@ describe('Signin Key Data Service', () => {
mockProvider(StoragePersistenceService), mockProvider(StoragePersistenceService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(SigninKeyDataService); service = TestBed.inject(SigninKeyDataService);
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
dataService = TestBed.inject(DataService); dataService = TestBed.inject(DataService);
@ -62,11 +59,11 @@ describe('Signin Key Data Service', () => {
); );
const result = service.getSigningKeys({ configId: 'configId1' }); const result = service.getSigningKeys({ configId: 'configId1' });
result.subscribe({ try {
error: (err) => { await lastValueFrom(result);
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('throws error when no jwksUri given', async () => { it('throws error when no jwksUri given', async () => {
@ -77,11 +74,11 @@ describe('Signin Key Data Service', () => {
); );
const result = service.getSigningKeys({ configId: 'configId1' }); const result = service.getSigningKeys({ configId: 'configId1' });
result.subscribe({ try {
error: (err) => { await lastValueFrom(result);
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('calls dataservice if jwksurl is given', async () => { it('calls dataservice if jwksurl is given', async () => {
@ -94,13 +91,15 @@ describe('Signin Key Data Service', () => {
const result = service.getSigningKeys({ configId: 'configId1' }); const result = service.getSigningKeys({ configId: 'configId1' });
result.subscribe({ try {
complete: () => { await lastValueFrom(result);
} catch (err: any) {
if (err instanceof EmptyError) {
expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl', { expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl', {
configId: 'configId1', configId: 'configId1',
}); });
}, }
}); }
}); });
it('should retry once', async () => { it('should retry once', async () => {
@ -116,12 +115,11 @@ describe('Signin Key Data Service', () => {
) )
); );
service.getSigningKeys({ configId: 'configId1' }).subscribe({ const res = await lastValueFrom(
next: (res) => { service.getSigningKeys({ configId: 'configId1' })
);
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual(DUMMY_JWKS); expect(res).toEqual(DUMMY_JWKS);
},
});
}); });
it('should retry twice', async () => { it('should retry twice', async () => {
@ -138,12 +136,11 @@ describe('Signin Key Data Service', () => {
) )
); );
service.getSigningKeys({ configId: 'configId1' }).subscribe({ const res = await lastValueFrom(
next: (res) => { service.getSigningKeys({ configId: 'configId1' })
);
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual(DUMMY_JWKS); expect(res).toEqual(DUMMY_JWKS);
},
});
}); });
it('should fail after three tries', async () => { it('should fail after three tries', async () => {
@ -161,16 +158,16 @@ describe('Signin Key Data Service', () => {
) )
); );
service.getSigningKeys({ configId: 'configId1' }).subscribe({ try {
error: (err) => { await lastValueFrom(service.getSigningKeys({ configId: 'configId1' }));
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
}); });
describe('handleErrorGetSigningKeys', () => { describe('handleErrorGetSigningKeys', () => {
it('keeps observable if error is catched', async () => { it('keeps observable if error is catched', () => {
const result = (service as any).handleErrorGetSigningKeys( const result = (service as any).handleErrorGetSigningKeys(
new HttpResponse() new HttpResponse()
); );
@ -182,52 +179,54 @@ describe('Signin Key Data Service', () => {
it('logs error if error is response', async () => { it('logs error if error is response', async () => {
const logSpy = vi.spyOn(loggerService, 'logError'); const logSpy = vi.spyOn(loggerService, 'logError');
(service as any) try {
.handleErrorGetSigningKeys( await lastValueFrom(
(service as any).handleErrorGetSigningKeys(
new HttpResponse({ status: 400, statusText: 'nono' }), new HttpResponse({ status: 400, statusText: 'nono' }),
{ configId: 'configId1' } { configId: 'configId1' }
) )
.subscribe({ );
error: () => { } catch {
expect(logSpy).toHaveBeenCalledExactlyOnceWith( expect(logSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'400 - nono {}' '400 - nono {}'
); );
}, }
});
}); });
it('logs error if error is not a response', async () => { it('logs error if error is not a response', async () => {
const logSpy = vi.spyOn(loggerService, 'logError'); const logSpy = vi.spyOn(loggerService, 'logError');
(service as any) try {
.handleErrorGetSigningKeys('Just some Error', { configId: 'configId1' }) await lastValueFrom(
.subscribe({ (service as any).handleErrorGetSigningKeys('Just some Error', {
error: () => { configId: 'configId1',
})
);
} catch {
expect(logSpy).toHaveBeenCalledExactlyOnceWith( expect(logSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'Just some Error' 'Just some Error'
); );
}, }
});
}); });
it('logs error if error with message property is not a response', async () => { it('logs error if error with message property is not a response', async () => {
const logSpy = vi.spyOn(loggerService, 'logError'); const logSpy = vi.spyOn(loggerService, 'logError');
(service as any) try {
.handleErrorGetSigningKeys( await lastValueFrom(
(service as any).handleErrorGetSigningKeys(
{ message: 'Just some Error' }, { message: 'Just some Error' },
{ configId: 'configId1' } { configId: 'configId1' }
) )
.subscribe({ );
error: () => { } catch {
expect(logSpy).toHaveBeenCalledExactlyOnceWith( expect(logSpy).toHaveBeenCalledExactlyOnceWith(
{ configId: 'configId1' }, { configId: 'configId1' },
'Just some Error' 'Just some Error'
); );
}, }
});
}); });
}); });
}); });

View File

@ -1,12 +1,12 @@
import { HttpResponse } from '@ngify/http'; import { HttpResponse } from '@ngify/http';
import { inject, Injectable } from 'injection-js'; import { inject, Injectable } from 'injection-js';
import { Observable, throwError } from 'rxjs'; import { type Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators'; import { catchError, retry } from 'rxjs/operators';
import { DataService } from '../api/data.service'; 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 { LoggerService } from '../logging/logger.service';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { JwtKeys } from '../validation/jwtkeys'; import type { JwtKeys } from '../validation/jwtkeys';
@Injectable() @Injectable()
export class SigninKeyDataService { export class SigninKeyDataService {
@ -62,7 +62,7 @@ export class SigninKeyDataService {
} else { } else {
const { message } = errorResponse; const { message } = errorResponse;
errMsg = !!message ? message : `${errorResponse}`; errMsg = message ? message : `${errorResponse}`;
} }
this.loggerService.logError(currentConfiguration, errMsg); this.loggerService.logError(currentConfiguration, errMsg);

View File

@ -1,5 +1,5 @@
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { of } from 'rxjs'; import { lastValueFrom, of } from 'rxjs';
import { skip } from 'rxjs/operators'; import { skip } from 'rxjs/operators';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
@ -35,15 +35,13 @@ describe('CheckSessionService', () => {
), ),
], ],
}); });
});
beforeEach(() => {
checkSessionService = TestBed.inject(CheckSessionService); checkSessionService = TestBed.inject(CheckSessionService);
loggerService = TestBed.inject(LoggerService); loggerService = TestBed.inject(LoggerService);
iFrameService = TestBed.inject(IFrameService); iFrameService = TestBed.inject(IFrameService);
storagePersistenceService = TestBed.inject(StoragePersistenceService); storagePersistenceService = TestBed.inject(StoragePersistenceService);
}); });
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
afterEach(() => { afterEach(() => {
const iFrameIdwhichshouldneverexist = window.document.getElementById( const iFrameIdwhichshouldneverexist = window.document.getElementById(
'idwhichshouldneverexist' 'idwhichshouldneverexist'
@ -68,7 +66,7 @@ describe('CheckSessionService', () => {
}); });
it('getOrCreateIframe calls iFrameService.addIFrameToWindowBody if no Iframe exists', () => { it('getOrCreateIframe calls iFrameService.addIFrameToWindowBody if no Iframe exists', () => {
vi.spyOn(iFrameService, 'addIFrameToWindowBody')(); vi.spyOn(iFrameService, 'addIFrameToWindowBody');
const result = (checkSessionService as any).getOrCreateIframe({ const result = (checkSessionService as any).getOrCreateIframe({
configId: 'configId1', configId: 'configId1',
@ -106,15 +104,18 @@ describe('CheckSessionService', () => {
}); });
it('log warning if authWellKnownEndpoints.check_session_iframe is not existing', () => { it('log warning if authWellKnownEndpoints.check_session_iframe is not existing', () => {
const spyLogWarning = vi.spyOn<any>(loggerService, 'logWarning'); const spyLogWarning = vi.spyOn<any, any>(loggerService, 'logWarning');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
vi.spyOn<any>(loggerService, 'logDebug').mockImplementation( vi.spyOn<any, any>(loggerService, 'logDebug').mockImplementation(
() => undefined () => undefined
); );
vi.spyOn(storagePersistenceService, 'read')
.withArgs('authWellKnownEndPoints', config) mockImplementationWhenArgsEqual(
.mockReturnValue({ checkSessionIframe: undefined }); vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ checkSessionIframe: undefined })
);
(checkSessionService as any).init(config); (checkSessionService as any).init(config);
expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith( expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith(
@ -124,7 +125,7 @@ describe('CheckSessionService', () => {
}); });
it('start() calls pollserversession() with clientId if no scheduledheartbeat is set', () => { it('start() calls pollserversession() with clientId if no scheduledheartbeat is set', () => {
const spy = vi.spyOn<any>(checkSessionService, 'pollServerSession'); const spy = vi.spyOn<any, any>(checkSessionService, 'pollServerSession');
const config = { clientId: 'clientId', configId: 'configId1' }; const config = { clientId: 'clientId', configId: 'configId1' };
checkSessionService.start(config); checkSessionService.start(config);
@ -133,7 +134,7 @@ describe('CheckSessionService', () => {
it('start() does not call pollServerSession() if scheduledHeartBeatRunning is set', () => { it('start() does not call pollServerSession() if scheduledHeartBeatRunning is set', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const spy = vi.spyOn<any>(checkSessionService, 'pollServerSession'); const spy = vi.spyOn<any, any>(checkSessionService, 'pollServerSession');
(checkSessionService as any).scheduledHeartBeatRunning = (): void => (checkSessionService as any).scheduledHeartBeatRunning = (): void =>
undefined; undefined;
@ -154,7 +155,10 @@ describe('CheckSessionService', () => {
it('stopCheckingSession does nothing if scheduledHeartBeatRunning is not set', () => { it('stopCheckingSession does nothing if scheduledHeartBeatRunning is not set', () => {
(checkSessionService as any).scheduledHeartBeatRunning = null; (checkSessionService as any).scheduledHeartBeatRunning = null;
const spy = vi.spyOn<any>(checkSessionService, 'clearScheduledHeartBeat'); const spy = vi.spyOn<any, any>(
checkSessionService,
'clearScheduledHeartBeat'
);
checkSessionService.stop(); checkSessionService.stop();
expect(spy).not.toHaveBeenCalledExactlyOnceWith(); expect(spy).not.toHaveBeenCalledExactlyOnceWith();
@ -187,11 +191,16 @@ describe('CheckSessionService', () => {
describe('pollServerSession', () => { describe('pollServerSession', () => {
beforeEach(() => { beforeEach(() => {
vi.spyOn<any>(checkSessionService, 'init').mockReturnValue(of(undefined)); vi.spyOn<any, any>(checkSessionService, 'init').mockReturnValue(
of(undefined)
);
}); });
it('increases outstandingMessages', () => { it('increases outstandingMessages', () => {
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue({ vi.spyOn<any, any>(
checkSessionService,
'getExistingIframe'
).mockReturnValue({
contentWindow: { postMessage: () => undefined }, contentWindow: { postMessage: () => undefined },
}); });
const authWellKnownEndpoints = { const authWellKnownEndpoints = {
@ -199,22 +208,26 @@ describe('CheckSessionService', () => {
}; };
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
mockImplementationWhenArgsEqual(
mockImplementationWhenArgsEqual( mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'), vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config], ['authWellKnownEndPoints', config],
() => authWellKnownEndpoints () => authWellKnownEndpoints
) ),
.withArgs('session_state', config) ['session_state', config],
.mockReturnValue('session_state'); () => 'session_state'
);
vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined); vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined);
(checkSessionService as any).pollServerSession('clientId', config); (checkSessionService as any).pollServerSession('clientId', config);
expect((checkSessionService as any).outstandingMessages).toBe(1); expect((checkSessionService as any).outstandingMessages).toBe(1);
}); });
it('logs warning if iframe does not exist', () => { it('logs warning if iframe does not exist', () => {
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue( vi.spyOn<any, any>(
null checkSessionService,
); 'getExistingIframe'
).mockReturnValue(null);
const authWellKnownEndpoints = { const authWellKnownEndpoints = {
checkSessionIframe: 'https://some-testing-url.com', checkSessionIframe: 'https://some-testing-url.com',
}; };
@ -238,9 +251,10 @@ describe('CheckSessionService', () => {
}); });
it('logs warning if clientId is not set', () => { it('logs warning if clientId is not set', () => {
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue( vi.spyOn<any, any>(
{} checkSessionService,
); 'getExistingIframe'
).mockReturnValue({});
const authWellKnownEndpoints = { const authWellKnownEndpoints = {
checkSessionIframe: 'https://some-testing-url.com', checkSessionIframe: 'https://some-testing-url.com',
}; };
@ -264,21 +278,24 @@ describe('CheckSessionService', () => {
}); });
it('logs debug if session_state is not set', () => { it('logs debug if session_state is not set', () => {
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue( vi.spyOn<any, any>(
{} checkSessionService,
); 'getExistingIframe'
).mockReturnValue({});
const authWellKnownEndpoints = { const authWellKnownEndpoints = {
checkSessionIframe: 'https://some-testing-url.com', checkSessionIframe: 'https://some-testing-url.com',
}; };
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
mockImplementationWhenArgsEqual(
mockImplementationWhenArgsEqual( mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'), vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config], ['authWellKnownEndPoints', config],
() => authWellKnownEndpoints () => authWellKnownEndpoints
) ),
.withArgs('session_state', config) ['session_state', config],
.mockReturnValue(null); () => null
);
const spyLogDebug = vi const spyLogDebug = vi
.spyOn(loggerService, 'logDebug') .spyOn(loggerService, 'logDebug')
@ -289,19 +306,23 @@ describe('CheckSessionService', () => {
}); });
it('logs debug if session_state is set but authWellKnownEndpoints are not set', () => { it('logs debug if session_state is set but authWellKnownEndpoints are not set', () => {
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue( vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue(
{} {} as any
); );
// biome-ignore lint/suspicious/noEvolvingTypes: <explanation>
const authWellKnownEndpoints = null; const authWellKnownEndpoints = null;
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
mockImplementationWhenArgsEqual(
mockImplementationWhenArgsEqual( mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'), vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config], ['authWellKnownEndPoints', config],
() => authWellKnownEndpoints () => authWellKnownEndpoints
) ),
.withArgs('session_state', config) ['session_state', config],
.mockReturnValue('some_session_state'); () => 'some_session_state'
);
const spyLogDebug = vi const spyLogDebug = vi
.spyOn(loggerService, 'logDebug') .spyOn(loggerService, 'logDebug')
.mockImplementation(() => undefined); .mockImplementation(() => undefined);
@ -321,7 +342,7 @@ describe('CheckSessionService', () => {
serviceAsAny.iframeRefreshInterval = lastRefresh; serviceAsAny.iframeRefreshInterval = lastRefresh;
const result = await lastValueFrom(serviceAsAny.init()); const result = await lastValueFrom(serviceAsAny.init());
expect(result).toBeUndefined(); expect(result).toBeUndefined();
}); });
}); });
@ -345,9 +366,10 @@ expect(result).toBeUndefined();
describe('checkSessionChanged$', () => { describe('checkSessionChanged$', () => {
it('emits when internal event is thrown', async () => { it('emits when internal event is thrown', async () => {
const result = await lastValueFrom(checkSessionService.checkSessionChanged$ const result = await lastValueFrom(
.pipe(skip(1))); checkSessionService.checkSessionChanged$.pipe(skip(1))
expect(result).toBe(true); );
expect(result).toBe(true);
const serviceAsAny = checkSessionService as any; const serviceAsAny = checkSessionService as any;
@ -355,17 +377,21 @@ expect(result).toBe(true);
}); });
it('emits false initially', async () => { it('emits false initially', async () => {
const result = await lastValueFrom(checkSessionService.checkSessionChanged$); const result = await lastValueFrom(
expect(result).toBe(false); checkSessionService.checkSessionChanged$
);
expect(result).toBe(false);
}); });
it('emits false then true when emitted', async () => { it('emits false then true when emitted', async () => {
const expectedResultsInOrder = [false, true]; const expectedResultsInOrder = [false, true];
let counter = 0; let counter = 0;
const result = await lastValueFrom(checkSessionService.checkSessionChanged$); const result = await lastValueFrom(
expect(result).toBe(expectedResultsInOrder[counter]);; checkSessionService.checkSessionChanged$
counter++; );
expect(result).toBe(expectedResultsInOrder[counter]);
counter++;
(checkSessionService as any).checkSessionChangedInternal$.next(true); (checkSessionService as any).checkSessionChangedInternal$.next(true);
}); });

View File

@ -1,6 +1,6 @@
import { Injectable, inject } from 'injection-js';
import { DOCUMENT } from '../../dom'; import { DOCUMENT } from '../../dom';
import { inject, Injectable } from 'injection-js'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { OpenIdConfiguration } from '../config/openid-configuration';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
@Injectable() @Injectable()
@ -52,7 +52,7 @@ export class IFrameService {
} }
return null; return null;
} catch (e) { } catch {
return null; return null;
} }
} }

View File

@ -1,5 +1,5 @@
import { TestBed, fakeAsync, tick } from '@/testing'; import { TestBed } from '@/testing';
import { Observable, of, throwError } from 'rxjs'; import { Observable, lastValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service'; import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service';
@ -42,9 +42,6 @@ describe('SilentRenewService ', () => {
FlowHelper, FlowHelper,
], ],
}); });
});
beforeEach(() => {
silentRenewService = TestBed.inject(SilentRenewService); silentRenewService = TestBed.inject(SilentRenewService);
iFrameService = TestBed.inject(IFrameService); iFrameService = TestBed.inject(IFrameService);
flowHelper = TestBed.inject(FlowHelper); flowHelper = TestBed.inject(FlowHelper);
@ -152,9 +149,14 @@ describe('SilentRenewService ', () => {
const urlParts = const urlParts =
'code=some-code&state=some-state&session_state=some-session-state'; 'code=some-code&state=some-state&session_state=some-session-state';
await lastValueFrom(silentRenewService await lastValueFrom(
.codeFlowCallbackSilentRenewIframe([url, urlParts], config, allConfigs)); silentRenewService.codeFlowCallbackSilentRenewIframe(
expect(spy).toHaveBeenCalledExactlyOnceWith( [url, urlParts],
config,
allConfigs
)
);
expect(spy).toHaveBeenCalledExactlyOnceWith(
expectedContext, expectedContext,
config, config,
allConfigs allConfigs
@ -185,10 +187,15 @@ expect(spy).toHaveBeenCalledExactlyOnceWith(
const url = 'url-part-1'; const url = 'url-part-1';
const urlParts = 'error=some_error'; const urlParts = 'error=some_error';
silentRenewService try {
.codeFlowCallbackSilentRenewIframe([url, urlParts], config, allConfigs) await lastValueFrom(
.subscribe({ silentRenewService.codeFlowCallbackSilentRenewIframe(
error: (error) => { [url, urlParts],
config,
allConfigs
)
);
} catch (error) {
expect(error).toEqual(new Error('some_error')); expect(error).toEqual(new Error('some_error'));
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
expect(authStateServiceSpy).toHaveBeenCalledExactlyOnceWith({ expect(authStateServiceSpy).toHaveBeenCalledExactlyOnceWith({
@ -202,8 +209,7 @@ expect(spy).toHaveBeenCalledExactlyOnceWith(
); );
expect(setNonceSpy).toHaveBeenCalledExactlyOnceWith('', config); expect(setNonceSpy).toHaveBeenCalledExactlyOnceWith('', config);
expect(stopPeriodicTokenCheckSpy).toHaveBeenCalledTimes(1); expect(stopPeriodicTokenCheckSpy).toHaveBeenCalledTimes(1);
}, }
});
}); });
}); });
@ -306,8 +312,10 @@ expect(spy).toHaveBeenCalledExactlyOnceWith(
const eventData = { detail: 'detail?detail2' } as CustomEvent; const eventData = { detail: 'detail?detail2' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
const result = await lastValueFrom(silentRenewService.refreshSessionWithIFrameCompleted$); const result = await lastValueFrom(
expect(result).toEqual({ silentRenewService.refreshSessionWithIFrameCompleted$
);
expect(result).toEqual({
refreshToken: 'callbackContext', refreshToken: 'callbackContext',
} as CallbackContext); } as CallbackContext);
@ -352,8 +360,10 @@ expect(result).toEqual({
const eventData = { detail: 'detail?detail2' } as CustomEvent; const eventData = { detail: 'detail?detail2' } as CustomEvent;
const allConfigs = [{ configId: 'configId1' }]; const allConfigs = [{ configId: 'configId1' }];
const result = await lastValueFrom(silentRenewService.refreshSessionWithIFrameCompleted$); const result = await lastValueFrom(
expect(result).toBeNull(); silentRenewService.refreshSessionWithIFrameCompleted$
);
expect(result).toBeNull();
silentRenewService.silentRenewEventHandler( silentRenewService.silentRenewEventHandler(
eventData, eventData,

View File

@ -153,7 +153,7 @@ export class SilentRenewService {
this.flowsDataService.resetSilentRenewRunning(config); this.flowsDataService.resetSilentRenewRunning(config);
}, },
error: (err: unknown) => { error: (err: unknown) => {
this.loggerService.logError(config, 'Error: ' + err); this.loggerService.logError(config, `Error: ${err}`);
this.refreshSessionWithIFrameCompletedInternal$.next(null); this.refreshSessionWithIFrameCompletedInternal$.next(null);
this.flowsDataService.resetSilentRenewRunning(config); this.flowsDataService.resetSilentRenewRunning(config);
}, },

View File

@ -10,6 +10,7 @@ import {
HttpTestingController, HttpTestingController,
provideHttpClientTesting, provideHttpClientTesting,
} from '@ngify/http/testing'; } from '@ngify/http/testing';
import { lastValueFrom } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { AuthStateService } from '../auth-state/auth-state.service'; import { AuthStateService } from '../auth-state/auth-state.service';
import { ConfigurationService } from '../config/config.service'; import { ConfigurationService } from '../config/config.service';
@ -18,14 +19,14 @@ import { mockProvider } from '../testing/mock';
import { AuthInterceptor, authInterceptor } from './auth.interceptor'; import { AuthInterceptor, authInterceptor } from './auth.interceptor';
import { ClosestMatchingRouteService } from './closest-matching-route.service'; import { ClosestMatchingRouteService } from './closest-matching-route.service';
describe(`AuthHttpInterceptor`, () => { describe('AuthHttpInterceptor', () => {
let httpTestingController: HttpTestingController; let httpTestingController: HttpTestingController;
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let httpClient: HttpClient; let httpClient: HttpClient;
let authStateService: AuthStateService; let authStateService: AuthStateService;
let closestMatchingRouteService: ClosestMatchingRouteService; let closestMatchingRouteService: ClosestMatchingRouteService;
describe(`with Class Interceptor`, () => { describe('with Class Interceptor', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [], imports: [],
@ -51,6 +52,7 @@ describe(`AuthHttpInterceptor`, () => {
closestMatchingRouteService = TestBed.inject(ClosestMatchingRouteService); closestMatchingRouteService = TestBed.inject(ClosestMatchingRouteService);
}); });
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
afterEach(() => { afterEach(() => {
httpTestingController.verify(); httpTestingController.verify();
}); });
@ -58,7 +60,7 @@ describe(`AuthHttpInterceptor`, () => {
runTests(); runTests();
}); });
describe(`with Functional Interceptor`, () => { describe('with Functional Interceptor', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
@ -78,6 +80,7 @@ describe(`AuthHttpInterceptor`, () => {
closestMatchingRouteService = TestBed.inject(ClosestMatchingRouteService); closestMatchingRouteService = TestBed.inject(ClosestMatchingRouteService);
}); });
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
afterEach(() => { afterEach(() => {
httpTestingController.verify(); httpTestingController.verify();
}); });
@ -87,7 +90,7 @@ describe(`AuthHttpInterceptor`, () => {
function runTests(): void { function runTests(): void {
it('should add an Authorization header when route matches and token is present', async () => { it('should add an Authorization header when route matches and token is present', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = 'https://jsonplaceholder.typicode.com/';
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
@ -104,7 +107,7 @@ describe(`AuthHttpInterceptor`, () => {
); );
const response = await lastValueFrom(httpClient.get(actionUrl)); const response = await lastValueFrom(httpClient.get(actionUrl));
expect(response).toBeTruthy(); expect(response).toBeTruthy();
const httpRequest = httpTestingController.expectOne(actionUrl); const httpRequest = httpTestingController.expectOne(actionUrl);
@ -115,7 +118,7 @@ expect(response).toBeTruthy();
}); });
it('should not add an Authorization header when `secureRoutes` is not given', async () => { it('should not add an Authorization header when `secureRoutes` is not given', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = 'https://jsonplaceholder.typicode.com/';
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
@ -130,7 +133,7 @@ expect(response).toBeTruthy();
); );
const response = await lastValueFrom(httpClient.get(actionUrl)); const response = await lastValueFrom(httpClient.get(actionUrl));
expect(response).toBeTruthy(); expect(response).toBeTruthy();
const httpRequest = httpTestingController.expectOne(actionUrl); const httpRequest = httpTestingController.expectOne(actionUrl);
@ -141,7 +144,7 @@ expect(response).toBeTruthy();
}); });
it('should not add an Authorization header when no routes configured', async () => { it('should not add an Authorization header when no routes configured', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = 'https://jsonplaceholder.typicode.com/';
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
@ -158,7 +161,7 @@ expect(response).toBeTruthy();
); );
const response = await lastValueFrom(httpClient.get(actionUrl)); const response = await lastValueFrom(httpClient.get(actionUrl));
expect(response).toBeTruthy(); expect(response).toBeTruthy();
const httpRequest = httpTestingController.expectOne(actionUrl); const httpRequest = httpTestingController.expectOne(actionUrl);
@ -169,7 +172,7 @@ expect(response).toBeTruthy();
}); });
it('should not add an Authorization header when no routes configured', async () => { it('should not add an Authorization header when no routes configured', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = 'https://jsonplaceholder.typicode.com/';
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
@ -183,7 +186,7 @@ expect(response).toBeTruthy();
); );
const response = await lastValueFrom(httpClient.get(actionUrl)); const response = await lastValueFrom(httpClient.get(actionUrl));
expect(response).toBeTruthy(); expect(response).toBeTruthy();
const httpRequest = httpTestingController.expectOne(actionUrl); const httpRequest = httpTestingController.expectOne(actionUrl);
@ -194,7 +197,7 @@ expect(response).toBeTruthy();
}); });
it('should not add an Authorization header when route is configured but no token is present', async () => { it('should not add an Authorization header when route is configured but no token is present', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = 'https://jsonplaceholder.typicode.com/';
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
@ -209,7 +212,7 @@ expect(response).toBeTruthy();
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(''); vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('');
const response = await lastValueFrom(httpClient.get(actionUrl)); const response = await lastValueFrom(httpClient.get(actionUrl));
expect(response).toBeTruthy(); expect(response).toBeTruthy();
const httpRequest = httpTestingController.expectOne(actionUrl); const httpRequest = httpTestingController.expectOne(actionUrl);
@ -220,14 +223,14 @@ expect(response).toBeTruthy();
}); });
it('should not add an Authorization header when no config is present', async () => { it('should not add an Authorization header when no config is present', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = 'https://jsonplaceholder.typicode.com/';
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
false false
); );
const response = await lastValueFrom(httpClient.get(actionUrl)); const response = await lastValueFrom(httpClient.get(actionUrl));
expect(response).toBeTruthy(); expect(response).toBeTruthy();
const httpRequest = httpTestingController.expectOne(actionUrl); const httpRequest = httpTestingController.expectOne(actionUrl);
@ -241,7 +244,7 @@ expect(response).toBeTruthy();
vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue(
true true
); );
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = 'https://jsonplaceholder.typicode.com/';
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ {
@ -258,7 +261,7 @@ expect(response).toBeTruthy();
}); });
const response = await lastValueFrom(httpClient.get(actionUrl)); const response = await lastValueFrom(httpClient.get(actionUrl));
expect(response).toBeTruthy(); expect(response).toBeTruthy();
const httpRequest = httpTestingController.expectOne(actionUrl); const httpRequest = httpTestingController.expectOne(actionUrl);
@ -269,8 +272,8 @@ expect(response).toBeTruthy();
}); });
it('should add an Authorization header when multiple routes are configured and token is present', async () => { it('should add an Authorization header when multiple routes are configured and token is present', async () => {
const actionUrl = `https://jsonplaceholder.typicode.com/`; const actionUrl = 'https://jsonplaceholder.typicode.com/';
const actionUrl2 = `https://some-other-url.com/`; const actionUrl2 = 'https://some-other-url.com/';
vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([
{ secureRoutes: [actionUrl, actionUrl2], configId: 'configId1' }, { secureRoutes: [actionUrl, actionUrl2], configId: 'configId1' },
@ -283,11 +286,11 @@ expect(response).toBeTruthy();
true true
); );
const response = await lastValueFrom(httpClient.get(actionUrl)); let response = await lastValueFrom(httpClient.get(actionUrl));
expect(response).toBeTruthy(); expect(response).toBeTruthy();
const response = await lastValueFrom(httpClient.get(actionUrl2)); response = await lastValueFrom(httpClient.get(actionUrl2));
expect(response).toBeTruthy(); expect(response).toBeTruthy();
const httpRequest = httpTestingController.expectOne(actionUrl); const httpRequest = httpTestingController.expectOne(actionUrl);

View File

@ -73,7 +73,7 @@ function interceptRequest(
if (allRoutesConfiguredFlat.length === 0) { if (allRoutesConfiguredFlat.length === 0) {
deps.loggerService.logDebug( deps.loggerService.logDebug(
allConfigurations[0], allConfigurations[0],
`No routes to check configured` 'No routes to check configured'
); );
return next(req); return next(req);
@ -114,7 +114,7 @@ function interceptRequest(
`'${req.url}' matches configured route '${matchingRoute}', adding token` `'${req.url}' matches configured route '${matchingRoute}', adding token`
); );
req = req.clone({ req = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + token), headers: req.headers.set('Authorization', `Bearer ${token}`),
}); });
return next(req); return next(req);

View File

@ -1,5 +1,4 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock'; import { mockProvider } from '../testing/mock';
import { ClosestMatchingRouteService } from './closest-matching-route.service'; import { ClosestMatchingRouteService } from './closest-matching-route.service';
@ -11,9 +10,6 @@ describe('ClosestMatchingRouteService', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ClosestMatchingRouteService, mockProvider(LoggerService)], providers: [ClosestMatchingRouteService, mockProvider(LoggerService)],
}); });
});
beforeEach(() => {
service = TestBed.inject(ClosestMatchingRouteService); service = TestBed.inject(ClosestMatchingRouteService);
}); });

View File

@ -1,5 +1,5 @@
import { Injectable } from 'injection-js'; import { Injectable } from 'injection-js';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
@Injectable() @Injectable()
export class ClosestMatchingRouteService { export class ClosestMatchingRouteService {

View File

@ -1,17 +1,20 @@
import { Injectable } from 'injection-js'; import { Injectable } from 'injection-js';
import { AbstractLoggerService } from './abstract-logger.service'; import type { AbstractLoggerService } from './abstract-logger.service';
@Injectable() @Injectable()
export class ConsoleLoggerService implements AbstractLoggerService { export class ConsoleLoggerService implements AbstractLoggerService {
logError(message: string | object, ...args: any[]): void { logError(message: string | object, ...args: any[]): void {
// biome-ignore lint/suspicious/noConsole: <explanation>
console.error(message, ...args); console.error(message, ...args);
} }
logWarning(message: string | object, ...args: any[]): void { logWarning(message: string | object, ...args: any[]): void {
// biome-ignore lint/suspicious/noConsole: <explanation>
console.warn(message, ...args); console.warn(message, ...args);
} }
logDebug(message: string | object, ...args: any[]): void { logDebug(message: string | object, ...args: any[]): void {
// biome-ignore lint/suspicious/noConsole: <explanation>
console.debug(message, ...args); console.debug(message, ...args);
} }
} }

View File

@ -1,6 +1,6 @@
export enum LogLevel { export enum LogLevel {
None, None = 0,
Debug, Debug = 1,
Warn, Warn = 2,
Error, Error = 3,
} }

View File

@ -15,9 +15,6 @@ describe('Logger Service', () => {
{ provide: AbstractLoggerService, useClass: ConsoleLoggerService }, { provide: AbstractLoggerService, useClass: ConsoleLoggerService },
], ],
}); });
});
beforeEach(() => {
loggerService = TestBed.inject(LoggerService); loggerService = TestBed.inject(LoggerService);
}); });

View File

@ -1,6 +1,6 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { of } from 'rxjs'; import { lastValueFrom, of } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service';
import { mockProvider } from '../testing/mock'; import { mockProvider } from '../testing/mock';
@ -31,9 +31,6 @@ describe('LoginService', () => {
mockProvider(PopUpService), mockProvider(PopUpService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(LoginService); service = TestBed.inject(LoginService);
parLoginService = TestBed.inject(ParLoginService); parLoginService = TestBed.inject(ParLoginService);
popUpLoginService = TestBed.inject(PopUpLoginService); popUpLoginService = TestBed.inject(PopUpLoginService);
@ -89,6 +86,7 @@ describe('LoginService', () => {
it("should throw error if configuration is null and doesn't call loginPar or loginStandard", () => { it("should throw error if configuration is null and doesn't call loginPar or loginStandard", () => {
// arrange // arrange
// biome-ignore lint/suspicious/noEvolvingTypes: <explanation>
const config = null; const config = null;
const loginParSpy = vi.spyOn(parLoginService, 'loginPar'); const loginParSpy = vi.spyOn(parLoginService, 'loginPar');
const standardLoginSpy = vi.spyOn(standardLoginService, 'loginStandard'); const standardLoginSpy = vi.spyOn(standardLoginService, 'loginStandard');
@ -119,8 +117,8 @@ describe('LoginService', () => {
// act // act
await lastValueFrom(service.loginWithPopUp(config, [config])); await lastValueFrom(service.loginWithPopUp(config, [config]));
expect(loginWithPopUpPar).toHaveBeenCalledTimes(1);; expect(loginWithPopUpPar).toHaveBeenCalledTimes(1);
expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled(); expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled();
}); });
it('calls standardLoginService loginstandard if usePushedAuthorisationRequests is false', async () => { it('calls standardLoginService loginstandard if usePushedAuthorisationRequests is false', async () => {
@ -135,8 +133,8 @@ expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled();
// act // act
await lastValueFrom(service.loginWithPopUp(config, [config])); await lastValueFrom(service.loginWithPopUp(config, [config]));
expect(loginWithPopUpPar).not.toHaveBeenCalled();; expect(loginWithPopUpPar).not.toHaveBeenCalled();
expect(loginWithPopUpStandardSpy).toHaveBeenCalledTimes(1); expect(loginWithPopUpStandardSpy).toHaveBeenCalledTimes(1);
}); });
it('stores the customParams to the storage if customParams are given', async () => { it('stores the customParams to the storage if customParams are given', async () => {
@ -153,15 +151,17 @@ expect(loginWithPopUpStandardSpy).toHaveBeenCalledTimes(1);
); );
// act // act
await lastValueFrom(service.loginWithPopUp(config, [config], authOptions)); await lastValueFrom(
expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith( service.loginWithPopUp(config, [config], authOptions)
);
expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith(
'storageCustomParamsAuthRequest', 'storageCustomParamsAuthRequest',
{ custom: 'params' }, { custom: 'params' },
config config
); );
}); });
it('returns error if there is already a popup open', () => { it('returns error if there is already a popup open', async () => {
// arrange // arrange
const config = { usePushedAuthorisationRequests: false }; const config = { usePushedAuthorisationRequests: false };
const authOptions = { customParams: { custom: 'params' } }; const authOptions = { customParams: { custom: 'params' } };
@ -175,13 +175,14 @@ expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith(
vi.spyOn(popUpService, 'isCurrentlyInPopup').mockReturnValue(true); vi.spyOn(popUpService, 'isCurrentlyInPopup').mockReturnValue(true);
// act // act
const result = await lastValueFrom(service const result = await lastValueFrom(
.loginWithPopUp(config, [config], authOptions)); service.loginWithPopUp(config, [config], authOptions)
expect(result).toEqual({ );
expect(result).toEqual({
errorMessage: 'There is already a popup open.', errorMessage: 'There is already a popup open.',
} as LoginResponse);; } as LoginResponse);
expect(loginWithPopUpPar).not.toHaveBeenCalled();; expect(loginWithPopUpPar).not.toHaveBeenCalled();
expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled(); expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled();
}); });
}); });
}); });

View File

@ -1,5 +1,5 @@
import { TestBed } from '@/testing'; import { TestBed, spyOnProperty } from '@/testing';
import { of } from 'rxjs'; import { lastValueFrom, of } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { CheckAuthService } from '../../auth-state/check-auth.service'; import { CheckAuthService } from '../../auth-state/check-auth.service';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
@ -40,9 +40,6 @@ describe('ParLoginService', () => {
mockProvider(ParService), mockProvider(ParService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(ParLoginService); service = TestBed.inject(ParLoginService);
loggerService = TestBed.inject(LoggerService); loggerService = TestBed.inject(LoggerService);
responseTypeValidationService = TestBed.inject( responseTypeValidationService = TestBed.inject(
@ -68,7 +65,7 @@ describe('ParLoginService', () => {
).mockReturnValue(false); ).mockReturnValue(false);
const loggerSpy = vi.spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const result = service.loginPar({}); const result = await lastValueFrom(service.loginPar({}));
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
@ -89,10 +86,12 @@ describe('ParLoginService', () => {
.spyOn(parService, 'postParRequest') .spyOn(parService, 'postParRequest')
.mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse));
const result = service.loginPar({ const result = await lastValueFrom(
service.loginPar({
authWellknownEndpointUrl: 'authWellknownEndpoint', authWellknownEndpointUrl: 'authWellknownEndpoint',
responseType: 'stubValue', responseType: 'stubValue',
}); })
);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
@ -117,9 +116,11 @@ describe('ParLoginService', () => {
.spyOn(parService, 'postParRequest') .spyOn(parService, 'postParRequest')
.mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse));
const result = service.loginPar(config, { const result = await lastValueFrom(
service.loginPar(config, {
customParams: { some: 'thing' }, customParams: { some: 'thing' },
}); })
);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(spy).toHaveBeenCalledExactlyOnceWith(config, { expect(spy).toHaveBeenCalledExactlyOnceWith(config, {
@ -148,7 +149,7 @@ describe('ParLoginService', () => {
vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(''); vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue('');
const spy = vi.spyOn(loggerService, 'logError'); const spy = vi.spyOn(loggerService, 'logError');
const result = service.loginPar(config); const result = await lastValueFrom(service.loginPar(config));
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
@ -179,7 +180,7 @@ describe('ParLoginService', () => {
); );
const spy = vi.spyOn(redirectService, 'redirectTo'); const spy = vi.spyOn(redirectService, 'redirectTo');
service.loginPar(config, authOptions); await lastValueFrom(service.loginPar(config, authOptions));
expect(spy).toHaveBeenCalledExactlyOnceWith('some-par-url'); expect(spy).toHaveBeenCalledExactlyOnceWith('some-par-url');
}); });
@ -206,7 +207,7 @@ describe('ParLoginService', () => {
'some-par-url' 'some-par-url'
); );
const redirectToSpy = vi.spyOn(redirectService, 'redirectTo'); const redirectToSpy = vi.spyOn(redirectService, 'redirectTo');
const spy = jasmine.createSpy(); const spy = vi.fn();
const urlHandler = (url: any): void => { const urlHandler = (url: any): void => {
spy(url); spy(url);
}; };
@ -228,12 +229,12 @@ describe('ParLoginService', () => {
const config = {}; const config = {};
const allConfigs = [config]; const allConfigs = [config];
service.loginWithPopUpPar(config, allConfigs).subscribe({ try {
error: (err) => { await lastValueFrom(service.loginWithPopUpPar(config, allConfigs));
} catch (err: any) {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
expect(err.message).toBe('Invalid response type!'); expect(err.message).toBe('Invalid response type!');
}, }
});
}); });
it('calls parService.postParRequest without custom params when no custom params are passed', async () => { it('calls parService.postParRequest without custom params when no custom params are passed', async () => {
@ -256,14 +257,14 @@ describe('ParLoginService', () => {
.spyOn(parService, 'postParRequest') .spyOn(parService, 'postParRequest')
.mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse));
service.loginWithPopUpPar(config, allConfigs).subscribe({ try {
error: (err) => { await lastValueFrom(service.loginWithPopUpPar(config, allConfigs));
} catch (err: any) {
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
expect(err.message).toBe( expect(err.message).toBe(
"Could not create URL with param requestUri: 'url'" "Could not create URL with param requestUri: 'url'"
); );
}, }
});
}); });
it('calls parService.postParRequest with custom params when custom params are passed', async () => { it('calls parService.postParRequest with custom params when custom params are passed', async () => {
@ -286,20 +287,20 @@ describe('ParLoginService', () => {
.spyOn(parService, 'postParRequest') .spyOn(parService, 'postParRequest')
.mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse));
service try {
.loginWithPopUpPar(config, allConfigs, { await lastValueFrom(
service.loginWithPopUpPar(config, allConfigs, {
customParams: { some: 'thing' }, customParams: { some: 'thing' },
}) })
.subscribe({ );
error: (err) => { } catch (err: any) {
expect(spy).toHaveBeenCalledExactlyOnceWith(config, { expect(spy).toHaveBeenCalledExactlyOnceWith(config, {
customParams: { some: 'thing' }, customParams: { some: 'thing' },
}); });
expect(err.message).toBe( expect(err.message).toBe(
"Could not create URL with param requestUri: 'url'" "Could not create URL with param requestUri: 'url'"
); );
}, }
});
}); });
it('returns undefined and logs error when no URL could be created', async () => { it('returns undefined and logs error when no URL could be created', async () => {
@ -324,18 +325,18 @@ describe('ParLoginService', () => {
vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(''); vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue('');
const spy = vi.spyOn(loggerService, 'logError'); const spy = vi.spyOn(loggerService, 'logError');
service try {
.loginWithPopUpPar(config, allConfigs, { await lastValueFrom(
service.loginWithPopUpPar(config, allConfigs, {
customParams: { some: 'thing' }, customParams: { some: 'thing' },
}) })
.subscribe({ );
error: (err) => { } catch (err: any) {
expect(err.message).toBe( expect(err.message).toBe(
"Could not create URL with param requestUri: 'url'" "Could not create URL with param requestUri: 'url'"
); );
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
}, }
});
}); });
it('calls popupService openPopUp when URL could be created', async () => { it('calls popupService openPopUp when URL could be created', async () => {
@ -369,7 +370,7 @@ describe('ParLoginService', () => {
const spy = vi.spyOn(popupService, 'openPopUp'); const spy = vi.spyOn(popupService, 'openPopUp');
await lastValueFrom(service.loginWithPopUpPar(config, allConfigs)); await lastValueFrom(service.loginWithPopUpPar(config, allConfigs));
expect(spy).toHaveBeenCalledExactlyOnceWith( expect(spy).toHaveBeenCalledExactlyOnceWith(
'some-par-url', 'some-par-url',
undefined, undefined,
config config
@ -418,13 +419,15 @@ expect(spy).toHaveBeenCalledExactlyOnceWith(
spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult)); spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult));
const result = await lastValueFrom(service.loginWithPopUpPar(config, allConfigs)); const result = await lastValueFrom(
expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith( service.loginWithPopUpPar(config, allConfigs)
);
expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith(
config, config,
allConfigs, allConfigs,
'someUrl' 'someUrl'
);; );
expect(result).toEqual({ expect(result).toEqual({
isAuthenticated: true, isAuthenticated: true,
configId: 'configId1', configId: 'configId1',
idToken: '', idToken: '',
@ -462,9 +465,11 @@ expect(result).toEqual({
spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult)); spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult));
const result = await lastValueFrom(service.loginWithPopUpPar(config, allConfigs)); const result = await lastValueFrom(
expect(checkAuthSpy).not.toHaveBeenCalled();; service.loginWithPopUpPar(config, allConfigs)
expect(result).toEqual({ );
expect(checkAuthSpy).not.toHaveBeenCalled();
expect(result).toEqual({
isAuthenticated: false, isAuthenticated: false,
errorMessage: 'User closed popup', errorMessage: 'User closed popup',
configId: 'configId1', configId: 'configId1',

View File

@ -1,6 +1,6 @@
import { Injectable, inject } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { type Observable, of, throwError } from 'rxjs'; import { type Observable, of, throwError } from 'rxjs';
import { switchMap, take } from 'rxjs/operators'; import { map, shareReplay, switchMap, take } from 'rxjs/operators';
import type { AuthOptions } from '../../auth-options'; import type { AuthOptions } from '../../auth-options';
import { CheckAuthService } from '../../auth-state/check-auth.service'; import { CheckAuthService } from '../../auth-state/check-auth.service';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
@ -39,7 +39,7 @@ export class ParLoginService {
loginPar( loginPar(
configuration: OpenIdConfiguration, configuration: OpenIdConfiguration,
authOptions?: AuthOptions authOptions?: AuthOptions
): void { ): Observable<void> {
if ( if (
!this.responseTypeValidationService.hasConfigValidResponseType( !this.responseTypeValidationService.hasConfigValidResponseType(
configuration configuration
@ -55,22 +55,30 @@ export class ParLoginService {
'BEGIN Authorize OIDC Flow, no auth data' 'BEGIN Authorize OIDC Flow, no auth data'
); );
this.authWellKnownService const result$ = this.authWellKnownService
.queryAndStoreAuthWellKnownEndPoints(configuration) .queryAndStoreAuthWellKnownEndPoints(configuration)
.pipe( .pipe(
switchMap(() => switchMap(() =>
this.parService.postParRequest(configuration, authOptions) this.parService.postParRequest(configuration, authOptions)
) ),
) map(() => {
.subscribe((response) => { (response) => {
this.loggerService.logDebug(configuration, 'par response: ', response); this.loggerService.logDebug(
configuration,
'par response: ',
response
);
const url = this.urlService.getAuthorizeParUrl( const url = this.urlService.getAuthorizeParUrl(
response.requestUri, response.requestUri,
configuration configuration
); );
this.loggerService.logDebug(configuration, 'par request url: ', url); this.loggerService.logDebug(
configuration,
'par request url: ',
url
);
if (!url) { if (!url) {
this.loggerService.logError( this.loggerService.logError(
@ -86,7 +94,14 @@ export class ParLoginService {
} else { } else {
this.redirectService.redirectTo(url); this.redirectService.redirectTo(url);
} }
}); };
}),
shareReplay(1)
);
result$.subscribe();
return result$;
} }
loginWithPopUpPar( loginWithPopUpPar(

View File

@ -1,6 +1,6 @@
import { TestBed } from '@/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { HttpHeaders } from '@ngify/http'; import { HttpHeaders } from '@ngify/http';
import { of, throwError } from 'rxjs'; import { lastValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
@ -26,9 +26,6 @@ describe('ParService', () => {
mockProvider(StoragePersistenceService), mockProvider(StoragePersistenceService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(ParService); service = TestBed.inject(ParService);
dataService = TestBed.inject(DataService); dataService = TestBed.inject(DataService);
loggerService = TestBed.inject(LoggerService); loggerService = TestBed.inject(LoggerService);
@ -50,13 +47,13 @@ describe('ParService', () => {
['authWellKnownEndPoints', { configId: 'configId1' }], ['authWellKnownEndPoints', { configId: 'configId1' }],
() => null () => null
); );
service.postParRequest({ configId: 'configId1' }).subscribe({ try {
error: (err) => { await lastValueFrom(service.postParRequest({ configId: 'configId1' }));
} catch (err: any) {
expect(err.message).toBe( expect(err.message).toBe(
'Could not read PAR endpoint because authWellKnownEndPoints are not given' 'Could not read PAR endpoint because authWellKnownEndPoints are not given'
); );
}, }
});
}); });
it('throws error if par endpoint does not exist in storage', async () => { it('throws error if par endpoint does not exist in storage', async () => {
@ -68,13 +65,13 @@ describe('ParService', () => {
['authWellKnownEndPoints', { configId: 'configId1' }], ['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({ some: 'thing' }) () => ({ some: 'thing' })
); );
service.postParRequest({ configId: 'configId1' }).subscribe({ try {
error: (err) => { await lastValueFrom(service.postParRequest({ configId: 'configId1' }));
} catch (err: any) {
expect(err.message).toBe( expect(err.message).toBe(
'Could not read PAR endpoint from authWellKnownEndpoints' 'Could not read PAR endpoint from authWellKnownEndpoints'
); );
}, }
});
}); });
it('calls data service with correct params', async () => { it('calls data service with correct params', async () => {
@ -92,7 +89,7 @@ describe('ParService', () => {
.mockReturnValue(of({})); .mockReturnValue(of({}));
await lastValueFrom(service.postParRequest({ configId: 'configId1' })); await lastValueFrom(service.postParRequest({ configId: 'configId1' }));
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith( expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
'parEndpoint', 'parEndpoint',
'some-url123', 'some-url123',
{ configId: 'configId1' }, { configId: 'configId1' },
@ -112,8 +109,10 @@ expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(
vi.spyOn(dataService, 'post').mockReturnValue( vi.spyOn(dataService, 'post').mockReturnValue(
of({ expires_in: 123, request_uri: 'request_uri' }) of({ expires_in: 123, request_uri: 'request_uri' })
); );
const result = await lastValueFrom(service.postParRequest({ configId: 'configId1' })); const result = await lastValueFrom(
expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); service.postParRequest({ configId: 'configId1' })
);
expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
}); });
it('throws error if data service has got an error', async () => { it('throws error if data service has got an error', async () => {
@ -130,8 +129,9 @@ expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
); );
const loggerSpy = vi.spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
service.postParRequest({ configId: 'configId1' }).subscribe({ try {
error: (err) => { await lastValueFrom(service.postParRequest({ configId: 'configId1' }));
} catch (err: any) {
expect(err.message).toBe( expect(err.message).toBe(
'There was an error on ParService postParRequest' 'There was an error on ParService postParRequest'
); );
@ -140,8 +140,7 @@ expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
'There was an error on ParService postParRequest', 'There was an error on ParService postParRequest',
expect.any(Error) expect.any(Error)
); );
}, }
});
}); });
it('should retry once', async () => { it('should retry once', async () => {
@ -160,12 +159,11 @@ expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
) )
); );
service.postParRequest({ configId: 'configId1' }).subscribe({ const res = await lastValueFrom(
next: (res) => { service.postParRequest({ configId: 'configId1' })
);
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
},
});
}); });
it('should retry twice', async () => { it('should retry twice', async () => {
@ -185,12 +183,11 @@ expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
) )
); );
service.postParRequest({ configId: 'configId1' }).subscribe({ const res = await lastValueFrom(
next: (res) => { service.postParRequest({ configId: 'configId1' })
);
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
},
});
}); });
it('should fail after three tries', async () => { it('should fail after three tries', async () => {
@ -211,11 +208,11 @@ expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' });
) )
); );
service.postParRequest({ configId: 'configId1' }).subscribe({ try {
error: (err) => { await lastValueFrom(service.postParRequest({ configId: 'configId1' }));
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
}); });
}); });

View File

@ -1,14 +1,14 @@
import { HttpHeaders } from '@ngify/http'; import { HttpHeaders } from '@ngify/http';
import { inject, Injectable } from 'injection-js'; import { inject, Injectable } from 'injection-js';
import { Observable, throwError } from 'rxjs'; import { type Observable, throwError } from 'rxjs';
import { catchError, map, retry, switchMap } from 'rxjs/operators'; import { catchError, map, retry, switchMap } from 'rxjs/operators';
import { DataService } from '../../api/data.service'; import { DataService } from '../../api/data.service';
import { AuthOptions } from '../../auth-options'; import type { AuthOptions } from '../../auth-options';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { UrlService } from '../../utils/url/url.service'; import { UrlService } from '../../utils/url/url.service';
import { ParResponse } from './par-response'; import type { ParResponse } from './par-response';
@Injectable() @Injectable()
export class ParService { export class ParService {
@ -74,7 +74,7 @@ export class ParService {
}; };
}), }),
catchError((error) => { catchError((error) => {
const errorMessage = `There was an error on ParService postParRequest`; const errorMessage = 'There was an error on ParService postParRequest';
this.loggerService.logError(configuration, errorMessage, error); this.loggerService.logError(configuration, errorMessage, error);

View File

@ -59,12 +59,14 @@ describe('PopUpLoginService', () => {
).mockReturnValue(false); ).mockReturnValue(false);
const loggerSpy = vi.spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
popUpLoginService.loginWithPopUpStandard(config, [config]).subscribe({ try {
error: (err) => { await lastValueFrom(
popUpLoginService.loginWithPopUpStandard(config, [config])
);
} catch (err: any) {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
expect(err.message).toBe('Invalid response type!'); expect(err.message).toBe('Invalid response type!');
}, }
});
}); });
it('calls urlService.getAuthorizeUrl() if everything fits', async () => { it('calls urlService.getAuthorizeUrl() if everything fits', async () => {

View File

@ -1,5 +1,6 @@
import { TestBed, fakeAsync, tick } from '@/testing'; import { TestBed, spyOnProperty } from '@/testing';
import { vi } from 'vitest'; import { lastValueFrom } from 'rxjs';
import { type MockInstance, vi } from 'vitest';
import type { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
@ -113,7 +114,7 @@ describe('PopUpService', () => {
}; };
const result = await lastValueFrom(popUpService.result$); const result = await lastValueFrom(popUpService.result$);
expect(result).toBe(popupResult); expect(result).toBe(popupResult);
(popUpService as any).resultInternal$.next(popupResult); (popUpService as any).resultInternal$.next(popupResult);
}); });
@ -122,7 +123,7 @@ expect(result).toBe(popupResult);
describe('openPopup', () => { describe('openPopup', () => {
it('popup opens with parameters and default options', async () => { it('popup opens with parameters and default options', async () => {
// arrange // arrange
const popupSpy = vi.spyOn(window, 'open').and.callFake( const popupSpy = vi.spyOn(window, 'open').mockImplementation(
() => () =>
({ ({
closed: true, closed: true,
@ -143,7 +144,7 @@ expect(result).toBe(popupResult);
it('popup opens with parameters and passed options', async () => { it('popup opens with parameters and passed options', async () => {
// arrange // arrange
const popupSpy = vi.spyOn(window, 'open').and.callFake( const popupSpy = vi.spyOn(window, 'open').mockImplementation(
() => () =>
({ ({
closed: true, closed: true,
@ -180,9 +181,9 @@ expect(result).toBe(popupResult);
describe('popup closed', () => { describe('popup closed', () => {
let popup: Window; let popup: Window;
let popupResult: PopupResult; let popupResult: PopupResult;
let cleanUpSpy: jasmine.Spy; let cleanUpSpy: MockInstance;
beforeEach(() => { beforeEach(async () => {
popup = { popup = {
closed: false, closed: false,
close: () => undefined, close: () => undefined,
@ -190,12 +191,12 @@ expect(result).toBe(popupResult);
vi.spyOn(window, 'open').mockReturnValue(popup); vi.spyOn(window, 'open').mockReturnValue(popup);
cleanUpSpy = vi.spyOn(popUpService as any, 'cleanUp')(); cleanUpSpy = vi.spyOn(popUpService as any, 'cleanUp');
popupResult = {} as PopupResult; popupResult = {} as PopupResult;
const result = await lastValueFrom(popUpService.result$); const result = await lastValueFrom(popUpService.result$);
(popupResult = result) popupResult = result;
}); });
it('message received with data', async () => { it('message received with data', async () => {
@ -203,8 +204,10 @@ expect(result).toBe(popupResult);
return; return;
}; };
vi.spyOn(window, 'addEventListener').and.callFake( vi.spyOn(window, 'addEventListener').mockImplementation(
(_: any, func: any) => (listener = func) // biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
// biome-ignore lint/complexity/noVoid: <explanation>
(_: any, func: any) => void (listener = func)
); );
popUpService.openPopUp('url', {}, { configId: 'configId1' }); popUpService.openPopUp('url', {}, { configId: 'configId1' });
@ -214,7 +217,7 @@ expect(result).toBe(popupResult);
listener(new MessageEvent('message', { data: 'some-url1111' })); listener(new MessageEvent('message', { data: 'some-url1111' }));
tick(200); await vi.advanceTimersByTimeAsync(200);
expect(popupResult).toEqual({ expect(popupResult).toEqual({
userClosed: false, userClosed: false,
@ -230,7 +233,8 @@ expect(result).toBe(popupResult);
return; return;
}; };
vi.spyOn(window, 'addEventListener').and.callFake( vi.spyOn(window, 'addEventListener').mockImplementation(
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
(_: any, func: any) => (listener = func) (_: any, func: any) => (listener = func)
); );
const nextSpy = vi.spyOn((popUpService as any).resultInternal$, 'next'); const nextSpy = vi.spyOn((popUpService as any).resultInternal$, 'next');
@ -242,7 +246,7 @@ expect(result).toBe(popupResult);
listener(new MessageEvent('message', { data: null })); listener(new MessageEvent('message', { data: null }));
tick(200); vi.advanceTimersByTimeAsync(200);
expect(popupResult).toEqual({} as PopupResult); expect(popupResult).toEqual({} as PopupResult);
expect(cleanUpSpy).toHaveBeenCalled(); expect(cleanUpSpy).toHaveBeenCalled();
@ -257,7 +261,7 @@ expect(result).toBe(popupResult);
(popup as any).closed = true; (popup as any).closed = true;
tick(200); await vi.advanceTimersByTimeAsync(200);
expect(popupResult).toEqual({ expect(popupResult).toEqual({
userClosed: true, userClosed: true,
@ -278,6 +282,8 @@ expect(result).toBe(popupResult);
// act // act
popUpService.sendMessageToMainWindow('', {}); popUpService.sendMessageToMainWindow('', {});
await vi.advanceTimersToNextFrame();
// assert // assert
expect(sendMessageSpy).not.toHaveBeenCalled(); expect(sendMessageSpy).not.toHaveBeenCalled();
}); });

View File

@ -1,11 +1,11 @@
import { DOCUMENT } from '../../dom'; import { DOCUMENT } from '../../dom';
import { inject, Injectable } from 'injection-js'; import { inject, Injectable } from 'injection-js';
import { Observable, Subject } from 'rxjs'; import { type Observable, Subject } from 'rxjs';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { PopupOptions } from './popup-options'; import type { PopupOptions } from './popup-options';
import { PopupResult } from './popup-result'; import type { PopupResult } from './popup-result';
@Injectable() @Injectable()
export class PopUpService { export class PopUpService {
@ -90,7 +90,7 @@ export class PopUpService {
return; return;
} }
this.loggerService.logDebug(config, 'Opened popup with url ' + url); this.loggerService.logDebug(config, `Opened popup with url ${url}`);
const listener = (event: MessageEvent): void => { const listener = (event: MessageEvent): void => {
if (!event?.data || typeof event.data !== 'string') { if (!event?.data || typeof event.data !== 'string') {
@ -104,7 +104,7 @@ export class PopUpService {
this.loggerService.logDebug( this.loggerService.logDebug(
config, config,
'Received message from popup with url ' + event.data `Received message from popup with url ${event.data}`
); );
this.resultInternal$.next({ userClosed: false, receivedUrl: event.data }); this.resultInternal$.next({ userClosed: false, receivedUrl: event.data });

View File

@ -18,9 +18,6 @@ describe('ResponseTypeValidationService', () => {
mockProvider(FlowHelper), mockProvider(FlowHelper),
], ],
}); });
});
beforeEach(() => {
responseTypeValidationService = TestBed.inject( responseTypeValidationService = TestBed.inject(
ResponseTypeValidationService ResponseTypeValidationService
); );

View File

@ -1,5 +1,5 @@
import { inject, Injectable } from 'injection-js'; import { inject, Injectable } from 'injection-js';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { FlowHelper } from '../../utils/flowHelper/flow-helper.service'; import { FlowHelper } from '../../utils/flowHelper/flow-helper.service';

View File

@ -1,5 +1,5 @@
import { TestBed, fakeAsync, tick } from '@/testing'; import { TestBed } from '@/testing';
import { of } from 'rxjs'; import { lastValueFrom, of } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
import { FlowsDataService } from '../../flows/flows-data.service'; import { FlowsDataService } from '../../flows/flows-data.service';
@ -32,9 +32,6 @@ describe('StandardLoginService', () => {
mockProvider(FlowsDataService), mockProvider(FlowsDataService),
], ],
}); });
});
beforeEach(() => {
standardLoginService = TestBed.inject(StandardLoginService); standardLoginService = TestBed.inject(StandardLoginService);
loggerService = TestBed.inject(LoggerService); loggerService = TestBed.inject(LoggerService);
responseTypeValidationService = TestBed.inject( responseTypeValidationService = TestBed.inject(
@ -59,9 +56,11 @@ describe('StandardLoginService', () => {
).mockReturnValue(false); ).mockReturnValue(false);
const loggerSpy = vi.spyOn(loggerService, 'logError'); const loggerSpy = vi.spyOn(loggerService, 'logError');
const result = standardLoginService.loginStandard({ const result = await lastValueFrom(
standardLoginService.loginStandard({
configId: 'configId1', configId: 'configId1',
}); })
);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
@ -84,7 +83,9 @@ describe('StandardLoginService', () => {
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const flowsDataSpy = vi.spyOn(flowsDataService, 'setCodeFlowInProgress'); const flowsDataSpy = vi.spyOn(flowsDataService, 'setCodeFlowInProgress');
const result = standardLoginService.loginStandard(config); const result = await lastValueFrom(
standardLoginService.loginStandard(config)
);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
expect(flowsDataSpy).toHaveBeenCalled(); expect(flowsDataSpy).toHaveBeenCalled();
@ -106,7 +107,9 @@ describe('StandardLoginService', () => {
).mockReturnValue(of({})); ).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const result = standardLoginService.loginStandard(config); const result = await lastValueFrom(
standardLoginService.loginStandard(config)
);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
}); });
@ -126,10 +129,10 @@ describe('StandardLoginService', () => {
'queryAndStoreAuthWellKnownEndPoints' 'queryAndStoreAuthWellKnownEndPoints'
).mockReturnValue(of({})); ).mockReturnValue(of({}));
vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl'));
const redirectSpy = vi.spyOn(redirectService, 'redirectTo')(); const redirectSpy = vi.spyOn(redirectService, 'redirectTo');
standardLoginService.loginStandard(config); standardLoginService.loginStandard(config);
tick(); await vi.advanceTimersByTimeAsync(0);
expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl'); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl');
}); });
@ -151,13 +154,13 @@ describe('StandardLoginService', () => {
const redirectSpy = vi const redirectSpy = vi
.spyOn(redirectService, 'redirectTo') .spyOn(redirectService, 'redirectTo')
.mockImplementation(() => undefined); .mockImplementation(() => undefined);
const spy = jasmine.createSpy(); const spy = vi.fn();
const urlHandler = (url: any): void => { const urlHandler = (url: any): void => {
spy(url); spy(url);
}; };
standardLoginService.loginStandard(config, { urlHandler }); standardLoginService.loginStandard(config, { urlHandler });
tick(); await vi.advanceTimersByTimeAsync(0);
expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl'); expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl');
expect(redirectSpy).not.toHaveBeenCalled(); expect(redirectSpy).not.toHaveBeenCalled();
}); });
@ -183,7 +186,7 @@ describe('StandardLoginService', () => {
); );
standardLoginService.loginStandard(config, {}); standardLoginService.loginStandard(config, {});
tick(); await vi.advanceTimersByTimeAsync(0);
expect(flowsDataSpy).toHaveBeenCalled(); expect(flowsDataSpy).toHaveBeenCalled();
}); });
@ -212,7 +215,7 @@ describe('StandardLoginService', () => {
standardLoginService.loginStandard(config, { standardLoginService.loginStandard(config, {
customParams: { to: 'add', as: 'well' }, customParams: { to: 'add', as: 'well' },
}); });
tick(); await vi.advanceTimersByTimeAsync(0);
expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl'); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl');
expect(getAuthorizeUrlSpy).toHaveBeenCalledExactlyOnceWith(config, { expect(getAuthorizeUrlSpy).toHaveBeenCalledExactlyOnceWith(config, {
customParams: { to: 'add', as: 'well' }, customParams: { to: 'add', as: 'well' },
@ -241,7 +244,7 @@ describe('StandardLoginService', () => {
.mockImplementation(() => undefined); .mockImplementation(() => undefined);
standardLoginService.loginStandard(config); standardLoginService.loginStandard(config);
tick(); await vi.advanceTimersByTimeAsync(0);
expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
config, config,
'Could not create URL', 'Could not create URL',

View File

@ -1,7 +1,8 @@
import { inject, Injectable } from 'injection-js'; import { Injectable, inject } from 'injection-js';
import { AuthOptions } from '../../auth-options'; import { type Observable, map, shareReplay, switchMap } from 'rxjs';
import type { AuthOptions } from '../../auth-options';
import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service';
import { OpenIdConfiguration } from '../../config/openid-configuration'; import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { FlowsDataService } from '../../flows/flows-data.service'; import { FlowsDataService } from '../../flows/flows-data.service';
import { LoggerService } from '../../logging/logger.service'; import { LoggerService } from '../../logging/logger.service';
import { RedirectService } from '../../utils/redirect/redirect.service'; import { RedirectService } from '../../utils/redirect/redirect.service';
@ -27,7 +28,7 @@ export class StandardLoginService {
loginStandard( loginStandard(
configuration: OpenIdConfiguration, configuration: OpenIdConfiguration,
authOptions?: AuthOptions authOptions?: AuthOptions
): void { ): Observable<void> {
if ( if (
!this.responseTypeValidationService.hasConfigValidResponseType( !this.responseTypeValidationService.hasConfigValidResponseType(
configuration configuration
@ -44,16 +45,16 @@ export class StandardLoginService {
); );
this.flowsDataService.setCodeFlowInProgress(configuration); this.flowsDataService.setCodeFlowInProgress(configuration);
this.authWellKnownService const result$ = this.authWellKnownService
.queryAndStoreAuthWellKnownEndPoints(configuration) .queryAndStoreAuthWellKnownEndPoints(configuration)
.subscribe(() => { .pipe(
const { urlHandler } = authOptions || {}; switchMap(() => {
this.flowsDataService.resetSilentRenewRunning(configuration); this.flowsDataService.resetSilentRenewRunning(configuration);
this.urlService return this.urlService.getAuthorizeUrl(configuration, authOptions);
.getAuthorizeUrl(configuration, authOptions) }),
.subscribe((url) => { map((url) => {
const { urlHandler } = authOptions || {};
if (!url) { if (!url) {
this.loggerService.logError( this.loggerService.logError(
configuration, configuration,
@ -69,7 +70,12 @@ export class StandardLoginService {
} else { } else {
this.redirectService.redirectTo(url); this.redirectService.redirectTo(url);
} }
}); }),
}); shareReplay(1)
);
result$.subscribe();
return result$;
} }
} }

View File

@ -1,6 +1,6 @@
import { TestBed } from '@/testing'; import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import type { HttpHeaders } from '@ngify/http'; import type { HttpHeaders } from '@ngify/http';
import { Observable, of, throwError } from 'rxjs'; import { Observable, lastValueFrom, of, throwError } from 'rxjs';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { DataService } from '../api/data.service'; import { DataService } from '../api/data.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service';
@ -35,9 +35,6 @@ describe('Logout and Revoke Service', () => {
mockProvider(RedirectService), mockProvider(RedirectService),
], ],
}); });
});
beforeEach(() => {
service = TestBed.inject(LogoffRevocationService); service = TestBed.inject(LogoffRevocationService);
dataService = TestBed.inject(DataService); dataService = TestBed.inject(DataService);
loggerService = TestBed.inject(LoggerService); loggerService = TestBed.inject(LoggerService);
@ -124,8 +121,8 @@ describe('Logout and Revoke Service', () => {
// Act // Act
const result = await lastValueFrom(service.revokeAccessToken(config)); const result = await lastValueFrom(service.revokeAccessToken(config));
expect(result).toEqual({ data: 'anything' });; expect(result).toEqual({ data: 'anything' });
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}); });
it('loggs error when request is negative', async () => { it('loggs error when request is negative', async () => {
@ -144,12 +141,12 @@ expect(loggerSpy).toHaveBeenCalled();
); );
// Act // Act
service.revokeAccessToken(config).subscribe({ try {
error: (err) => { await lastValueFrom(service.revokeAccessToken(config));
} catch (err: any) {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('should retry once', async () => { it('should retry once', async () => {
@ -170,14 +167,10 @@ expect(loggerSpy).toHaveBeenCalled();
) )
); );
service.revokeAccessToken(config).subscribe({ const res = await lastValueFrom(service.revokeAccessToken(config));
next: (res) => {
// Assert
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual({ data: 'anything' }); expect(res).toEqual({ data: 'anything' });
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
},
});
}); });
it('should retry twice', async () => { it('should retry twice', async () => {
@ -199,14 +192,10 @@ expect(loggerSpy).toHaveBeenCalled();
) )
); );
service.revokeAccessToken(config).subscribe({ const res = await lastValueFrom(service.revokeAccessToken(config));
next: (res) => {
// Assert
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual({ data: 'anything' }); expect(res).toEqual({ data: 'anything' });
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
},
});
}); });
it('should fail after three tries', async () => { it('should fail after three tries', async () => {
@ -229,12 +218,12 @@ expect(loggerSpy).toHaveBeenCalled();
) )
); );
service.revokeAccessToken(config).subscribe({ try {
error: (err) => { await lastValueFrom(service.revokeAccessToken(config));
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}, }
});
}); });
}); });
@ -309,8 +298,8 @@ expect(loggerSpy).toHaveBeenCalled();
// Act // Act
const result = await lastValueFrom(service.revokeRefreshToken(config)); const result = await lastValueFrom(service.revokeRefreshToken(config));
expect(result).toEqual({ data: 'anything' });; expect(result).toEqual({ data: 'anything' });
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}); });
it('loggs error when request is negative', async () => { it('loggs error when request is negative', async () => {
@ -329,12 +318,12 @@ expect(loggerSpy).toHaveBeenCalled();
); );
// Act // Act
service.revokeRefreshToken(config).subscribe({ try {
error: (err) => { await lastValueFrom(service.revokeRefreshToken(config));
} catch (err: any) {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('should retry once', async () => { it('should retry once', async () => {
@ -355,14 +344,10 @@ expect(loggerSpy).toHaveBeenCalled();
) )
); );
service.revokeRefreshToken(config).subscribe({ const res = await lastValueFrom(service.revokeRefreshToken(config));
next: (res) => {
// Assert
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual({ data: 'anything' }); expect(res).toEqual({ data: 'anything' });
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
},
});
}); });
it('should retry twice', async () => { it('should retry twice', async () => {
@ -384,14 +369,10 @@ expect(loggerSpy).toHaveBeenCalled();
) )
); );
service.revokeRefreshToken(config).subscribe({ const res = await lastValueFrom(service.revokeRefreshToken(config));
next: (res) => {
// Assert
expect(res).toBeTruthy(); expect(res).toBeTruthy();
expect(res).toEqual({ data: 'anything' }); expect(res).toEqual({ data: 'anything' });
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
},
});
}); });
it('should fail after three tries', async () => { it('should fail after three tries', async () => {
@ -414,12 +395,12 @@ expect(loggerSpy).toHaveBeenCalled();
) )
); );
service.revokeRefreshToken(config).subscribe({ try {
error: (err) => { await lastValueFrom(service.revokeRefreshToken(config));
} catch (err: any) {
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
}, }
});
}); });
}); });
@ -439,7 +420,7 @@ expect(loggerSpy).toHaveBeenCalled();
// Assert // Assert
await lastValueFrom(result$); await lastValueFrom(result$);
expect(serverStateChangedSpy).not.toHaveBeenCalled(); expect(serverStateChangedSpy).not.toHaveBeenCalled();
}); });
it('logs and returns if `serverStateChanged` is true', async () => { it('logs and returns if `serverStateChanged` is true', async () => {
@ -455,13 +436,13 @@ expect(serverStateChangedSpy).not.toHaveBeenCalled();
// Assert // Assert
await lastValueFrom(result$); await lastValueFrom(result$);
expect(redirectSpy).not.toHaveBeenCalled(); expect(redirectSpy).not.toHaveBeenCalled();
}); });
it('calls urlHandler if urlhandler is passed', async () => { it('calls urlHandler if urlhandler is passed', async () => {
// Arrange // Arrange
vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue'); vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue');
const spy = jasmine.createSpy(); const spy = vi.fn();
const urlHandler = (url: string): void => { const urlHandler = (url: string): void => {
spy(url); spy(url);
}; };
@ -481,9 +462,9 @@ expect(redirectSpy).not.toHaveBeenCalled();
// Assert // Assert
await lastValueFrom(result$); await lastValueFrom(result$);
expect(redirectSpy).not.toHaveBeenCalled();; expect(redirectSpy).not.toHaveBeenCalled();
expect(spy).toHaveBeenCalledExactlyOnceWith('someValue');; expect(spy).toHaveBeenCalledExactlyOnceWith('someValue');
expect(resetAuthorizationDataSpy).toHaveBeenCalled(); expect(resetAuthorizationDataSpy).toHaveBeenCalled();
}); });
it('calls redirect service if no logoutOptions are passed', async () => { it('calls redirect service if no logoutOptions are passed', async () => {
@ -502,7 +483,7 @@ expect(resetAuthorizationDataSpy).toHaveBeenCalled();
// Assert // Assert
await lastValueFrom(result$); await lastValueFrom(result$);
expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue');
}); });
it('calls redirect service if logoutOptions are passed and method is GET', async () => { it('calls redirect service if logoutOptions are passed and method is GET', async () => {
@ -521,7 +502,7 @@ expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue');
// Assert // Assert
await lastValueFrom(result$); await lastValueFrom(result$);
expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue');
}); });
it('calls dataservice post if logoutOptions are passed and method is POST', async () => { it('calls dataservice post if logoutOptions are passed and method is POST', async () => {
@ -553,8 +534,8 @@ expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue');
// Assert // Assert
await lastValueFrom(result$); await lastValueFrom(result$);
expect(redirectSpy).not.toHaveBeenCalled();; expect(redirectSpy).not.toHaveBeenCalled();
expect(postSpy).toHaveBeenCalledExactlyOnceWith( expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'some-url', 'some-url',
{ {
id_token_hint: 'id-token', id_token_hint: 'id-token',
@ -563,10 +544,10 @@ expect(postSpy).toHaveBeenCalledExactlyOnceWith(
}, },
config, config,
expect.anything() expect.anything()
);; );
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTruthy();; expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
@ -605,8 +586,8 @@ expect(httpHeaders.get('Content-Type')).toBe(
// Assert // Assert
await lastValueFrom(result$); await lastValueFrom(result$);
expect(redirectSpy).not.toHaveBeenCalled();; expect(redirectSpy).not.toHaveBeenCalled();
expect(postSpy).toHaveBeenCalledExactlyOnceWith( expect(postSpy).toHaveBeenCalledExactlyOnceWith(
'some-url', 'some-url',
{ {
id_token_hint: 'id-token', id_token_hint: 'id-token',
@ -618,10 +599,10 @@ expect(postSpy).toHaveBeenCalledExactlyOnceWith(
}, },
config, config,
expect.anything() expect.anything()
);; );
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
expect(httpHeaders.has('Content-Type')).toBeTruthy();; expect(httpHeaders.has('Content-Type')).toBeTruthy();
expect(httpHeaders.get('Content-Type')).toBe( expect(httpHeaders.get('Content-Type')).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
); );
}); });
@ -667,8 +648,8 @@ expect(httpHeaders.get('Content-Type')).toBe(
// Act // Act
await lastValueFrom(service.logoffAndRevokeTokens(config, [config])); await lastValueFrom(service.logoffAndRevokeTokens(config, [config]));
expect(revokeRefreshTokenSpy).toHaveBeenCalled();; expect(revokeRefreshTokenSpy).toHaveBeenCalled();
expect(revokeAccessTokenSpy).toHaveBeenCalled(); expect(revokeAccessTokenSpy).toHaveBeenCalled();
}); });
it('logs error when revokeaccesstoken throws an error', async () => { it('logs error when revokeaccesstoken throws an error', async () => {
@ -694,12 +675,12 @@ expect(revokeAccessTokenSpy).toHaveBeenCalled();
); );
// Act // Act
service.logoffAndRevokeTokens(config, [config]).subscribe({ try {
error: (err) => { await lastValueFrom(service.logoffAndRevokeTokens(config, [config]));
} catch (err: any) {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
it('calls logoff in case of success', async () => { it('calls logoff in case of success', async () => {
@ -720,7 +701,7 @@ expect(revokeAccessTokenSpy).toHaveBeenCalled();
// Act // Act
await lastValueFrom(service.logoffAndRevokeTokens(config, [config])); await lastValueFrom(service.logoffAndRevokeTokens(config, [config]));
expect(logoffSpy).toHaveBeenCalled(); expect(logoffSpy).toHaveBeenCalled();
}); });
it('calls logoff with urlhandler in case of success', async () => { it('calls logoff with urlhandler in case of success', async () => {
@ -741,9 +722,10 @@ expect(logoffSpy).toHaveBeenCalled();
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
// Act // Act
await lastValueFrom(service await lastValueFrom(
.logoffAndRevokeTokens(config, [config], { urlHandler })); service.logoffAndRevokeTokens(config, [config], { urlHandler })
expect(logoffSpy).toHaveBeenCalledExactlyOnceWith(config, [config], { );
expect(logoffSpy).toHaveBeenCalledExactlyOnceWith(config, [config], {
urlHandler, urlHandler,
}); });
}); });
@ -768,8 +750,8 @@ expect(logoffSpy).toHaveBeenCalledExactlyOnceWith(config, [config], {
// Act // Act
await lastValueFrom(service.logoffAndRevokeTokens(config, [config])); await lastValueFrom(service.logoffAndRevokeTokens(config, [config]));
expect(revokeRefreshTokenSpy).not.toHaveBeenCalled();; expect(revokeRefreshTokenSpy).not.toHaveBeenCalled();
expect(revokeAccessTokenSpy).toHaveBeenCalled(); expect(revokeAccessTokenSpy).toHaveBeenCalled();
}); });
it('logs error when revokeaccesstoken throws an error', async () => { it('logs error when revokeaccesstoken throws an error', async () => {
@ -791,12 +773,12 @@ expect(revokeAccessTokenSpy).toHaveBeenCalled();
); );
// Act // Act
service.logoffAndRevokeTokens(config, [config]).subscribe({ try {
error: (err) => { await lastValueFrom(service.logoffAndRevokeTokens(config, [config]));
} catch (err: any) {
expect(loggerSpy).toHaveBeenCalled(); expect(loggerSpy).toHaveBeenCalled();
expect(err).toBeTruthy(); expect(err).toBeTruthy();
}, }
});
}); });
}); });

View File

@ -141,18 +141,7 @@ export class LogoffRevocationService {
return this.revokeRefreshToken(config).pipe( return this.revokeRefreshToken(config).pipe(
switchMap((_) => this.revokeAccessToken(config)), switchMap((_) => this.revokeAccessToken(config)),
catchError((error) => { catchError((error) => {
const errorMessage = `revoke token failed`; const errorMessage = 'revoke token failed';
this.loggerService.logError(config, errorMessage, error);
return throwError(() => new Error(errorMessage));
}),
concatMap(() => this.logoff(config, allConfigs, logoutAuthOptions))
);
} else {
return this.revokeAccessToken(config).pipe(
catchError((error) => {
const errorMessage = `revoke accessToken failed`;
this.loggerService.logError(config, errorMessage, error); this.loggerService.logError(config, errorMessage, error);
@ -161,6 +150,16 @@ export class LogoffRevocationService {
concatMap(() => this.logoff(config, allConfigs, logoutAuthOptions)) concatMap(() => this.logoff(config, allConfigs, logoutAuthOptions))
); );
} }
return this.revokeAccessToken(config).pipe(
catchError((error) => {
const errorMessage = 'revoke accessToken failed';
this.loggerService.logError(config, errorMessage, error);
return throwError(() => new Error(errorMessage));
}),
concatMap(() => this.logoff(config, allConfigs, logoutAuthOptions))
);
} }
// https://tools.ietf.org/html/rfc7009 // https://tools.ietf.org/html/rfc7009
@ -281,7 +280,7 @@ export class LogoffRevocationService {
return of(response); return of(response);
}), }),
catchError((error) => { catchError((error) => {
const errorMessage = `Revocation request failed`; const errorMessage = 'Revocation request failed';
this.loggerService.logError(configuration, errorMessage, error); this.loggerService.logError(configuration, errorMessage, error);

View File

@ -1,4 +1,3 @@
// biome-ignore lint/nursery/noEnum: <explanation>
export enum EventTypes { export enum EventTypes {
/** /**
* This only works in the AppModule Constructor * This only works in the AppModule Constructor

View File

@ -1,4 +1,4 @@
import { EventTypes } from './event-types'; import type { EventTypes } from './event-types';
export interface OidcClientNotification<T> { export interface OidcClientNotification<T> {
type: EventTypes; type: EventTypes;

View File

@ -1,4 +1,5 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { lastValueFrom } from 'rxjs';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { EventTypes } from './event-types'; import { EventTypes } from './event-types';
@ -11,9 +12,6 @@ describe('Events Service', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [PublicEventsService], providers: [PublicEventsService],
}); });
});
beforeEach(() => {
eventsService = TestBed.inject(PublicEventsService); eventsService = TestBed.inject(PublicEventsService);
}); });
@ -23,8 +21,8 @@ describe('Events Service', () => {
it('registering to single event with one event emit works', async () => { it('registering to single event with one event emit works', async () => {
const firedEvent = await lastValueFrom(eventsService.registerForEvents()); const firedEvent = await lastValueFrom(eventsService.registerForEvents());
expect(firedEvent).toBeTruthy();; expect(firedEvent).toBeTruthy();
expect(firedEvent).toEqual({ expect(firedEvent).toEqual({
type: EventTypes.ConfigLoaded, type: EventTypes.ConfigLoaded,
value: { myKey: 'myValue' }, value: { myKey: 'myValue' },
}); });
@ -32,11 +30,11 @@ expect(firedEvent).toEqual({
}); });
it('registering to single event with multiple same event emit works', async () => { it('registering to single event with multiple same event emit works', async () => {
const spy = jasmine.createSpy('spy'); const spy = vi.fn()('spy');
const firedEvent = await lastValueFrom(eventsService.registerForEvents()); const firedEvent = await lastValueFrom(eventsService.registerForEvents());
spy(firedEvent);; spy(firedEvent);
expect(firedEvent).toBeTruthy(); expect(firedEvent).toBeTruthy();
eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' }); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' });
eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue2' }); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue2' });
@ -45,18 +43,20 @@ expect(firedEvent).toBeTruthy();
type: EventTypes.ConfigLoaded, type: EventTypes.ConfigLoaded,
value: { myKey: 'myValue' }, value: { myKey: 'myValue' },
}); });
expect(spy.calls.mostRecent().args[0]).toEqual({ expect(spy.postSpy.mock.calls.at(-1)?.[0]).toEqual({
type: EventTypes.ConfigLoaded, type: EventTypes.ConfigLoaded,
value: { myKey: 'myValue2' }, value: { myKey: 'myValue2' },
}); });
}); });
it('registering to single event with multiple emit works', async () => { it('registering to single event with multiple emit works', async () => {
const firedEvent = await lastValueFrom(eventsService const firedEvent = await lastValueFrom(
eventsService
.registerForEvents() .registerForEvents()
.pipe(filter((x) => x.type === EventTypes.ConfigLoaded))); .pipe(filter((x) => x.type === EventTypes.ConfigLoaded))
expect(firedEvent).toBeTruthy();; );
expect(firedEvent).toEqual({ expect(firedEvent).toBeTruthy();
expect(firedEvent).toEqual({
type: EventTypes.ConfigLoaded, type: EventTypes.ConfigLoaded,
value: { myKey: 'myValue' }, value: { myKey: 'myValue' },
}); });

View File

@ -1,7 +1,7 @@
import { Injectable } from 'injection-js'; import { Injectable } from 'injection-js';
import { Observable, ReplaySubject } from 'rxjs'; import { type Observable, ReplaySubject } from 'rxjs';
import { EventTypes } from './event-types'; import type { EventTypes } from './event-types';
import { OidcClientNotification } from './notification'; import type { OidcClientNotification } from './notification';
@Injectable() @Injectable()
export class PublicEventsService { export class PublicEventsService {

View File

@ -11,7 +11,7 @@ export interface RouterStateSnapshot {
} }
export abstract class AbstractRouter { export abstract class AbstractRouter {
navigateByUrl(url: string): void { navigateByUrl(_url: string): void {
// TODO // TODO
// Implementation of navigating to a URL // Implementation of navigating to a URL
} }
@ -23,5 +23,9 @@ export abstract class AbstractRouter {
} }
// TODO // TODO
parseUrl(url: string) {} parseUrl(_url: string): any {
// TODO
// Implementation of getting the current navigation
return null;
}
} }

View File

@ -20,9 +20,6 @@ describe('BrowserStorageService', () => {
}, },
], ],
}); });
});
beforeEach(() => {
abstractSecurityStorage = TestBed.inject(AbstractSecurityStorage); abstractSecurityStorage = TestBed.inject(AbstractSecurityStorage);
service = TestBed.inject(BrowserStorageService); service = TestBed.inject(BrowserStorageService);
}); });
@ -76,7 +73,7 @@ describe('BrowserStorageService', () => {
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')(); const writeSpy = vi.spyOn(abstractSecurityStorage, 'write');
const result = service.write({ anyKey: 'anyvalue' }, config); const result = service.write({ anyKey: 'anyvalue' }, config);
@ -92,7 +89,7 @@ describe('BrowserStorageService', () => {
vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')(); const writeSpy = vi.spyOn(abstractSecurityStorage, 'write');
const somethingFalsy = ''; const somethingFalsy = '';
const result = service.write(somethingFalsy, config); const result = service.write(somethingFalsy, config);
@ -117,7 +114,7 @@ describe('BrowserStorageService', () => {
vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const setItemSpy = vi.spyOn(abstractSecurityStorage, 'remove')(); const setItemSpy = vi.spyOn(abstractSecurityStorage, 'remove');
const result = service.remove('anyKey', config); const result = service.remove('anyKey', config);
@ -137,7 +134,7 @@ describe('BrowserStorageService', () => {
it('returns true if clear is called', () => { it('returns true if clear is called', () => {
vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); vi.spyOn(service as any, 'hasStorage').mockReturnValue(true);
const setItemSpy = vi.spyOn(abstractSecurityStorage, 'clear')(); const setItemSpy = vi.spyOn(abstractSecurityStorage, 'clear');
const config = { configId: 'configId1' }; const config = { configId: 'configId1' };
const result = service.clear(config); const result = service.clear(config);
@ -149,8 +146,11 @@ describe('BrowserStorageService', () => {
describe('hasStorage', () => { describe('hasStorage', () => {
it('returns false if there is no storage', () => { it('returns false if there is no storage', () => {
// biome-ignore lint/suspicious/noGlobalAssign: <explanation>
(Storage as any) = undefined; (Storage as any) = undefined;
expect((service as any).hasStorage()).toBeFalsy(); expect((service as any).hasStorage()).toBeFalsy();
// biome-ignore lint/correctness/noSelfAssign: <explanation>
// biome-ignore lint/suspicious/noGlobalAssign: <explanation>
Storage = Storage; Storage = Storage;
}); });
}); });

View File

@ -1,5 +1,5 @@
import { inject, Injectable } from 'injection-js'; import { inject, Injectable } from 'injection-js';
import { OpenIdConfiguration } from '../config/openid-configuration'; import type { OpenIdConfiguration } from '../config/openid-configuration';
import { LoggerService } from '../logging/logger.service'; import { LoggerService } from '../logging/logger.service';
import { AbstractSecurityStorage } from './abstract-security-storage'; import { AbstractSecurityStorage } from './abstract-security-storage';
@ -54,7 +54,7 @@ export class BrowserStorageService {
if (!this.hasStorage()) { if (!this.hasStorage()) {
this.loggerService.logDebug( this.loggerService.logDebug(
configuration, configuration,
`Wanted to write but Storage was falsy` 'Wanted to write but Storage was falsy'
); );
return false; return false;
@ -94,7 +94,7 @@ export class BrowserStorageService {
if (!this.hasStorage()) { if (!this.hasStorage()) {
this.loggerService.logDebug( this.loggerService.logDebug(
configuration, configuration,
`Wanted to clear storage but Storage was falsy` 'Wanted to clear storage but Storage was falsy'
); );
return false; return false;

View File

@ -9,9 +9,6 @@ describe('DefaultLocalStorageService', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [DefaultLocalStorageService], providers: [DefaultLocalStorageService],
}); });
});
beforeEach(() => {
service = TestBed.inject(DefaultLocalStorageService); service = TestBed.inject(DefaultLocalStorageService);
}); });

View File

@ -1,21 +1,21 @@
import { Injectable } from 'injection-js'; import { Injectable } from 'injection-js';
import { AbstractSecurityStorage } from './abstract-security-storage'; import type { AbstractSecurityStorage } from './abstract-security-storage';
@Injectable() @Injectable()
export class DefaultLocalStorageService implements AbstractSecurityStorage { export class DefaultLocalStorageService implements AbstractSecurityStorage {
public read(key: string): string | null { read(key: string): string | null {
return localStorage.getItem(key); return localStorage.getItem(key);
} }
public write(key: string, value: string): void { write(key: string, value: string): void {
localStorage.setItem(key, value); localStorage.setItem(key, value);
} }
public remove(key: string): void { remove(key: string): void {
localStorage.removeItem(key); localStorage.removeItem(key);
} }
public clear(): void { clear(): void {
localStorage.clear(); localStorage.clear();
} }
} }

View File

@ -9,9 +9,6 @@ describe('DefaultSessionStorageService', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [DefaultSessionStorageService], providers: [DefaultSessionStorageService],
}); });
});
beforeEach(() => {
service = TestBed.inject(DefaultSessionStorageService); service = TestBed.inject(DefaultSessionStorageService);
}); });

View File

@ -1,21 +1,21 @@
import { Injectable } from 'injection-js'; import { Injectable } from 'injection-js';
import { AbstractSecurityStorage } from './abstract-security-storage'; import type { AbstractSecurityStorage } from './abstract-security-storage';
@Injectable() @Injectable()
export class DefaultSessionStorageService implements AbstractSecurityStorage { export class DefaultSessionStorageService implements AbstractSecurityStorage {
public read(key: string): string | null { read(key: string): string | null {
return sessionStorage.getItem(key); return sessionStorage.getItem(key);
} }
public write(key: string, value: string): void { write(key: string, value: string): void {
sessionStorage.setItem(key, value); sessionStorage.setItem(key, value);
} }
public remove(key: string): void { remove(key: string): void {
sessionStorage.removeItem(key); sessionStorage.removeItem(key);
} }
public clear(): void { clear(): void {
sessionStorage.clear(); sessionStorage.clear();
} }
} }

View File

@ -12,9 +12,6 @@ describe('Storage Persistence Service', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [mockProvider(BrowserStorageService)], providers: [mockProvider(BrowserStorageService)],
}); });
});
beforeEach(() => {
service = TestBed.inject(StoragePersistenceService); service = TestBed.inject(StoragePersistenceService);
securityStorage = TestBed.inject(BrowserStorageService); securityStorage = TestBed.inject(BrowserStorageService);
}); });

View File

@ -1,10 +1,11 @@
import { type Observable, of } from 'rxjs'; import { type Observable, of } from 'rxjs';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { vi } from 'vitest';
// Create retriable observable stream to test retry / retryWhen. Credits to: // Create retriable observable stream to test retry / retryWhen. Credits to:
// https://stackoverflow.com/questions/51399819/how-to-create-a-mock-observable-to-test-http-rxjs-retry-retrywhen-in-angular // https://stackoverflow.com/questions/51399819/how-to-create-a-mock-observable-to-test-http-rxjs-retry-retrywhen-in-angular
export const createRetriableStream = (...resp$: any): Observable<any> => { export const createRetriableStream = (...resp$: any): Observable<any> => {
const fetchData: jasmine.Spy = jasmine.createSpy('fetchData'); const fetchData = vi.fn()('fetchData');
fetchData.mockReturnValues(...resp$); fetchData.mockReturnValues(...resp$);

View File

@ -50,7 +50,7 @@ describe('User Service', () => {
expect(userService).toBeTruthy(); expect(userService).toBeTruthy();
}); });
it('public authorize$ is observable$', () => { it('authorize$ is observable$', () => {
expect(userService.userData$).toBeInstanceOf(Observable); expect(userService.userData$).toBeInstanceOf(Observable);
}); });
@ -225,6 +225,7 @@ describe('User Service', () => {
const idToken = ''; const idToken = '';
const decodedIdToken = { sub: 'decodedIdToken' }; const decodedIdToken = { sub: 'decodedIdToken' };
const userDataInstore = ''; const userDataInstore = '';
// biome-ignore lint/suspicious/noEvolvingTypes: <explanation>
const userDataFromSts = null; const userDataFromSts = null;
const config = { const config = {

View File

@ -1,5 +1,4 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { DOCUMENT } from '../../dom'; import { DOCUMENT } from '../../dom';
import { CryptoService } from './crypto.service'; import { CryptoService } from './crypto.service';
@ -16,9 +15,6 @@ describe('CryptoService', () => {
}, },
], ],
}); });
});
beforeEach(() => {
cryptoService = TestBed.inject(CryptoService); cryptoService = TestBed.inject(CryptoService);
}); });
@ -50,9 +46,6 @@ describe('CryptoService: msCrypto', () => {
}, },
], ],
}); });
});
beforeEach(() => {
cryptoService = TestBed.inject(CryptoService); cryptoService = TestBed.inject(CryptoService);
}); });

View File

@ -1,5 +1,4 @@
import { TestBed } from '@/testing'; import { TestBed } from '@/testing';
import { vi } from 'vitest';
import { IFrameService } from '../../iframe/existing-iframe.service'; import { IFrameService } from '../../iframe/existing-iframe.service';
import { EqualityService } from './equality.service'; import { EqualityService } from './equality.service';
@ -10,9 +9,6 @@ describe('EqualityService Tests', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [EqualityService, IFrameService], providers: [EqualityService, IFrameService],
}); });
});
beforeEach(() => {
equalityHelperService = TestBed.inject(EqualityService); equalityHelperService = TestBed.inject(EqualityService);
}); });

View File

@ -99,7 +99,7 @@ export class EqualityService {
return typeof value === 'object'; return typeof value === 'object';
} }
private arraysStrictEqual(arr1: Array<string>, arr2: Array<string>): boolean { private arraysStrictEqual(arr1: string[], arr2: string[]): boolean {
if (arr1.length !== arr2.length) { if (arr1.length !== arr2.length) {
return false; return false;
} }
@ -113,10 +113,7 @@ export class EqualityService {
return true; return true;
} }
private arraysHaveEqualContent( private arraysHaveEqualContent(arr1: string[], arr2: string[]): boolean {
arr1: Array<string>,
arr2: Array<string>
): boolean {
if (arr1.length !== arr2.length) { if (arr1.length !== arr2.length) {
return false; return false;
} }

View File

@ -9,9 +9,6 @@ describe('Flow Helper Service', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [FlowHelper], providers: [FlowHelper],
}); });
});
beforeEach(() => {
flowHelper = TestBed.inject(FlowHelper); flowHelper = TestBed.inject(FlowHelper);
}); });

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