import { Test , TestingModule } from '@nestjs/testing' ; import { ConfigService } from '@fc/config' ; import { CryptographyService } from '@fc/cryptography' ; import { LoggerService } from '@fc/logger' ; import { IdentityProviderMetadata } from '@fc/oidc' ; import { IdentityProvider } from './enums' ; import { IdentityProviderAdapterEnvService } from './identity-provider-adapter-env.service' ; describe ( 'IdentityProviderAdapterEnvService' , ( ) => { let service ; const env = { provider : { client_id : 'client_id' , client_secret : '7vhnwzo1yUVOJT9GJ91gD5oid56effu1' , discovery : true , discoveryUrl : 'https://core-fcp-high.docker.dev-franceconnect.fr/api/v2/.well-known/openid-configuration' , id_token_signed_response_alg : 'ES256' , post_logout_redirect_uris : [ 'https://fsp1-high.docker.dev-franceconnect.fr/logout-callback' , ] , redirect_uris : [ 'https://fsp1-high.docker.dev-franceconnect.fr/login-callback' , ] , response_types : [ 'code' ] , 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 : 'ES256' , jwks_uri : 'https://fsp1-high.docker.dev-franceconnect.fr/jwks_uri' , } , } ; const validIdentityProviderMock = { uid : IdentityProvider . IDP_ID , name : 'envIssuer' , title : 'envIssuer Title' , active : true , display : true , client_id : 'client_id' , client_secret : '7vhnwzo1yUVOJT9GJ91gD5oid56effu1' , discovery : true , discoveryUrl : 'https://core-fcp-high.docker.dev-franceconnect.fr/api/v2/.well-known/openid-configuration' , id_token_signed_response_alg : 'ES256' , post_logout_redirect_uris : [ 'https://fsp1-high.docker.dev-franceconnect.fr/logout-callback' , ] , redirect_uris : [ 'https://fsp1-high.docker.dev-franceconnect.fr/login-callback' , ] , response_types : [ 'code' ] , 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 : 'ES256' , jwks_uri : 'https://fsp1-high.docker.dev-franceconnect.fr/jwks_uri' , } ; const toPanvaFormatMock = { uid : 'envIssuer' , title : 'envIssuer Title' , name : 'envIssuer' , display : true , active : true , discovery : true , discoveryUrl : 'https://core-fcp-high.docker.dev-franceconnect.fr/api/v2/.well-known/openid-configuration' , issuer : { jwks_uri : 'https://fsp1-high.docker.dev-franceconnect.fr/jwks_uri' , } , client : { client_id : 'client_id' , client_secret : '7vhnwzo1yUVOJT9GJ91gD5oid56effu1' , id_token_signed_response_alg : 'ES256' , post_logout_redirect_uris : [ 'https://fsp1-high.docker.dev-franceconnect.fr/logout-callback' , ] , redirect_uris : [ 'https://fsp1-high.docker.dev-franceconnect.fr/login-callback' , ] , response_types : [ 'code' ] , 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 : 'ES256' , } , } ; const identityProviderListMock = [ validIdentityProviderMock ] ; const loggerMock = { setContext : jest . fn ( ) , warn : jest . fn ( ) , } ; const cryptographyMock = { decrypt : jest . fn ( ) , } ; const configMock = { get : jest . fn ( ) , } ; beforeEach ( async ( ) => { jest . clearAllMocks ( ) ; jest . resetAllMocks ( ) ; jest . restoreAllMocks ( ) ; const module = await Test . createTestingModule ( { providers : [ CryptographyService , IdentityProviderAdapterEnvService , LoggerService , ConfigService , ] , } ) . overrideProvider ( CryptographyService ) . useValue ( cryptographyMock ) . overrideProvider ( LoggerService ) . useValue ( loggerMock ) . overrideProvider ( ConfigService ) . useValue ( configMock ) . compile ( ) ; service = module . get < IdentityProviderAdapterEnvService > ( IdentityProviderAdapterEnvService ) ; configMock . get . mockReturnValue ( { provider : env . provider , } ) ; } ) ; it ( 'should be defined' , ( ) => { expect ( service ) . toBeDefined ( ) ; } ) ; describe ( 'legacyToOpenIdPropertyName' , ( ) => { it ( 'should return identity provider with change legacy property name by openid property name' , ( ) => { service [ 'decryptClientSecret' ] = jest . fn ( ) . mockReturnValueOnce ( toPanvaFormatMock . client . client_secret ) ; const result = service [ 'legacyToOpenIdPropertyName' ] ( validIdentityProviderMock ) ; expect ( result ) . toEqual ( toPanvaFormatMock ) ; } ) ; } ) ; describe ( 'findAllIdentityProvider' , ( ) => { it ( 'should resolve' , async ( ) => { const result = service [ 'findAllIdentityProvider' ] ( ) ; expect ( result ) . toBeInstanceOf ( Promise ) ; } ) ; it ( 'should return result of type list' , async ( ) => { configMock . get . mockReturnValueOnce ( env ) ; const result = await service [ 'findAllIdentityProvider' ] ( ) ; expect ( result ) . toEqual ( identityProviderListMock ) ; } ) ; it ( 'should log a warning if an entry is not validated by the DTO' , async ( ) => { const invalidEnvMock = { ... env . provider , jwks_uri : 'not an url' , } ; configMock . get . mockReturnValueOnce ( { provider : invalidEnvMock } ) ; await service [ 'findAllIdentityProvider' ] ( ) ; expect ( loggerMock . warn ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'should log a warning if an entry is exluded by the DTO' , async ( ) => { const invalidEnvMock = { ... env . provider , not_validated : 'by DTO' , } ; configMock . get . mockReturnValueOnce ( { provider : invalidEnvMock , } ) ; await service [ 'findAllIdentityProvider' ] ( ) ; expect ( loggerMock . warn ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; } ) ; describe ( 'getList' , ( ) => { it ( 'should resolve' , async ( ) => { const result = service . getList ( ) ; expect ( result ) . toBeInstanceOf ( Promise ) ; } ) ; it ( 'should return a list of valids identity providers' , async ( ) => { configMock . get . mockReturnValueOnce ( env ) ; const expected = toPanvaFormatMock ; service [ 'decryptClientSecret' ] = jest . fn ( ) . mockReturnValueOnce ( expected . client . client_secret ) ; const result = await service . getList ( ) ; expect ( result ) . toEqual ( [ expected ] ) ; } ) ; it ( 'should call findAll method if refreshCache is true' , async ( ) => { const refresh = true ; const listMock = [ { client_id : 'foo' , } , { client_id : 'bar' , } , ] ; service [ 'findAllIdentityProvider' ] = jest . fn ( ) . mockResolvedValue ( listMock ) ; service [ 'legacyToOpenIdPropertyName' ] = jest . fn ( ) . mockImplementation ( ( input ) => input ) ; const result = await service . getList ( refresh ) ; expect ( service [ 'findAllIdentityProvider' ] ) . toHaveBeenCalledTimes ( 1 ) ; expect ( service [ 'findAllIdentityProvider' ] ) . toHaveBeenCalledWith ( ) ; expect ( service [ 'legacyToOpenIdPropertyName' ] ) . toHaveBeenCalledTimes ( listMock . length ) ; expect ( result ) . toEqual ( listMock ) ; } ) ; it ( 'should not call findAll method if refreshCache is not set and cache exists' , async ( ) => { service [ 'identityProviderCache' ] = [ { client : { client_id : 'foo' , } , } as IdentityProviderMetadata , { client : { client_id : 'bar' , } , } as IdentityProviderMetadata , ] ; service [ 'findAllIdentityProvider' ] = jest . fn ( ) ; const result = await service . getList ( ) ; expect ( result ) . toBe ( service [ 'identityProviderCache' ] ) ; expect ( service [ 'findAllIdentityProvider' ] ) . toHaveBeenCalledTimes ( 0 ) ; } ) ; } ) ; describe ( 'getFilteredList' , ( ) => { beforeEach ( ( ) => { service [ 'getList' ] = jest . fn ( ) . mockResolvedValueOnce ( identityProviderListMock ) ; } ) ; it ( 'should resolve' , async ( ) => { const result = service . getFilteredList ( [ ] , true ) ; expect ( result ) . toBeInstanceOf ( Promise ) ; } ) ; it ( 'should return a list of filtered whitelist identity providers' , async ( ) => { const expected = [ { ... validIdentityProviderMock , } , ] ; service [ 'decryptClientSecret' ] = jest . fn ( ) . mockReturnValueOnce ( expected [ 0 ] . client_secret ) ; const result = await service . getFilteredList ( [ 'envIssuer' ] , false ) ; expect ( result ) . toEqual ( expected ) ; } ) ; it ( 'should return an empty list of filtered whitelist identity providers' , async ( ) => { const expected = [ { ... validIdentityProviderMock , } , ] ; cryptographyMock . decrypt . mockReturnValueOnce ( expected [ 0 ] . client_secret ) ; const result = await service . getFilteredList ( [ 'false_uid' ] , false ) ; expect ( result ) . toEqual ( [ ] ) ; } ) ; it ( 'should return an empty list of filtered blacklist identity providers' , async ( ) => { const result = await service . getFilteredList ( [ 'envIssuer' ] , true ) ; expect ( result ) . toEqual ( [ ] ) ; } ) ; it ( 'should return a list of filtered blacklist identity providers' , async ( ) => { const expected = [ { ... validIdentityProviderMock , } , ] ; service [ 'decryptClientSecret' ] = jest . fn ( ) . mockReturnValueOnce ( expected [ 0 ] . client_secret ) ; const result = await service . getFilteredList ( [ 'false_uid' ] , true ) ; expect ( result ) . toEqual ( expected ) ; } ) ; } ) ; describe ( 'getById' , ( ) => { const idpListMock = [ { uid : 'wizz' } , { uid : 'foo' } , { uid : 'bar' } ] ; it ( 'should return an existing IdP' , async ( ) => { const idMock = 'foo' ; service . getList = jest . fn ( ) . mockResolvedValueOnce ( idpListMock ) ; const result = await service . getById ( idMock ) ; expect ( result ) . toEqual ( { uid : 'foo' } ) ; } ) ; it ( 'should return undefined for non existing IdP' , async ( ) => { const idMock = 'nope' ; service . getList = jest . fn ( ) . mockResolvedValueOnce ( idpListMock ) ; const result = await service . getById ( idMock ) ; expect ( result ) . toBeUndefined ( ) ; } ) ; it ( 'should pass refresh flag to getList method' , async ( ) => { const idMock = 'foo' ; const refresh = true ; service . getList = jest . fn ( ) . mockResolvedValueOnce ( idpListMock ) ; await service . getById ( idMock , refresh ) ; expect ( service . getList ) . toHaveBeenCalledTimes ( 1 ) ; expect ( service . getList ) . toHaveBeenCalledWith ( refresh ) ; } ) ; } ) ; describe ( 'decryptClientSecret' , ( ) => { it ( 'should get clientSecretEncryptKey from config' , ( ) => { const clientSecretMock = 'some string' ; const clientSecretEncryptKey = 'Key' ; configMock . get . mockReturnValue ( { clientSecretEncryptKey } ) ; service [ 'decryptClientSecret' ] ( clientSecretMock ) ; expect ( configMock . get ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'should call decrypt with enc key from config' , ( ) => { const clientSecretMock = 'some string' ; const clientSecretEncryptKey = 'Key' ; configMock . get . mockReturnValue ( { clientSecretEncryptKey } ) ; cryptographyMock . decrypt . mockReturnValue ( 'totoIsDecrypted' ) ; service [ 'decryptClientSecret' ] ( clientSecretMock ) ; expect ( cryptographyMock . decrypt ) . toHaveBeenCalledTimes ( 1 ) ; expect ( cryptographyMock . decrypt ) . toHaveBeenCalledWith ( clientSecretEncryptKey , Buffer . from ( clientSecretMock , 'base64' ) ) ; } ) ; it ( 'should return clientSecretEncryptKey' , ( ) => { const clientSecretMock = 'some string' ; const clientSecretEncryptKey = 'Key' ; configMock . get . mockReturnValue ( { clientSecretEncryptKey } ) ; cryptographyMock . decrypt . mockReturnValue ( 'totoIsDecrypted' ) ; const result = service [ 'decryptClientSecret' ] ( clientSecretMock ) ; expect ( result ) . toEqual ( 'totoIsDecrypted' ) ; } ) ; } ) ; } ) ;