import { ClientMetadata } from 'oidc-provider' ; import { Client , custom , Issuer } from 'openid-client' ; import { Test , TestingModule } from '@nestjs/testing' ; import { LoggerService } from '@fc/logger' ; import { OidcClientProviderDisabledException , OidcClientProviderNotFoundException , } from '../exceptions' ; import { OidcClientConfigService } from './oidc-client-config.service' ; import { OidcClientIssuerService } from './oidc-client-issuer.service' ; describe ( 'OidcClientIssuerService' , ( ) => { let service ; const loggerServiceMock = { setContext : jest . fn ( ) , trace : jest . fn ( ) , debug : jest . fn ( ) , businessEvent : jest . fn ( ) , } as unknown as LoggerService ; const oidcClientConfigServiceMock = { get : jest . fn ( ) , } ; const issuerProxyMock = jest . fn ( ) as unknown as Issuer < Client > ; issuerProxyMock [ 'discover' ] = jest . fn ( ) ; const idpMetadataIssuerMock = { issuer : 'https://corev2.docker.dev-franceconnect.fr' , token_endpoint : 'https://corev2.docker.dev-franceconnect.fr/api/v2/token' , authorization_endpoint : 'https://corev2.docker.dev-franceconnect.fr/api/v2/authorize' , jwks_uri : 'https://corev2.docker.dev-franceconnect.fr/api/v2/certs' , userinfo_endpoint : 'https://corev2.docker.dev-franceconnect.fr/api/v2/userinfo' , end_session_endpoint : 'https://corev2.docker.dev-franceconnect.fr/api/v2/session/end' , } ; const idpMetadataClientMock = { client_id : 'clientID' , client_secret : '7vhnwzo1yUVOJT9GJ91gD5oid56effu1' , post_logout_redirect_uris : [ 'https://corev2.docker.dev-franceconnect.fr/api/v2/logout-from-provider' , ] , redirect_uris : [ 'https://corev2.docker.dev-franceconnect.fr/api/v2/oidc-callback/fip1v2' , ] , response_types : [ 'code' ] , id_token_signed_response_alg : 'HS256' , token_endpoint_auth_method : 'client_secret_post' , revocation_endpoint_auth_method : 'client_secret_post' , id_token_encrypted_response_alg : 'RSA-OAEP' , id_token_encrypted_response_enc : 'A256GCM' , userinfo_encrypted_response_alg : 'RSA-OAEP' , userinfo_encrypted_response_enc : 'A256GCM' , userinfo_signed_response_alg : 'HS256' , } ; const idpMetadataMock = { jwks : [ ] , httpOtions : { } , providers : [ { uid : 'idpUidMock' , name : 'idpNameMock' , redirect_uris : [ 'redirect' , 'uris' ] , response_types : [ 'response' , 'types' ] , discoveryUrl : 'mock well-known url' , } , ] , client : idpMetadataClientMock , issuer : idpMetadataIssuerMock , discovery : true , discoveryUrl : 'mock well-known url' , fapi : false , } ; beforeEach ( async ( ) => { const module = await Test . createTestingModule ( { providers : [ OidcClientIssuerService , LoggerService , OidcClientConfigService , ] , } ) . overrideProvider ( OidcClientConfigService ) . useValue ( oidcClientConfigServiceMock ) . overrideProvider ( LoggerService ) . useValue ( loggerServiceMock ) . compile ( ) ; service = module . get < OidcClientIssuerService > ( OidcClientIssuerService ) ; jest . resetAllMocks ( ) ; service [ 'IssuerProxy' ] = issuerProxyMock as any ; oidcClientConfigServiceMock . get . mockResolvedValue ( idpMetadataMock ) ; } ) ; describe ( 'constructor' , ( ) => { it ( 'should be defined' , ( ) => { expect ( service ) . toBeDefined ( ) ; } ) ; } ) ; describe ( 'getClientClass' , ( ) => { it ( 'should return "Client"' , async ( ) => { oidcClientConfigServiceMock . get . mockResolvedValueOnce ( { fapi : false } ) ; const result = await service [ 'getClientClass' ] ( ) ; expect ( result ) . toBe ( 'Client' ) ; } ) ; it ( 'should return "FAPI1Client"' , async ( ) => { oidcClientConfigServiceMock . get . mockResolvedValueOnce ( { fapi : true } ) ; const result = await service [ 'getClientClass' ] ( ) ; expect ( result ) . toBe ( 'FAPI1Client' ) ; } ) ; } ) ; describe ( 'getClient' , ( ) => { const issuerMock = { Client : jest . fn ( ) , } ; const issuerId = 'foo' ; beforeEach ( ( ) => { service [ 'getIdpMetadata' ] = jest . fn ( ) . mockResolvedValue ( idpMetadataMock ) ; service [ 'getIssuer' ] = jest . fn ( ) . mockResolvedValue ( issuerMock ) ; } ) ; it ( 'should call getIssuer' , async ( ) => { await service . getClient ( issuerId ) ; expect ( service [ 'getIssuer' ] ) . toHaveBeenCalledTimes ( 1 ) ; expect ( service [ 'getIssuer' ] ) . toHaveBeenCalledWith ( issuerId ) ; } ) ; it ( 'should instantiate new client' , async ( ) => { await service . getClient ( issuerId ) ; expect ( issuerMock . Client ) . toHaveBeenCalledTimes ( 1 ) ; expect ( issuerMock . Client ) . toHaveBeenCalledWith ( idpMetadataClientMock , idpMetadataMock . jwks ) ; } ) ; it ( 'should call config' , async ( ) => { service [ 'getClientClass' ] = jest . fn ( ) . mockResolvedValue ( 'Client' ) ; await service . getClient ( issuerId ) ; expect ( oidcClientConfigServiceMock . get ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'should call getClientClass' , async ( ) => { service [ 'getClientClass' ] = jest . fn ( ) . mockResolvedValue ( 'Client' ) ; await service . getClient ( issuerId ) ; expect ( service [ 'getClientClass' ] ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'should return created client instance' , async ( ) => { const clientInstanceMock = { } ; issuerMock . Client . mockReturnValue ( clientInstanceMock ) ; const result = await service . getClient ( issuerId ) ; expect ( result ) . toBe ( clientInstanceMock ) ; } ) ; it ( 'should set httpOptions on client' , async ( ) => { const clientInstanceMock = { } ; issuerMock . Client . mockReturnValue ( clientInstanceMock ) ; const getHttpOptionsReturnValue = Symbol ( 'getHttpOptionsReturnValue' ) ; service [ 'getHttpOptions' ] = jest . fn ( ) . mockReturnValue ( getHttpOptionsReturnValue ) ; const options = { } ; const client = await service . getClient ( issuerId ) ; const result = client [ custom . http_options ] ( { } as URL , options ) ; expect ( result ) . toBe ( getHttpOptionsReturnValue ) ; } ) ; } ) ; describe ( 'getHttpOptions' , ( ) => { it ( 'should return fusion from config and input' , ( ) => { const givenOptions = { auth : 'bar' } ; const configOptions = { servername : 'buzz' } ; const result = service [ 'getHttpOptions' ] ( configOptions , { } as URL , givenOptions ) ; expect ( result ) . toEqual ( { auth : 'bar' , servername : 'buzz' , } ) ; } ) ; } ) ; describe ( 'getIssuer' , ( ) => { beforeEach ( ( ) => { service [ 'getIdpMetadata' ] = jest . fn ( ) . mockResolvedValue ( idpMetadataMock ) ; } ) ; it ( 'should call getIdpMetadata' , async ( ) => { const issuerId = 'foo' ; await service [ 'getIssuer' ] ( issuerId ) ; expect ( service [ 'getIdpMetadata' ] ) . toHaveBeenCalledTimes ( 1 ) ; expect ( service [ 'getIdpMetadata' ] ) . toHaveBeenCalledWith ( issuerId ) ; } ) ; it ( 'should call IssuerProxy.discover' , async ( ) => { const issuerId = 'foo' ; await service [ 'getIssuer' ] ( issuerId ) ; expect ( issuerProxyMock . discover ) . toHaveBeenCalledTimes ( 1 ) ; expect ( issuerProxyMock . discover ) . toHaveBeenCalledWith ( idpMetadataMock . discoveryUrl ) ; } ) ; it ( 'should instantiate IssuerProxy' , async ( ) => { const issuerId = 'foo' ; const noDiscoveryMetadata = { ... idpMetadataMock , discovery : false , } ; service [ 'getIdpMetadata' ] = jest . fn ( ) . mockResolvedValue ( noDiscoveryMetadata ) ; await service [ 'getIssuer' ] ( issuerId ) ; expect ( service [ 'IssuerProxy' ] ) . toHaveBeenCalledTimes ( 1 ) ; expect ( service [ 'IssuerProxy' ] ) . toHaveBeenCalledWith ( idpMetadataIssuerMock ) ; } ) ; } ) ; describe ( 'getIdpMetadata' , ( ) => { const providerMock1 = { name : 'provider1' , uid : 'p1' , active : true , } as unknown as ClientMetadata ; const providerMock2 = { name : 'provider2' , uid : 'p2' , active : true , } as unknown as ClientMetadata ; const providerMock3 = { name : 'provider3' , uid : 'p3' , active : false , } as unknown as ClientMetadata ; const providers = [ providerMock1 , providerMock2 , providerMock3 ] ; it ( 'should return provider in config' , async ( ) => { oidcClientConfigServiceMock . get . mockResolvedValue ( { providers } ) ; const result = await service [ 'getIdpMetadata' ] ( 'p2' ) ; expect ( result ) . toBe ( providerMock2 ) ; } ) ; it ( 'should throw if provider is not in config' , async ( ) => { oidcClientConfigServiceMock . get . mockResolvedValue ( { providers } ) ; expect ( service [ 'getIdpMetadata' ] ( 'p0' ) ) . rejects . toThrow ( OidcClientProviderNotFoundException ) ; } ) ; it ( 'should throw if provider is not active' , async ( ) => { oidcClientConfigServiceMock . get . mockResolvedValue ( { providers } ) ; expect ( service [ 'getIdpMetadata' ] ( 'p3' ) ) . rejects . toThrow ( OidcClientProviderDisabledException ) ; } ) ; } ) ; } ) ;