import { Test , TestingModule } from '@nestjs/testing' ; import { AccountBlockedException } from '@fc/account' ; import { RequiredExcept } from '@fc/common' ; import { ConfigService } from '@fc/config' ; import { CoreService } from '@fc/core' ; import { CryptographyFcpService } from '@fc/cryptography-fcp' ; import { LoggerService } from '@fc/logger' ; import { IOidcIdentity } from '@fc/oidc' ; import { RnippReceivedValidEvent , RnippRequestedEvent , RnippService , } from '@fc/rnipp' ; import { ServiceProviderAdapterMongoService } from '@fc/service-provider-adapter-mongo' ; import { SessionService } from '@fc/session' ; import { TrackingService } from '@fc/tracking' ; import { CoreFcpDefaultVerifyHandler } from './core-fcp-default-verify.handler' ; describe ( 'CoreFcpDefaultVerifyHandler' , ( ) => { let service ; const loggerServiceMock = { setContext : jest . fn ( ) , debug : jest . fn ( ) , warn : jest . fn ( ) , trace : jest . fn ( ) , } ; const uidMock = '42' ; const getInteractionResultMock = { prompt : { } , params : { acr_values : 'eidas3' , client_id : 'spId' , } , uid : uidMock , } ; const getInteractionMock = jest . fn ( ) ; const sessionServiceMock = { get : jest . fn ( ) , set : jest . fn ( ) , } ; const rnippServiceMock = { check : jest . fn ( ) , } ; const spIdentityMock = { given_name : 'Edward' , family_name : 'TEACH' , email : 'eteach@fqdn.ext' , } as RequiredExcept < IOidcIdentity , 'sub' | 'email' > ; const idpIdentityMock = { sub : 'some idpSub' , } ; const reqMock = { fc : { interactionId : uidMock } , ip : '123.123.123.123' , } ; const configServiceMock = { get : jest . fn ( ) , } ; const trackingMock = { track : jest . fn ( ) , } ; const accountIdMock = 'accountIdMock value' ; const coreServiceMock = { checkIfAccountIsBlocked : jest . fn ( ) , checkIfAcrIsValid : jest . fn ( ) , computeInteraction : jest . fn ( ) , } ; const sessionDataMock = { idpId : '42' , idpAcr : 'eidas3' , idpName : 'my favorite Idp' , idpIdentity : idpIdentityMock , spId : 'sp_id' , spAcr : 'eidas3' , spName : 'my great SP' , spIdentity : spIdentityMock , } ; const serviceProviderMock = { getById : jest . fn ( ) , } ; const cryptographyFcpServiceMock = { computeSubV1 : jest . fn ( ) , computeIdentityHash : jest . fn ( ) , } ; beforeEach ( async ( ) => { const module = await Test . createTestingModule ( { providers : [ ConfigService , CoreService , CoreFcpDefaultVerifyHandler , LoggerService , SessionService , RnippService , TrackingService , ServiceProviderAdapterMongoService , CryptographyFcpService , ] , } ) . overrideProvider ( ConfigService ) . useValue ( configServiceMock ) . overrideProvider ( CoreService ) . useValue ( coreServiceMock ) . overrideProvider ( LoggerService ) . useValue ( loggerServiceMock ) . overrideProvider ( SessionService ) . useValue ( sessionServiceMock ) . overrideProvider ( RnippService ) . useValue ( rnippServiceMock ) . overrideProvider ( TrackingService ) . useValue ( trackingMock ) . overrideProvider ( ServiceProviderAdapterMongoService ) . useValue ( serviceProviderMock ) . overrideProvider ( CryptographyFcpService ) . useValue ( cryptographyFcpServiceMock ) . compile ( ) ; service = module . get < CoreFcpDefaultVerifyHandler > ( CoreFcpDefaultVerifyHandler ) ; jest . resetAllMocks ( ) ; getInteractionMock . mockResolvedValue ( getInteractionResultMock ) ; sessionServiceMock . get . mockResolvedValue ( sessionDataMock ) ; rnippServiceMock . check . mockResolvedValue ( spIdentityMock ) ; cryptographyFcpServiceMock . computeIdentityHash . mockReturnValueOnce ( 'spIdentityHash' ) . mockReturnValueOnce ( 'idpIdentityHash' ) ; cryptographyFcpServiceMock . computeSubV1 . mockReturnValueOnce ( 'computedSubSp' ) . mockReturnValueOnce ( 'computedSubIdp' ) ; coreServiceMock . computeInteraction . mockResolvedValue ( accountIdMock ) ; } ) ; it ( 'should be defined' , ( ) => { expect ( service ) . toBeDefined ( ) ; } ) ; describe ( 'handle' , ( ) => { const spMock = { key : '123456' , entityId : 'AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH' , } ; const trackingContext = { } ; const handleArgument = { sessionOidc : sessionServiceMock , trackingContext , } ; beforeEach ( ( ) => { serviceProviderMock . getById . mockResolvedValue ( spMock ) ; } ) ; it ( 'Should not throw if verified' , async ( ) => { await expect ( service . handle ( handleArgument ) ) . resolves . not . toThrow ( ) ; } ) ; it ( 'Should throw if account is blocked' , async ( ) => { const errorMock = new AccountBlockedException ( ) ; coreServiceMock . checkIfAccountIsBlocked . mockRejectedValueOnce ( errorMock ) ; await expect ( service . handle ( handleArgument ) ) . rejects . toThrow ( errorMock ) ; } ) ; it ( 'Should throw if acr is not validated' , async ( ) => { const errorMock = new Error ( 'my error 1' ) ; coreServiceMock . checkIfAcrIsValid . mockImplementation ( ( ) => { throw errorMock ; } ) ; await expect ( service . handle ( handleArgument ) ) . rejects . toThrow ( errorMock ) ; } ) ; it ( 'Should throw if identity provider is not usable' , async ( ) => { const errorMock = new Error ( 'my error' ) ; sessionServiceMock . get . mockRejectedValueOnce ( errorMock ) ; await expect ( service . handle ( handleArgument ) ) . rejects . toThrow ( errorMock ) ; } ) ; it ( 'Should throw if service Provider service fails' , async ( ) => { const errorMock = new Error ( 'my error' ) ; serviceProviderMock . getById . mockReset ( ) . mockRejectedValueOnce ( errorMock ) ; await expect ( service . handle ( handleArgument ) ) . rejects . toThrow ( errorMock ) ; } ) ; it ( 'Should throw if rnipp check refuses identity' , async ( ) => { const errorMock = new Error ( 'my error' ) ; rnippServiceMock . check . mockRejectedValueOnce ( errorMock ) ; await expect ( service . handle ( handleArgument ) ) . rejects . toThrow ( errorMock ) ; } ) ; it ( 'Should throw if identity storage for service provider fails' , async ( ) => { const errorMock = new Error ( 'my error' ) ; sessionServiceMock . set . mockRejectedValueOnce ( errorMock ) ; await expect ( service . handle ( handleArgument ) ) . rejects . toThrow ( errorMock ) ; } ) ; it ( 'Should call session set with amr parameter' , async ( ) => { await service . handle ( handleArgument ) ; expect ( sessionServiceMock . set ) . toHaveBeenCalledTimes ( 1 ) ; expect ( sessionServiceMock . set ) . toHaveBeenCalledWith ( { accountId : accountIdMock , amr : [ 'fc' ] , idpIdentity : idpIdentityMock , spIdentity : { ... idpIdentityMock , sub : 'computedSubSp' } , } ) ; } ) ; it ( 'Should call computeInteraction()' , async ( ) => { await service . handle ( handleArgument ) ; expect ( coreServiceMock . computeInteraction ) . toHaveBeenCalledTimes ( 1 ) ; expect ( coreServiceMock . computeInteraction ) . toBeCalledWith ( { spId : sessionDataMock . spId , entityId : spMock . entityId , subSp : 'computedSubSp' , hashSp : 'spIdentityHash' , } , { idpId : sessionDataMock . idpId , subIdp : 'computedSubIdp' , } ) ; } ) ; it ( 'should call computeIdentityHash with rnipp identity on first call' , async ( ) => { await service . handle ( handleArgument ) ; expect ( cryptographyFcpServiceMock . computeIdentityHash ) . toHaveBeenCalledTimes ( 2 ) ; expect ( cryptographyFcpServiceMock . computeIdentityHash ) . toHaveBeenNthCalledWith ( 1 , spIdentityMock ) ; } ) ; it ( 'should call computeIdentityHash with identity given by the identity provider on the second call' , async ( ) => { await service . handle ( handleArgument ) ; expect ( cryptographyFcpServiceMock . computeIdentityHash ) . toHaveBeenCalledTimes ( 2 ) ; expect ( cryptographyFcpServiceMock . computeIdentityHash ) . toHaveBeenNthCalledWith ( 2 , idpIdentityMock ) ; } ) ; it ( 'should call computeSubV1 with entityId and rnippIdentityHash on first call' , async ( ) => { await service . handle ( handleArgument ) ; expect ( cryptographyFcpServiceMock . computeSubV1 ) . toHaveBeenCalledTimes ( 2 ) ; expect ( cryptographyFcpServiceMock . computeSubV1 ) . toHaveBeenNthCalledWith ( 1 , spMock . entityId , 'spIdentityHash' ) ; } ) ; it ( 'should call computeSubV1 with spId and idpIdentityHash on the second call' , async ( ) => { await service . handle ( handleArgument ) ; expect ( cryptographyFcpServiceMock . computeSubV1 ) . toHaveBeenCalledTimes ( 2 ) ; expect ( cryptographyFcpServiceMock . computeSubV1 ) . toHaveBeenNthCalledWith ( 2 , sessionDataMock . spId , 'idpIdentityHash' ) ; } ) ; } ) ; describe ( 'rnippCheck' , ( ) => { it ( 'should not throw if rnipp service does not' , async ( ) => { expect ( service [ 'rnippCheck' ] ( spIdentityMock , reqMock ) ) . resolves . not . toThrow ( ) ; } ) ; it ( 'should return rnippIdentity' , async ( ) => { const result = await service [ 'rnippCheck' ] ( spIdentityMock , reqMock ) ; expect ( result ) . toBe ( spIdentityMock ) ; } ) ; it ( 'should publish events when searching' , async ( ) => { await service [ 'rnippCheck' ] ( spIdentityMock , reqMock ) ; expect ( trackingMock . track ) . toHaveBeenCalledTimes ( 2 ) ; expect ( trackingMock . track ) . toHaveBeenCalledWith ( RnippRequestedEvent , expect . any ( Object ) ) ; expect ( trackingMock . track ) . toHaveBeenCalledWith ( RnippReceivedValidEvent , expect . any ( Object ) ) ; } ) ; it ( 'should add interactionId and Ip address properties in published events' , async ( ) => { const expectedEventStruct = { fc : { interactionId : '42' } , ip : '123.123.123.123' , } ; await service [ 'rnippCheck' ] ( spIdentityMock , reqMock ) ; expect ( trackingMock . track ) . toHaveBeenCalledTimes ( 2 ) ; expect ( trackingMock . track ) . toHaveBeenCalledWith ( RnippRequestedEvent , expectedEventStruct ) ; expect ( trackingMock . track ) . toHaveBeenCalledWith ( RnippReceivedValidEvent , expectedEventStruct ) ; } ) ; } ) ; } ) ;