import { Test , TestingModule } from '@nestjs/testing' ; import { ConfigService } from '@fc/config' ; import { OidcSession } from '@fc/oidc' ; import { ISessionBoundContext , SessionNotFoundException , SessionService , } from '@fc/session' ; import { IEventContext } from '@fc/tracking' ; import { CoreMissingContextException } from '../exceptions' ; import { ICoreTrackingContext } from '../interfaces' ; import { CoreTrackingService } from './core-tracking.service' ; describe ( 'CoreTrackingService' , ( ) => { let service ; const sessionServiceMock = { get : { bind : jest . fn ( ) , } , getAlias : jest . fn ( ) , } ; const appConfigMock = { urlPrefix : '/api/v2' , } ; const configServiceMock = { get : ( ) => appConfigMock , } ; const eventMock = { step : '1' , category : 'some category' , event : 'name' , route : '/' , exceptions : [ ] , intercept : false , } ; const ipMock = '123.123.123.123' ; const interactionIdMock = 'interactionIdValue' ; const sessionIdMock = 'sessionIdValue' ; const contextMock = { req : { headers : { 'x-forwarded-for' : ipMock } , fc : { interactionId : interactionIdMock , } , } , } ; const extractedValueMock = { ip : ipMock , sessionId : sessionIdMock , interactionId : interactionIdMock , claims : [ 'foo' , 'bar' ] , } ; const sessionDataMock = { accountId : 'accountId Mock Value' , spId : 'some spId' , spName : 'some spName' , spAcr : 'some spAcr' , spIdentity : { sub : 'some spSub' } , idpId : 'some idpId' , idpName : 'some idpName' , idpAcr : 'some idpAcr' , idpIdentity : { sub : 'some idpSub' } , } ; beforeEach ( async ( ) => { const module = await Test . createTestingModule ( { providers : [ CoreTrackingService , SessionService , ConfigService ] , } ) . overrideProvider ( SessionService ) . useValue ( sessionServiceMock ) . overrideProvider ( ConfigService ) . useValue ( configServiceMock ) . compile ( ) ; service = module . get < CoreTrackingService > ( CoreTrackingService ) ; jest . resetAllMocks ( ) ; jest . restoreAllMocks ( ) ; const sessionGetBinded = jest . fn ( ) . mockResolvedValue ( sessionDataMock ) ; sessionServiceMock . get . bind . mockReturnValue ( sessionGetBinded ) ; } ) ; it ( 'should be defined' , ( ) => { expect ( service ) . toBeDefined ( ) ; } ) ; describe ( 'buildLog()' , ( ) => { let extractContextMock ; let getDataFromContextMock ; let getDataFromSessionMock ; beforeEach ( ( ) => { extractContextMock = jest . spyOn < CoreTrackingService , any > ( service , 'extractContext' ) ; getDataFromContextMock = jest . spyOn < CoreTrackingService , any > ( service , 'getDataFromContext' ) ; getDataFromSessionMock = jest . spyOn < CoreTrackingService , any > ( service , 'getDataFromSession' ) ; extractContextMock . mockResolvedValueOnce ( extractedValueMock ) ; getDataFromContextMock . mockReturnValueOnce ( ) ; getDataFromSessionMock . mockResolvedValueOnce ( sessionDataMock ) ; } ) ; it ( 'should call extractContext with req property of context param' , async ( ) => { await service . buildLog ( eventMock , contextMock ) ; expect ( service [ 'extractContext' ] ) . toHaveBeenCalledTimes ( 1 ) ; expect ( service [ 'extractContext' ] ) . toHaveBeenCalledWith ( contextMock ) ; } ) ; it ( 'should call service.getDataFromContext if event is FC_AUTHORIZE_INITIATED' , async ( ) => { await service . buildLog ( service . EventsMap . FC_AUTHORIZE_INITIATED , contextMock ) ; expect ( service [ 'getDataFromContext' ] ) . toHaveBeenCalledTimes ( 1 ) ; expect ( service [ 'getDataFromContext' ] ) . toHaveBeenCalledWith ( contextMock ) ; expect ( service [ 'getDataFromSession' ] ) . toHaveBeenCalledTimes ( 0 ) ; } ) ; it ( 'should call service.getDataFromContext if event is not FC_AUTHORIZE_INITIATED' , async ( ) => { await service . buildLog ( service . EventsMap . FC_SHOWED_IDP_CHOICE , contextMock ) ; expect ( service [ 'getDataFromContext' ] ) . toHaveBeenCalledTimes ( 0 ) ; expect ( service [ 'getDataFromSession' ] ) . toHaveBeenCalledTimes ( 1 ) ; expect ( service [ 'getDataFromSession' ] ) . toHaveBeenCalledWith ( sessionIdMock ) ; } ) ; it ( 'should return log message' , async ( ) => { const expectedResult = { ... sessionDataMock , interactionId : interactionIdMock , step : eventMock . step , category : eventMock . category , event : eventMock . event , ip : ipMock , claims : 'foo bar' , } ; const result = await service . buildLog ( eventMock , contextMock ) ; expect ( result ) . toEqual ( expectedResult ) ; } ) ; it ( 'should return object containing joined claims' , async ( ) => { const contextMock = { req : { } , claims : [ 'foo' , 'bar' ] , } ; const result = await service . buildLog ( eventMock , contextMock ) ; expect ( result ) . toEqual ( expect . objectContaining ( { claims : 'foo bar' } ) ) ; } ) ; it ( 'should return object without claims if none provided' , async ( ) => { const contextMock = { req : { } , } ; const extractedContextMockValue = { ... extractedValueMock } ; extractedContextMockValue . claims = undefined ; extractContextMock . mockReset ( ) ; extractContextMock . mockResolvedValueOnce ( extractedContextMockValue ) ; const result = await service . buildLog ( eventMock , contextMock ) ; expect ( result ) . toEqual ( expect . not . objectContaining ( { claims : 'foo bar' } ) ) ; } ) ; } ) ; describe ( 'extractContext()' , ( ) => { let extractIpFromContextMock ; let getInteractionIdFromContextMock ; beforeEach ( ( ) => { extractIpFromContextMock = jest . spyOn < CoreTrackingService , any > ( service , 'extractIpFromContext' ) ; extractIpFromContextMock . mockReturnValueOnce ( ipMock ) ; getInteractionIdFromContextMock = jest . spyOn < CoreTrackingService , any > ( service , 'getInteractionIdFromContext' ) ; getInteractionIdFromContextMock . mockReturnValueOnce ( interactionIdMock ) ; } ) ; it ( 'should return informations from context' , async ( ) => { const contextMock = { req : { cookies : { _interaction : interactionIdMock , } , sessionId : sessionIdMock , claims : [ 'foo' , 'bar' ] , } , } ; const result = await service [ 'extractContext' ] ( contextMock ) ; expect ( result ) . toEqual ( extractedValueMock ) ; } ) ; it ( 'should throw if req is missing' , async ( ) => { const contextMock = { foo : 'bar' } ; await expect ( ( ) => service [ 'extractContext' ] ( contextMock ) ) . rejects . toThrow ( CoreMissingContextException ) ; } ) ; it ( 'should return informations from session' , async ( ) => { sessionServiceMock . getAlias . mockResolvedValueOnce ( sessionIdMock ) ; const contextMock = { req : { headers : { 'x-forwarded-for' : '123.123.123.123' } , cookies : { _interaction : interactionIdMock , } , claims : [ 'foo' , 'bar' ] , } , } ; const result = await service [ 'extractContext' ] ( contextMock ) ; expect ( result ) . toEqual ( extractedValueMock ) ; expect ( getInteractionIdFromContextMock ) . toHaveBeenCalledTimes ( 1 ) ; expect ( getInteractionIdFromContextMock ) . toHaveBeenCalledWith ( contextMock ) ; } ) ; it ( 'should throw if interactionId is missing' , async ( ) => { const errorMock = new Error ( 'Unknown Error' ) ; extractIpFromContextMock . mockReset ( ) . mockImplementationOnce ( ( ) => { throw errorMock ; } ) ; const contextMock = { req : { } } ; await expect ( ( ) => service [ 'extractContext' ] ( contextMock ) ) . rejects . toThrow ( errorMock ) ; } ) ; it ( 'should throw if `getInteractionIdFromContext` failed' , async ( ) => { const errorMock = new Error ( 'Unknown Error' ) ; getInteractionIdFromContextMock . mockReset ( ) . mockImplementationOnce ( ( ) => { throw errorMock ; } ) ; const contextMock = { req : { cookies : { _interaction : interactionIdMock , } , sessionId : sessionIdMock , } , } ; await expect ( ( ) => service [ 'extractContext' ] ( contextMock ) ) . rejects . toThrow ( errorMock ) ; } ) ; } ) ; describe ( 'extractIpFromContext()' , ( ) => { it ( 'should retreive an IP from the provided context.' , ( ) => { const contextMock = { req : { headers : { 'x-forwarded-for' : ipMock , } , fc : { interactionId : 'foo' } , } , } ; const result = service [ 'extractIpFromContext' ] ( contextMock ) ; expect ( result ) . toEqual ( ipMock ) ; } ) ; it ( 'should throw if header is missing' , ( ) => { const contextMock = { req : { fc : { interactionId : 'foo' } } } ; expect ( ( ) => service [ 'extractIpFromContext' ] ( contextMock ) ) . toThrow ( CoreMissingContextException ) ; } ) ; it ( 'should throw if ip is missing' , ( ) => { const contextMock = { req : { headers : { } , fc : { interactionId : 'foo' } } , } ; expect ( ( ) => service [ 'extractIpFromContext' ] ( contextMock ) ) . toThrow ( CoreMissingContextException ) ; } ) ; } ) ; describe ( 'extractInteractionId' , ( ) => { it ( 'should retrieve an interactionId if provided in `req.fc.interactionId`' , ( ) => { const eventContextMock = { fc : { interactionId : interactionIdMock } , } ; const result = service [ 'extractInteractionId' ] ( eventContextMock ) ; expect ( result ) . toBe ( interactionIdMock ) ; } ) ; it ( 'should retrieve an interactionId if provided in `req.body.uid`' , ( ) => { const eventContextMock = { body : { uid : interactionIdMock } , } ; const result = service [ 'extractInteractionId' ] ( eventContextMock ) ; expect ( result ) . toBe ( interactionIdMock ) ; } ) ; it ( 'should retrieve an interactionId if provided in `req.params.uid`' , ( ) => { const eventContextMock = { params : { uid : interactionIdMock } , } ; const result = service [ 'extractInteractionId' ] ( eventContextMock ) ; expect ( result ) . toBe ( interactionIdMock ) ; } ) ; it ( 'should retrieve an interactionId if provided in `req.cookies._interaction`' , ( ) => { const eventContextMock = { cookies : { _interaction : interactionIdMock } , } ; const result = service [ 'extractInteractionId' ] ( eventContextMock ) ; expect ( result ) . toBe ( interactionIdMock ) ; } ) ; it ( 'should return [undefined] if interactionId is not found / context is corrupted' , ( ) => { const eventContextMock = { } ; const result = service [ 'extractInteractionId' ] ( eventContextMock ) ; expect ( result ) . toBe ( undefined ) ; } ) ; } ) ; describe ( 'getInteractionIdFromContext()' , ( ) => { it ( 'should throw an Exception `CoreMissingContextException` if the context is corrupt' , ( ) => { const eventContextMock = { } ; service [ 'extractInteractionId' ] = jest . fn ( ) . mockReturnValueOnce ( undefined ) ; expect ( ( ) => service [ 'getInteractionIdFromContext' ] ( eventContextMock ) ) . toThrow ( CoreMissingContextException ) ; } ) ; it ( 'should return interactionId provided by extractInteractionId if the context is ok' , ( ) => { const eventContextMock = { } ; service [ 'extractInteractionId' ] = jest . fn ( ) . mockReturnValueOnce ( interactionIdMock ) ; const result = service [ 'getInteractionIdFromContext' ] ( eventContextMock ) ; expect ( result ) . toBe ( interactionIdMock ) ; } ) ; } ) ; describe ( 'getDataFromSession()' , ( ) => { it ( 'should call session.get' , async ( ) => { const boundSessionContextMock = { sessionId : sessionIdMock , moduleName : 'OidcClient' , } ; const sessionGetBinded = jest . fn ( ) . mockResolvedValue ( sessionDataMock ) ; sessionServiceMock . get . bind . mockReturnValueOnce ( sessionGetBinded ) ; await service [ 'getDataFromSession' ] ( sessionIdMock ) ; expect ( sessionServiceMock . get . bind ) . toHaveBeenCalledTimes ( 1 ) ; expect ( sessionServiceMock . get . bind ) . toHaveBeenCalledWith ( sessionServiceMock , boundSessionContextMock ) ; } ) ; it ( 'should throw if session is not found' , async ( ) => { const sessionGetBinded = jest . fn ( ) . mockResolvedValue ( undefined ) ; sessionServiceMock . get . bind . mockReturnValueOnce ( sessionGetBinded ) ; await expect ( service [ 'getDataFromSession' ] ( sessionIdMock ) ) . rejects . toThrow ( SessionNotFoundException ) ; } ) ; it ( 'Should return partial data from session.get' , async ( ) => { const expectedResult = { accountId : 'accountId Mock Value' , spId : 'some spId' , spName : 'some spName' , spAcr : 'some spAcr' , spSub : 'some spSub' , idpId : 'some idpId' , idpName : 'some idpName' , idpAcr : 'some idpAcr' , idpSub : 'some idpSub' , } ; const result = await service [ 'getDataFromSession' ] ( sessionIdMock ) ; expect ( result ) . toEqual ( expectedResult ) ; } ) ; it ( 'should return null values for idp info and spSub if not set in session' , async ( ) => { const expectedResult = { accountId : null , spId : 'spIdMock' , spName : 'spNameMock' , spAcr : 'spAcrMock' , spSub : null , idpId : null , idpName : null , idpAcr : null , idpSub : null , } ; const sessionMock = { spId : 'spIdMock' , spName : 'spNameMock' , spAcr : 'spAcrMock' , } ; const sessionGetBinded = jest . fn ( ) . mockResolvedValue ( sessionMock ) ; sessionServiceMock . get . bind . mockReturnValueOnce ( sessionGetBinded ) ; const result = await service [ 'getDataFromSession' ] ( interactionIdMock ) ; expect ( result ) . toEqual ( expectedResult ) ; } ) ; it ( 'should return null values for `idp` info if not set in session' , async ( ) => { const expectedResult = { accountId : null , spId : 'spIdMock' , spName : 'spNameMock' , spAcr : 'spAcrMock' , spSub : 'spSubMock' , idpId : null , idpName : null , idpAcr : null , idpSub : null , } ; const sessionMock = { spId : 'spIdMock' , spName : 'spNameMock' , spAcr : 'spAcrMock' , spIdentity : { sub : 'spSubMock' } , } ; const sessionGetBinded = jest . fn ( ) . mockResolvedValue ( sessionMock ) ; sessionServiceMock . get . bind . mockReturnValueOnce ( sessionGetBinded ) ; const result = await service [ 'getDataFromSession' ] ( interactionIdMock ) ; expect ( result ) . toEqual ( expectedResult ) ; } ) ; it ( 'should return null values for `sp` info if not set in session' , async ( ) => { const expectedResult = { accountId : null , spId : null , spName : null , spAcr : null , spSub : 'spSubMock' , idpId : null , idpName : null , idpAcr : null , idpSub : null , } ; const sessionMock = { spIdentity : { sub : 'spSubMock' } , } ; const sessionGetBinded = jest . fn ( ) . mockResolvedValue ( sessionMock ) ; sessionServiceMock . get . bind . mockReturnValueOnce ( sessionGetBinded ) ; const result = await service [ 'getDataFromSession' ] ( interactionIdMock ) ; expect ( result ) . toEqual ( expectedResult ) ; } ) ; } ) ; describe ( 'getDataFromContext()' , ( ) => { it ( 'Should return partial data from context and null values' , ( ) => { const expectedResult = { accountId : null , spId : 'spIdMock' , spName : 'spNameMock' , spAcr : 'spAcrMock' , spSub : 'spSubMock' , idpId : null , idpName : null , idpAcr : null , idpSub : null , } ; const myContextMock = { ... contextMock , req : { spId : 'spIdMock' , spName : 'spNameMock' , spAcr : 'spAcrMock' , spSub : 'spSubMock' , } , } ; const result = service [ 'getDataFromContext' ] ( myContextMock ) ; expect ( result ) . toEqual ( expectedResult ) ; } ) ; it ( 'should throw if req is missing' , ( ) => { const contextMock = { } ; expect ( ( ) => service [ 'getDataFromContext' ] ( contextMock ) ) . toThrow ( CoreMissingContextException ) ; } ) ; } ) ; } ) ;