From ca5f4984a48157de9497b44b8c333fb5126e828d Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Thu, 30 Jan 2025 21:38:04 +0800 Subject: [PATCH] refactor: rewrite observable subscribe --- src/auth-state/check-auth.service.spec.ts | 20 +- src/callback/refresh-session.service.spec.ts | 87 +++--- src/login/par/par-login.service.spec.ts | 10 +- src/login/popup/popup-login.service.spec.ts | 12 +- src/login/popup/popup.service.spec.ts | 8 +- src/oidc.security.service.spec.ts | 291 ++++++++----------- src/testing/index.ts | 1 + src/testing/spy.ts | 54 ++++ src/utils/redirect/redirect.service.spec.ts | 2 +- 9 files changed, 242 insertions(+), 243 deletions(-) diff --git a/src/auth-state/check-auth.service.spec.ts b/src/auth-state/check-auth.service.spec.ts index 5f491b7..bef3f70 100644 --- a/src/auth-state/check-auth.service.spec.ts +++ b/src/auth-state/check-auth.service.spec.ts @@ -2,8 +2,9 @@ import { TestBed, mockImplementationWhenArgsEqual, mockRouterProvider, + spyOnProperty, } from '@/testing'; -import { of, throwError } from 'rxjs'; +import { lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import { AutoLoginService } from '../auto-login/auto-login.service'; import { CallbackService } from '../callback/callback.service'; @@ -108,13 +109,14 @@ describe('CheckAuthService', () => { ); const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); - checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - allConfigs[0]!, - allConfigs, - undefined - ); - }); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith( + allConfigs[0]!, + allConfigs, + undefined + ); }); it('throws error when url has state param and stored config with matching state param is not found', async () => { @@ -171,7 +173,7 @@ describe('CheckAuthService', () => { vi.spyOn(currentUrlService, 'getCurrentUrl').mockReturnValue( 'http://localhost:4200' ); - vi.spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({ + spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({ opener: {} as Window, }); vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null); diff --git a/src/callback/refresh-session.service.spec.ts b/src/callback/refresh-session.service.spec.ts index 73fca25..167194e 100644 --- a/src/callback/refresh-session.service.spec.ts +++ b/src/callback/refresh-session.service.spec.ts @@ -1,5 +1,5 @@ -import { TestBed, fakeAsync, tick } from '@/testing'; -import { of, throwError } from 'rxjs'; +import { TestBed, spyOnProperty } from '@/testing'; +import { lastValueFrom, of, throwError } from 'rxjs'; import { delay } from 'rxjs/operators'; import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; @@ -50,9 +50,6 @@ describe('RefreshSessionService ', () => { mockProvider(PublicEventsService), ], }); - }); - - beforeEach(() => { refreshSessionService = TestBed.inject(RefreshSessionService); flowsDataService = TestBed.inject(FlowsDataService); flowHelper = TestBed.inject(FlowHelper); @@ -180,23 +177,24 @@ describe('RefreshSessionService ', () => { }, ]; - refreshSessionService - .userForceRefreshSession(allConfigs[0]!, allConfigs) - .subscribe({ - next: () => { - fail('It should not return any result.'); - }, - error: (error) => { - expect(error).toBeInstanceOf(Error); - }, - complete: () => { - expect( - flowsDataService.resetSilentRenewRunning - ).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); - }, - }); + try { + const result = await lastValueFrom( + refreshSessionService.userForceRefreshSession( + allConfigs[0]!, + allConfigs + ) + ); + if (result) { + expect.fail('It should not return any result.'); + } else { + expect( + flowsDataService.resetSilentRenewRunning + ).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); + } + } catch (error: any) { + expect(error).toBeInstanceOf(Error); + } }); - it('should call resetSilentRenewRunning in case of no error', async () => { vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue( of({} as LoginResponse) @@ -214,7 +212,7 @@ describe('RefreshSessionService ', () => { .userForceRefreshSession(allConfigs[0]!, allConfigs) .subscribe({ error: () => { - fail('It should not return any error.'); + expect.fail('It should not return any error.'); }, complete: () => { expect( @@ -302,7 +300,7 @@ describe('RefreshSessionService ', () => { vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( true ); - vi.spyOnProperty( + spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' ).mockReturnValue( @@ -340,7 +338,7 @@ describe('RefreshSessionService ', () => { vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - vi.spyOnProperty( + spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' ).mockReturnValue(of(null)); @@ -374,7 +372,7 @@ describe('RefreshSessionService ', () => { refreshSessionService as any, 'startRefreshSession' ).mockReturnValue(of(null)); - vi.spyOnProperty( + spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' ).mockReturnValue(of(null).pipe(delay(11000))); @@ -395,21 +393,24 @@ describe('RefreshSessionService ', () => { ); const expectedInvokeCount = MAX_RETRY_ATTEMPTS; - refreshSessionService - .forceRefreshSession(allConfigs[0]!, allConfigs) - .subscribe({ - next: () => { - fail('It should not return any result.'); - }, - error: (error) => { - expect(error).toBeInstanceOf(Error); - expect(resetSilentRenewRunningSpy).toHaveBeenCalledTimes( - expectedInvokeCount - ); - }, - }); + try { + const result = await lastValueFrom( + refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs) + ); - tick(allConfigs[0].silentRenewTimeoutInSeconds * 10000); + if (result) { + expect.fail('It should not return any result.'); + } + } catch (error: any) { + expect(error).toBeInstanceOf(Error); + expect(resetSilentRenewRunningSpy).toHaveBeenCalledTimes( + expectedInvokeCount + ); + } + + await vi.advanceTimersByTimeAsync( + allConfigs[0]!.silentRenewTimeoutInSeconds * 10000 + ); }); it('occurs unknown error throws it to subscriber', async () => { @@ -426,7 +427,7 @@ describe('RefreshSessionService ', () => { flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens' ).mockReturnValue(false); - vi.spyOnProperty( + spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' ).mockReturnValue(of(null)); @@ -447,7 +448,7 @@ describe('RefreshSessionService ', () => { .forceRefreshSession(allConfigs[0]!, allConfigs) .subscribe({ next: () => { - fail('It should not return any result.'); + expect.fail('It should not return any result.'); }, error: (error) => { expect(error).toBeInstanceOf(Error); @@ -477,7 +478,7 @@ describe('RefreshSessionService ', () => { vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue( false ); - vi.spyOnProperty( + spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' ).mockReturnValue(of(null)); @@ -512,7 +513,7 @@ describe('RefreshSessionService ', () => { refreshSessionService as any, 'startRefreshSession' ).mockReturnValue(of(null)); - vi.spyOnProperty( + spyOnProperty( silentRenewService, 'refreshSessionWithIFrameCompleted$' ).mockReturnValue( diff --git a/src/login/par/par-login.service.spec.ts b/src/login/par/par-login.service.spec.ts index c41831e..53475ec 100644 --- a/src/login/par/par-login.service.spec.ts +++ b/src/login/par/par-login.service.spec.ts @@ -363,7 +363,7 @@ describe('ParLoginService', () => { vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue( of({} as LoginResponse) ); - vi.spyOnProperty(popupService, 'result$').mockReturnValue( + spyOnProperty(popupService, 'result$').mockReturnValue( of({} as PopupResult) ); const spy = vi.spyOn(popupService, 'openPopUp'); @@ -417,9 +417,7 @@ describe('ParLoginService', () => { receivedUrl: 'someUrl', }; - vi.spyOnProperty(popupService, 'result$').mockReturnValue( - of(popupResult) - ); + spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult)); service.loginWithPopUpPar(config, allConfigs).subscribe((result) => { expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith( @@ -465,9 +463,7 @@ describe('ParLoginService', () => { const checkAuthSpy = vi.spyOn(checkAuthService, 'checkAuth'); const popupResult = { userClosed: true } as PopupResult; - vi.spyOnProperty(popupService, 'result$').mockReturnValue( - of(popupResult) - ); + spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult)); service.loginWithPopUpPar(config, allConfigs).subscribe((result) => { expect(checkAuthSpy).not.toHaveBeenCalled(); diff --git a/src/login/popup/popup-login.service.spec.ts b/src/login/popup/popup-login.service.spec.ts index ee27439..97d5862 100644 --- a/src/login/popup/popup-login.service.spec.ts +++ b/src/login/popup/popup-login.service.spec.ts @@ -85,7 +85,7 @@ describe('PopUpLoginService', () => { authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints' ).mockReturnValue(of({})); - vi.spyOnProperty(popupService, 'result$').mockReturnValue( + spyOnProperty(popupService, 'result$').mockReturnValue( of({} as PopupResult) ); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); @@ -115,7 +115,7 @@ describe('PopUpLoginService', () => { 'queryAndStoreAuthWellKnownEndPoints' ).mockReturnValue(of({})); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); - vi.spyOnProperty(popupService, 'result$').mockReturnValue( + spyOnProperty(popupService, 'result$').mockReturnValue( of({} as PopupResult) ); vi.spyOn(checkAuthService, 'checkAuth').mockReturnValue( @@ -162,9 +162,7 @@ describe('PopUpLoginService', () => { receivedUrl: 'someUrl', }; - vi.spyOnProperty(popupService, 'result$').mockReturnValue( - of(popupResult) - ); + spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult)); popUpLoginService .loginWithPopUpStandard(config, [config]) @@ -207,9 +205,7 @@ describe('PopUpLoginService', () => { .mockReturnValue(of({} as LoginResponse)); const popupResult = { userClosed: true } as PopupResult; - vi.spyOnProperty(popupService, 'result$').mockReturnValue( - of(popupResult) - ); + spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult)); popUpLoginService .loginWithPopUpStandard(config, [config]) diff --git a/src/login/popup/popup.service.spec.ts b/src/login/popup/popup.service.spec.ts index d28810a..0a9ea67 100644 --- a/src/login/popup/popup.service.spec.ts +++ b/src/login/popup/popup.service.spec.ts @@ -52,7 +52,7 @@ describe('PopUpService', () => { vi.spyOn(popUpService as any, 'canAccessSessionStorage').mockReturnValue( false ); - vi.spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({ + spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({ opener: {} as Window, }); vi.spyOn(storagePersistenceService, 'read').mockReturnValue({ @@ -89,7 +89,7 @@ describe('PopUpService', () => { vi.spyOn(popUpService as any, 'canAccessSessionStorage').mockReturnValue( true ); - vi.spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({ + spyOnProperty(popUpService as any, 'windowInternal').mockReturnValue({ opener: {} as Window, }); vi.spyOn(storagePersistenceService, 'read').mockReturnValue({ @@ -271,7 +271,7 @@ describe('PopUpService', () => { describe('sendMessageToMainWindow', () => { it('does nothing if window.opener is null', async () => { // arrange - vi.spyOnProperty(window, 'opener').mockReturnValue(null); + spyOnProperty(window, 'opener').mockReturnValue(null); const sendMessageSpy = vi.spyOn(popUpService as any, 'sendMessage'); @@ -284,7 +284,7 @@ describe('PopUpService', () => { it('calls postMessage when window opener is given', async () => { // arrange - vi.spyOnProperty(window, 'opener').mockReturnValue({ + spyOnProperty(window, 'opener').mockReturnValue({ postMessage: () => undefined, }); const sendMessageSpy = vi.spyOn(window.opener, 'postMessage'); diff --git a/src/oidc.security.service.spec.ts b/src/oidc.security.service.spec.ts index bffcd4b..818e59b 100644 --- a/src/oidc.security.service.spec.ts +++ b/src/oidc.security.service.spec.ts @@ -1,6 +1,6 @@ -import { TestBed } from '@/testing'; +import { TestBed, spyOnProperty } from '@/testing'; import { Observable, lastValueFrom, of } from 'rxjs'; -import { vi } from 'vitest'; +import { type MockInstance, vi } from 'vitest'; import { AuthStateService } from './auth-state/auth-state.service'; import { CheckAuthService } from './auth-state/check-auth.service'; import { CallbackService } from './callback/callback.service'; @@ -33,8 +33,10 @@ describe('OidcSecurityService', () => { let userService: UserService; let urlService: UrlService; let callbackService: CallbackService; - let authenticatedSpy: jasmine.Spy; - let userDataSpy: jasmine.Spy; + let authenticatedSpy: MockInstance< + () => (typeof authStateService)['authenticated$'] + >; + let userDataSpy: MockInstance<() => (typeof userService)['userData$']>; beforeEach(() => { TestBed.configureTestingModule({ @@ -71,14 +73,15 @@ describe('OidcSecurityService', () => { callbackService = TestBed.inject(CallbackService); // this is required because these methods will be invoked by the signal properties when the service is created - authenticatedSpy = vi - .spyOnProperty(authStateService, 'authenticated$') - .mockReturnValue( - of({ isAuthenticated: false, allConfigsAuthenticated: [] }) - ); - userDataSpy = vi - .spyOnProperty(userService, 'userData$') - .mockReturnValue(of({ userData: null, allUserData: [] })); + authenticatedSpy = spyOnProperty( + authStateService, + 'authenticated$' + ).mockReturnValue( + of({ isAuthenticated: false, allConfigsAuthenticated: [] }) + ); + userDataSpy = spyOnProperty(userService, 'userData$').mockReturnValue( + of({ userData: null, allUserData: [] }) + ); oidcSecurityService = TestBed.inject(OidcSecurityService); }); @@ -88,11 +91,10 @@ describe('OidcSecurityService', () => { describe('userData$', () => { it('calls userService.userData$', async () => { - oidcSecurityService.userData$.subscribe(() => { - // 1x from this subscribe - // 1x by the signal property - expect(userDataSpy).toHaveBeenCalledTimes(2); - }); + await lastValueFrom(oidcSecurityService.userData$); + // 1x from this subscribe + // 1x by the signal property + expect(userDataSpy).toHaveBeenCalledTimes(2); }); }); @@ -106,11 +108,10 @@ describe('OidcSecurityService', () => { describe('isAuthenticated$', () => { it('calls authStateService.isAuthenticated$', async () => { - oidcSecurityService.isAuthenticated$.subscribe(() => { - // 1x from this subscribe - // 1x by the signal property - expect(authenticatedSpy).toHaveBeenCalledTimes(2); - }); + await lastValueFrom(oidcSecurityService.isAuthenticated$); + // 1x from this subscribe + // 1x by the signal property + expect(authenticatedSpy).toHaveBeenCalledTimes(2); }); }); @@ -126,25 +127,24 @@ describe('OidcSecurityService', () => { describe('checkSessionChanged$', () => { it('calls checkSessionService.checkSessionChanged$', async () => { - const spy = vi - .spyOnProperty(checkSessionService, 'checkSessionChanged$') - .mockReturnValue(of(true)); - - oidcSecurityService.checkSessionChanged$.subscribe(() => { - expect(spy).toHaveBeenCalledTimes(1); - }); + const spy = spyOnProperty( + checkSessionService, + 'checkSessionChanged$' + ).mockReturnValue(of(true)); + await lastValueFrom(oidcSecurityService.checkSessionChanged$); + expect(spy).toHaveBeenCalledTimes(1); }); }); describe('stsCallback$', () => { it('calls callbackService.stsCallback$', async () => { - const spy = vi - .spyOnProperty(callbackService, 'stsCallback$') - .mockReturnValue(of()); + const spy = spyOnProperty( + callbackService, + 'stsCallback$' + ).mockReturnValue(of()); - oidcSecurityService.stsCallback$.subscribe(() => { - expect(spy).toHaveBeenCalledTimes(1); - }); + await lastValueFrom(oidcSecurityService.stsCallback$); + expect(spy).toHaveBeenCalledTimes(1); }); }); @@ -159,9 +159,8 @@ describe('OidcSecurityService', () => { .spyOn(authWellKnownService, 'queryAndStoreAuthWellKnownEndPoints') .mockReturnValue(of({})); - oidcSecurityService.preloadAuthWellKnownDocument().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config); - }); + await lastValueFrom(oidcSecurityService.preloadAuthWellKnownDocument()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); }); @@ -211,9 +210,8 @@ describe('OidcSecurityService', () => { some: 'thing', }); - oidcSecurityService.getUserData('configId').subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config); - }); + await lastValueFrom(oidcSecurityService.getUserData('configId')); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); it('returns userdata', async () => { @@ -245,13 +243,8 @@ describe('OidcSecurityService', () => { .spyOn(checkAuthService, 'checkAuth') .mockReturnValue(of({} as LoginResponse)); - oidcSecurityService.checkAuth().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - config, - [config], - undefined - ); - }); + await lastValueFrom(oidcSecurityService.checkAuth()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config], undefined); }); it('calls checkAuthService.checkAuth() with url if one is passed', async () => { @@ -265,13 +258,8 @@ describe('OidcSecurityService', () => { .spyOn(checkAuthService, 'checkAuth') .mockReturnValue(of({} as LoginResponse)); - oidcSecurityService.checkAuth('some-url').subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - config, - [config], - 'some-url' - ); - }); + await lastValueFrom(oidcSecurityService.checkAuth('some-url')); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config], 'some-url'); }); }); @@ -287,9 +275,8 @@ describe('OidcSecurityService', () => { .spyOn(checkAuthService, 'checkAuthMultiple') .mockReturnValue(of([{}] as LoginResponse[])); - oidcSecurityService.checkAuthMultiple().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith([config], undefined); - }); + await lastValueFrom(oidcSecurityService.checkAuthMultiple()); + expect(spy).toHaveBeenCalledExactlyOnceWith([config], undefined); }); it('calls checkAuthService.checkAuthMultiple() with url if one is passed', async () => { @@ -303,9 +290,8 @@ describe('OidcSecurityService', () => { .spyOn(checkAuthService, 'checkAuthMultiple') .mockReturnValue(of([{}] as LoginResponse[])); - oidcSecurityService.checkAuthMultiple('some-url').subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith([config], 'some-url'); - }); + await lastValueFrom(oidcSecurityService.checkAuthMultiple('some-url')); + expect(spy).toHaveBeenCalledExactlyOnceWith([config], 'some-u-+rl'); }); }); @@ -321,9 +307,8 @@ describe('OidcSecurityService', () => { .spyOn(authStateService, 'isAuthenticated') .mockReturnValue(true); - oidcSecurityService.isAuthenticated().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config); - }); + await lastValueFrom(oidcSecurityService.isAuthenticated()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); }); @@ -339,9 +324,8 @@ describe('OidcSecurityService', () => { .spyOn(checkAuthService, 'checkAuthIncludingServer') .mockReturnValue(of({} as LoginResponse)); - oidcSecurityService.checkAuthIncludingServer().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config]); - }); + await lastValueFrom(oidcSecurityService.checkAuthIncludingServer()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config]); }); }); @@ -357,9 +341,8 @@ describe('OidcSecurityService', () => { .spyOn(authStateService, 'getAccessToken') .mockReturnValue(''); - oidcSecurityService.getAccessToken().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config); - }); + await lastValueFrom(oidcSecurityService.getAccessToken()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); }); @@ -373,9 +356,8 @@ describe('OidcSecurityService', () => { const spy = vi.spyOn(authStateService, 'getIdToken').mockReturnValue(''); - oidcSecurityService.getIdToken().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config); - }); + await lastValueFrom(oidcSecurityService.getIdToken()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); }); @@ -390,9 +372,8 @@ describe('OidcSecurityService', () => { .spyOn(authStateService, 'getRefreshToken') .mockReturnValue(''); - oidcSecurityService.getRefreshToken().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config); - }); + await lastValueFrom(oidcSecurityService.getRefreshToken()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); }); @@ -408,9 +389,8 @@ describe('OidcSecurityService', () => { .spyOn(authStateService, 'getAuthenticationResult') .mockReturnValue(null); - oidcSecurityService.getAuthenticationResult().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config); - }); + await lastValueFrom(oidcSecurityService.getAuthenticationResult()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); }); @@ -426,13 +406,8 @@ describe('OidcSecurityService', () => { .spyOn(tokenHelperService, 'getPayloadFromToken') .mockReturnValue(null); - oidcSecurityService.getPayloadFromIdToken().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - 'some-token', - false, - config - ); - }); + await lastValueFrom(oidcSecurityService.getPayloadFromIdToken()); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-token', false, config); }); it('calls `authStateService.getIdToken` method, encode = true', async () => { @@ -446,9 +421,8 @@ describe('OidcSecurityService', () => { .spyOn(tokenHelperService, 'getPayloadFromToken') .mockReturnValue(null); - oidcSecurityService.getPayloadFromIdToken(true).subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith('some-token', true, config); - }); + await lastValueFrom(oidcSecurityService.getPayloadFromIdToken(true)); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-token', true, config); }); }); @@ -466,13 +440,12 @@ describe('OidcSecurityService', () => { .spyOn(tokenHelperService, 'getPayloadFromToken') .mockReturnValue(null); - oidcSecurityService.getPayloadFromAccessToken().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - 'some-access-token', - false, - config - ); - }); + await lastValueFrom(oidcSecurityService.getPayloadFromAccessToken()); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'some-access-token', + false, + config + ); }); it('calls `authStateService.getIdToken` method, encode = true', async () => { @@ -488,13 +461,12 @@ describe('OidcSecurityService', () => { .spyOn(tokenHelperService, 'getPayloadFromToken') .mockReturnValue(null); - oidcSecurityService.getPayloadFromAccessToken(true).subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - 'some-access-token', - true, - config - ); - }); + await lastValueFrom(oidcSecurityService.getPayloadFromAccessToken(true)); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'some-access-token', + true, + config + ); }); }); @@ -507,9 +479,8 @@ describe('OidcSecurityService', () => { ); const spy = vi.spyOn(flowsDataService, 'setAuthStateControl'); - oidcSecurityService.setState('anyString').subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith('anyString', config); - }); + await lastValueFrom(oidcSecurityService.setState('anyString')); + expect(spy).toHaveBeenCalledExactlyOnceWith('anyString', config); }); }); @@ -522,9 +493,8 @@ describe('OidcSecurityService', () => { ); const spy = vi.spyOn(flowsDataService, 'getAuthStateControl'); - oidcSecurityService.getState().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config); - }); + await lastValueFrom(oidcSecurityService.getState()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config); }); }); @@ -573,14 +543,13 @@ describe('OidcSecurityService', () => { .spyOn(loginService, 'loginWithPopUp') .mockImplementation(() => of({} as LoginResponse)); - oidcSecurityService.authorizeWithPopUp().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - config, - [config], - undefined, - undefined - ); - }); + await lastValueFrom(oidcSecurityService.authorizeWithPopUp()); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + undefined, + undefined + ); }); }); @@ -596,13 +565,8 @@ describe('OidcSecurityService', () => { .spyOn(refreshSessionService, 'userForceRefreshSession') .mockReturnValue(of({} as LoginResponse)); - oidcSecurityService.forceRefreshSession().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - config, - [config], - undefined - ); - }); + await lastValueFrom(oidcSecurityService.forceRefreshSession()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config], undefined); }); }); @@ -617,13 +581,8 @@ describe('OidcSecurityService', () => { .spyOn(logoffRevocationService, 'logoffAndRevokeTokens') .mockReturnValue(of(null)); - oidcSecurityService.logoffAndRevokeTokens().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - config, - [config], - undefined - ); - }); + await lastValueFrom(oidcSecurityService.logoffAndRevokeTokens()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config], undefined); }); }); @@ -638,13 +597,8 @@ describe('OidcSecurityService', () => { .spyOn(logoffRevocationService, 'logoff') .mockReturnValue(of(null)); - oidcSecurityService.logoff().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith( - config, - [config], - undefined - ); - }); + await lastValueFrom(oidcSecurityService.logoff()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config], undefined); }); }); @@ -656,7 +610,6 @@ describe('OidcSecurityService', () => { of({ allConfigs: [config], currentConfig: config }) ); const spy = vi.spyOn(logoffRevocationService, 'logoffLocal'); - await lastValueFrom(oidcSecurityService.logoffLocal()); expect(spy).toHaveBeenCalledExactlyOnceWith(config, [config]); }); @@ -687,9 +640,8 @@ describe('OidcSecurityService', () => { .spyOn(logoffRevocationService, 'revokeAccessToken') .mockReturnValue(of(null)); - oidcSecurityService.revokeAccessToken().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); - }); + await lastValueFrom(oidcSecurityService.revokeAccessToken()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); }); it('calls logoffRevocationService.revokeAccessToken with accesstoken', async () => { @@ -702,9 +654,10 @@ describe('OidcSecurityService', () => { .spyOn(logoffRevocationService, 'revokeAccessToken') .mockReturnValue(of(null)); - oidcSecurityService.revokeAccessToken('access_token').subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, 'access_token'); - }); + await lastValueFrom( + oidcSecurityService.revokeAccessToken('access_token') + ); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, 'access_token'); }); }); @@ -719,9 +672,8 @@ describe('OidcSecurityService', () => { .spyOn(logoffRevocationService, 'revokeRefreshToken') .mockReturnValue(of(null)); - oidcSecurityService.revokeRefreshToken().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); - }); + await lastValueFrom(oidcSecurityService.revokeRefreshToken()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); }); it('calls logoffRevocationService.revokeRefreshToken with refresh token', async () => { @@ -734,9 +686,10 @@ describe('OidcSecurityService', () => { .spyOn(logoffRevocationService, 'revokeRefreshToken') .mockReturnValue(of(null)); - oidcSecurityService.revokeRefreshToken('refresh_token').subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, 'refresh_token'); - }); + await lastValueFrom( + oidcSecurityService.revokeRefreshToken('refresh_token') + ); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, 'refresh_token'); }); }); @@ -752,9 +705,8 @@ describe('OidcSecurityService', () => { .spyOn(urlService, 'getEndSessionUrl') .mockReturnValue(null); - oidcSecurityService.getEndSessionUrl().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); - }); + await lastValueFrom(oidcSecurityService.getEndSessionUrl()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); }); it('calls logoffRevocationService.getEndSessionUrl with customparams', async () => { @@ -768,13 +720,12 @@ describe('OidcSecurityService', () => { .spyOn(urlService, 'getEndSessionUrl') .mockReturnValue(null); - oidcSecurityService - .getEndSessionUrl({ custom: 'params' }) - .subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, { - custom: 'params', - }); - }); + await lastValueFrom( + oidcSecurityService.getEndSessionUrl({ custom: 'params' }) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, { + custom: 'params', + }); }); }); @@ -790,9 +741,8 @@ describe('OidcSecurityService', () => { .spyOn(urlService, 'getAuthorizeUrl') .mockReturnValue(of(null)); - oidcSecurityService.getAuthorizeUrl().subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); - }); + await lastValueFrom(oidcSecurityService.getAuthorizeUrl()); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, undefined); }); it('calls urlService.getAuthorizeUrl with customparams', async () => { @@ -806,13 +756,12 @@ describe('OidcSecurityService', () => { .spyOn(urlService, 'getAuthorizeUrl') .mockReturnValue(of(null)); - oidcSecurityService - .getAuthorizeUrl({ custom: 'params' }) - .subscribe(() => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, { - customParams: { custom: 'params' }, - }); - }); + await lastValueFrom( + oidcSecurityService.getAuthorizeUrl({ custom: 'params' }) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith(config, { + customParams: { custom: 'params' }, + }); }); }); }); diff --git a/src/testing/index.ts b/src/testing/index.ts index 37fb2b2..9482c09 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -2,6 +2,7 @@ export { TestBed } from './testbed'; export { createSpyObj, mockImplementationWhenArgsEqual, + spyOnProperty, } from './spy'; export { createRetriableStream } from './create-retriable-stream.helper'; export { MockRouter, mockRouterProvider } from './router'; diff --git a/src/testing/spy.ts b/src/testing/spy.ts index 5cdaa9b..afa3813 100644 --- a/src/testing/spy.ts +++ b/src/testing/spy.ts @@ -51,3 +51,57 @@ export function mockImplementationWhenArgs>( return spyImpl?.(...args); }); } + +/** + * mock Jasmine spyOnProperty + */ +export function spyOnProperty( + obj: T, + propertyKey: K, + accessType: 'get' | 'set' = 'get', + mockImplementation?: any +) { + const originalDescriptor = Object.getOwnPropertyDescriptor(obj, propertyKey); + + if (!originalDescriptor) { + throw new Error( + `Property ${String(propertyKey)} does not exist on the object.` + ); + } + + const spy = vi.fn(); + + let value: T[K] | undefined; + + if (accessType === 'get') { + Object.defineProperty(obj, propertyKey, { + get: mockImplementation + ? () => { + value = mockImplementation(); + return value; + } + : spy, + configurable: true, + }); + } else if (accessType === 'set') { + Object.defineProperty(obj, propertyKey, { + set: mockImplementation + ? (next) => { + value = next; + } + : spy, + configurable: true, + }); + } + + // 恢复原始属性 + spy.mockRestore = () => { + if (originalDescriptor) { + Object.defineProperty(obj, propertyKey, originalDescriptor); + } else { + delete obj[propertyKey]; + } + }; + + return spy; +} diff --git a/src/utils/redirect/redirect.service.spec.ts b/src/utils/redirect/redirect.service.spec.ts index ed498c7..f0ea098 100644 --- a/src/utils/redirect/redirect.service.spec.ts +++ b/src/utils/redirect/redirect.service.spec.ts @@ -39,7 +39,7 @@ describe('Redirect Service Tests', () => { }); it('redirectTo sets window location href', () => { - const spy = vi.spyOnProperty(myDocument.location, 'href', 'set'); + const spy = spyOnProperty(myDocument.location, 'href', 'set'); service.redirectTo('anyurl'); expect(spy).toHaveBeenCalledExactlyOnceWith('anyurl');