import { mocked } from 'jest-mock' ; import { encode } from 'querystring' ; import { Test , TestingModule } from '@nestjs/testing' ; import { ConfigService } from '@fc/config' ; import { CryptographyService } from '@fc/cryptography' ; import { IdentityProviderAdapterEnvService } from '@fc/identity-provider-adapter-env' ; import { LoggerService } from '@fc/logger' ; import { OidcClientService , OidcClientSession } from '@fc/oidc-client' ; import { SessionCsrfService , SessionNotFoundException , SessionService , } from '@fc/session' ; import { ExploitationController } from './exploitation.controller' ; jest . mock ( 'querystring' , ( ) => ( { encode : jest . fn ( ) , } ) ) ; describe ( 'ExploitationController' , ( ) => { let controller ; const configServiceMock = { get : jest . fn ( ) , } ; const sessionServiceMock = { get : jest . fn ( ) , set : jest . fn ( ) , reset : jest . fn ( ) , } ; const cryptographyServiceMock = { genRandomString : jest . fn ( ) , } ; const oidcClientServiceMock = { getTokenFromProvider : jest . fn ( ) , getUserInfosFromProvider : jest . fn ( ) , getEndSessionUrlFromProvider : jest . fn ( ) , utils : { buildAuthorizeParameters : jest . fn ( ) , getAuthorizeUrl : jest . fn ( ) , } , } ; const loggerServiceMock = { setContext : jest . fn ( ) , trace : jest . fn ( ) , } ; const identityProviderAdapterEnvService = { getList : jest . fn ( ) , } ; const sessionOidcMock = { get : jest . fn ( ) , set : jest . fn ( ) , } ; const sessionCsrfServiceMock = { get : jest . fn ( ) , save : jest . fn ( ) , validate : jest . fn ( ) , } ; const reqMock = { foo : 'bar' , query : { firstQueryParam : 'first' , secondQueryParam : 'second' , } , params : { providerUid : 'secretProviderUid' , } , } ; const resMock = { redirect : jest . fn ( ) , } ; const queryStringEncodeMock = mocked ( encode ) ; beforeEach ( async ( ) => { jest . resetAllMocks ( ) ; const app = await Test . createTestingModule ( { controllers : [ ExploitationController ] , providers : [ ConfigService , CryptographyService , OidcClientService , LoggerService , IdentityProviderAdapterEnvService , SessionService , SessionCsrfService , ] , } ) . overrideProvider ( ConfigService ) . useValue ( configServiceMock ) . overrideProvider ( CryptographyService ) . useValue ( cryptographyServiceMock ) . overrideProvider ( OidcClientService ) . useValue ( oidcClientServiceMock ) . overrideProvider ( LoggerService ) . useValue ( loggerServiceMock ) . overrideProvider ( IdentityProviderAdapterEnvService ) . useValue ( identityProviderAdapterEnvService ) . overrideProvider ( SessionService ) . useValue ( sessionServiceMock ) . overrideProvider ( SessionCsrfService ) . useValue ( sessionCsrfServiceMock ) . compile ( ) ; controller = app . get < ExploitationController > ( ExploitationController ) ; } ) ; it ( 'should be defined' , ( ) => { expect ( controller ) . toBeDefined ( ) ; } ) ; it ( 'should set the logger context' , ( ) => { expect ( loggerServiceMock . setContext ) . toHaveBeenCalledTimes ( 1 ) ; expect ( loggerServiceMock . setContext ) . toHaveBeenCalledWith ( 'ExploitationController' ) ; } ) ; describe ( 'getAuthorizeUrl' , ( ) => { const providerMock = { uid : 'uid' , } ; const oidcClientConfigMock = { acr : 'acr' , claims : 'claims' , scope : 'scope' , } ; const authorizeParametersMock = { nonce : 'nonce' , state : 'state' , } ; beforeEach ( ( ) => { identityProviderAdapterEnvService . getList . mockResolvedValueOnce ( [ providerMock , ] ) ; configServiceMock . get . mockReturnValueOnce ( oidcClientConfigMock ) ; oidcClientServiceMock . utils . buildAuthorizeParameters . mockReturnValueOnce ( authorizeParametersMock ) ; } ) ; it ( 'should retrieve the identity provider informations' , async ( ) => { await controller . getAuthorizeUrl ( sessionOidcMock ) ; expect ( identityProviderAdapterEnvService . getList ) . toHaveBeenCalledTimes ( 1 ) ; expect ( identityProviderAdapterEnvService . getList ) . toHaveBeenCalledWith ( ) ; } ) ; it ( 'should retrieve the oidc client config' , async ( ) => { await controller . getAuthorizeUrl ( sessionOidcMock ) ; expect ( configServiceMock . get ) . toHaveBeenCalledTimes ( 1 ) ; expect ( configServiceMock . get ) . toHaveBeenCalledWith ( 'OidcClient' ) ; } ) ; it ( 'should build the authorize parameters' , async ( ) => { await controller . getAuthorizeUrl ( sessionOidcMock ) ; expect ( oidcClientServiceMock . utils . buildAuthorizeParameters ) . toHaveBeenCalledTimes ( 1 ) ; expect ( oidcClientServiceMock . utils . buildAuthorizeParameters ) . toHaveBeenCalledWith ( ) ; } ) ; it ( 'should build the authorize url with the given parameters' , async ( ) => { const expectedParameters = { acr_values : oidcClientConfigMock . acr , claims : oidcClientConfigMock . claims , nonce : authorizeParametersMock . nonce , idpId : providerMock . uid , scope : oidcClientConfigMock . scope , state : authorizeParametersMock . state , } ; await controller . getAuthorizeUrl ( sessionOidcMock ) ; expect ( oidcClientServiceMock . utils . getAuthorizeUrl ) . toHaveBeenCalledTimes ( 1 ) ; expect ( oidcClientServiceMock . utils . getAuthorizeUrl ) . toHaveBeenCalledWith ( expectedParameters ) ; } ) ; it ( 'should save the state and nonce in the oidc session' , async ( ) => { const expectedSessionSet = { idpId : 'uid' , idpState : authorizeParametersMock . state , idpNonce : authorizeParametersMock . nonce , } ; await controller . getAuthorizeUrl ( sessionOidcMock ) ; expect ( sessionOidcMock . set ) . toHaveBeenCalledTimes ( 1 ) ; expect ( sessionOidcMock . set ) . toHaveBeenCalledWith ( expectedSessionSet ) ; } ) ; } ) ; describe ( 'getOidcCallback' , ( ) => { const reqMock = { foo : 'bar' , } ; const resMock = { redirect : jest . fn ( ) , } ; const idpIdMock = 'idpIdMockValue' ; const oidcSessionMock = { idpId : idpIdMock , idpState : 'idpState' , idpNonce : 'idpNonce' , } ; const tokenFromProviderMock = { accessToken : 'accessToken' , idToken : 'idToken' , acr : 'acr' , amr : 'amr' , } ; const userInfosMock = { sub : 'sub' , email : 'email' , } ; beforeEach ( ( ) => { sessionOidcMock . get . mockResolvedValueOnce ( oidcSessionMock ) ; oidcClientServiceMock . getTokenFromProvider . mockResolvedValueOnce ( tokenFromProviderMock ) ; oidcClientServiceMock . getUserInfosFromProvider . mockResolvedValueOnce ( userInfosMock ) ; } ) ; it ( 'should redirect to login with an error if the query contains an OpenId error' , async ( ) => { const queryMock = { error : 'error' , error_description : 'error_description' , } ; const loginQueryMock = 'error=${queryMock.error}&error_description=${queryMock.error_description}' ; queryStringEncodeMock . mockReturnValueOnce ( loginQueryMock ) ; const expectedLoginUriMock = `/login?${ loginQueryMock }` ; await controller . getOidcCallback ( reqMock , resMock , queryMock , sessionOidcMock ) ; expect ( resMock . redirect ) . toHaveBeenCalledTimes ( 1 ) ; expect ( resMock . redirect ) . toHaveBeenCalledWith ( expectedLoginUriMock ) ; } ) ; it ( 'should throw an error if no session is found' , async ( ) => { const queryMock = { code : 'code' , } ; sessionOidcMock . get . mockReset ( ) . mockResolvedValueOnce ( undefined ) ; await expect ( ( ) => controller . getOidcCallback ( reqMock , resMock , queryMock , sessionOidcMock ) ) . rejects . toThrow ( SessionNotFoundException ) ; } ) ; it ( 'should retrieve the token from the provider' , async ( ) => { const queryMock = { code : 'code' , } ; await controller . getOidcCallback ( reqMock , resMock , queryMock , sessionOidcMock ) ; expect ( oidcClientServiceMock . getTokenFromProvider ) . toHaveBeenCalledTimes ( 1 ) ; expect ( oidcClientServiceMock . getTokenFromProvider ) . toHaveBeenCalledWith ( idpIdMock , { state : oidcSessionMock . idpState , nonce : oidcSessionMock . idpNonce , } , reqMock ) ; } ) ; it ( 'should retrieve the user infos from the provider' , async ( ) => { const queryMock = { code : 'code' , } ; await controller . getOidcCallback ( reqMock , resMock , queryMock , sessionOidcMock ) ; expect ( oidcClientServiceMock . getUserInfosFromProvider ) . toHaveBeenCalledTimes ( 1 ) ; expect ( oidcClientServiceMock . getUserInfosFromProvider ) . toHaveBeenCalledWith ( { accessToken : tokenFromProviderMock . accessToken , idpId : idpIdMock , } , reqMock ) ; } ) ; it ( 'should save the identity and cinematic informations to the oidc session' , async ( ) => { const queryMock = { code : 'code' , } ; await controller . getOidcCallback ( reqMock , resMock , queryMock , sessionOidcMock ) ; expect ( sessionOidcMock . set ) . toHaveBeenCalledTimes ( 1 ) ; expect ( sessionOidcMock . set ) . toHaveBeenCalledWith ( { idpIdentity : userInfosMock , idpAcr : tokenFromProviderMock . acr , amr : tokenFromProviderMock . amr , idpAccessToken : tokenFromProviderMock . accessToken , idpIdToken : tokenFromProviderMock . idToken , } ) ; } ) ; it ( 'should redirect to the home page' , async ( ) => { const queryMock = { code : 'code' , } ; await controller . getOidcCallback ( reqMock , resMock , queryMock , sessionOidcMock ) ; expect ( resMock . redirect ) . toHaveBeenCalledTimes ( 1 ) ; expect ( resMock . redirect ) . toHaveBeenCalledWith ( '/' ) ; } ) ; } ) ; describe ( 'loadUserInfos' , ( ) => { const idpIdentityMock = { sub : 'sub' , email : 'email' , } ; beforeEach ( ( ) => { jest . resetAllMocks ( ) ; } ) ; it ( 'should retrieve the idp identity from the oidc session' , async ( ) => { sessionOidcMock . get . mockResolvedValueOnce ( idpIdentityMock as OidcClientSession ) ; await controller . loadUserInfos ( sessionOidcMock ) ; expect ( sessionOidcMock . get ) . toHaveBeenCalledTimes ( 1 ) ; expect ( sessionOidcMock . get ) . toHaveBeenCalledWith ( 'idpIdentity' ) ; } ) ; it ( 'should return the connection status of the user if connected' , async ( ) => { sessionOidcMock . get . mockResolvedValueOnce ( idpIdentityMock ) ; const expectedStatus = { connected : true , userinfos : idpIdentityMock , } ; const result = await controller . loadUserInfos ( sessionOidcMock ) ; expect ( result ) . toStrictEqual ( expectedStatus ) ; } ) ; it ( 'should return the connection status of the user if not connected' , async ( ) => { const expectedStatus = { connected : false , userinfos : undefined , } ; const result = await controller . loadUserInfos ( sessionOidcMock ) ; expect ( result ) . toStrictEqual ( expectedStatus ) ; } ) ; } ) ; describe ( 'getEndSessionUrl' , ( ) => { it ( 'should build the authorize url with the given parameters (without postLogoutRedirectUri)' , async ( ) => { const sessionMock = { idpIdToken : 'idpIdToken' , idpState : 'idspState' , idpId : 'idpId' , } ; sessionOidcMock . get . mockResolvedValueOnce ( sessionMock ) ; oidcClientServiceMock . getEndSessionUrlFromProvider . mockResolvedValueOnce ( 'http://foo.bar' ) ; const result = await controller . getEndSessionUrl ( sessionOidcMock ) ; expect ( sessionOidcMock . get ) . toHaveBeenCalledTimes ( 1 ) ; expect ( oidcClientServiceMock . getEndSessionUrlFromProvider ) . toHaveBeenCalledTimes ( 1 ) ; expect ( oidcClientServiceMock . getEndSessionUrlFromProvider ) . toHaveBeenCalledWith ( 'idpId' , 'idspState' , 'idpIdToken' , undefined ) ; expect ( result ) . toStrictEqual ( 'http://foo.bar' ) ; } ) ; it ( 'should build the authorize url with the given parameters (with postLogoutRedirectUri)' , async ( ) => { const postLogoutRedirectUri = 'postLogoutRedirectUri' ; const sessionMock = { idpIdToken : 'idpIdToken' , idpState : 'idspState' , idpId : 'idpId' , } ; sessionOidcMock . get . mockResolvedValueOnce ( sessionMock ) ; oidcClientServiceMock . getEndSessionUrlFromProvider . mockResolvedValueOnce ( 'http://foo.bar' ) ; const result = await controller . getEndSessionUrl ( sessionOidcMock , postLogoutRedirectUri ) ; expect ( sessionOidcMock . get ) . toHaveBeenCalledTimes ( 1 ) ; expect ( oidcClientServiceMock . getEndSessionUrlFromProvider ) . toHaveBeenCalledTimes ( 1 ) ; expect ( oidcClientServiceMock . getEndSessionUrlFromProvider ) . toHaveBeenCalledWith ( 'idpId' , 'idspState' , 'idpIdToken' , 'postLogoutRedirectUri' ) ; expect ( result ) . toStrictEqual ( 'http://foo.bar' ) ; } ) ; } ) ; describe ( 'logoutCallback' , ( ) => { it ( 'should redirect to the home page' , async ( ) => { await controller . logoutCallback ( reqMock , resMock ) ; expect ( loggerServiceMock . trace ) . toHaveBeenCalledTimes ( 1 ) ; expect ( sessionServiceMock . reset ) . toHaveBeenCalledTimes ( 1 ) ; expect ( resMock . redirect ) . toHaveBeenCalledTimes ( 1 ) ; expect ( resMock . redirect ) . toHaveBeenCalledWith ( '/' ) ; } ) ; } ) ; describe ( 'getCsrfToken' , ( ) => { const csrfTokenMock = 'csrfTokenMock' ; beforeEach ( ( ) => { sessionCsrfServiceMock . get . mockReturnValueOnce ( csrfTokenMock ) ; } ) ; it ( 'should call csrfService.get' , async ( ) => { await controller . getCsrfToken ( sessionServiceMock ) ; expect ( sessionCsrfServiceMock . get ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'should call csrfService.save' , async ( ) => { await controller . getCsrfToken ( sessionServiceMock ) ; expect ( sessionCsrfServiceMock . save ) . toHaveBeenCalledTimes ( 1 ) ; expect ( sessionCsrfServiceMock . save ) . toHaveBeenCalledWith ( sessionServiceMock , csrfTokenMock ) ; } ) ; it ( 'should return csrfToken' , async ( ) => { const result = await controller . getCsrfToken ( sessionServiceMock ) ; expect ( result ) . toEqual ( { csrfToken : csrfTokenMock } ) ; } ) ; } ) ; } ) ;