import { mocked } from 'jest-mock' ; import { Test , TestingModule } from '@nestjs/testing' ; import { PartialExcept } from '@fc/common' ; import { ConfigService } from '@fc/config' ; import { LoggerService } from '@fc/logger' ; import { IOidcIdentity , OidcSession } from '@fc/oidc' ; import { TemplateNotFoundException } from '../exceptions' ; import { StdoutTransport } from '../transports' ; import { MailerService } from './mailer.service' ; import { SmtpService } from './smtp.service' ; import { TemplateService } from './template.service' ; jest . mock ( '../transports' ) ; describe ( 'MailerService' , ( ) => { let service ; const transportSendMock = jest . fn ( ) ; const emailParamsMock = { subject : 'subject' , body : 'body' , from : { email : 'from.email@fqdn.ext' , name : 'from.name' , } , to : [ { email : 'recipient_1@fqdn.ext' , name : 'recipient_1' , } , { email : 'recipient_2@fqdn.ext' , name : 'recipient_2' , } , ] , } ; const configServiceMock = { get : jest . fn ( ) } ; const loggerServiceMock = { setContext : jest . fn ( ) , debug : jest . fn ( ) , error : jest . fn ( ) , trace : jest . fn ( ) , } ; const templateServiceMock = { readFile : jest . fn ( ) , render : jest . fn ( ) , getFilePath : jest . fn ( ) , } ; const smtpServiceMock = { send : jest . fn ( ) , } ; const StdoutTransportMock = mocked ( StdoutTransport ) ; beforeEach ( async ( ) => { jest . resetAllMocks ( ) ; jest . restoreAllMocks ( ) ; const module = await Test . createTestingModule ( { providers : [ ConfigService , LoggerService , MailerService , TemplateService , SmtpService , ] , } ) . overrideProvider ( ConfigService ) . useValue ( configServiceMock ) . overrideProvider ( LoggerService ) . useValue ( loggerServiceMock ) . overrideProvider ( TemplateService ) . useValue ( templateServiceMock ) . overrideProvider ( SmtpService ) . useValue ( smtpServiceMock ) . compile ( ) ; service = module . get < MailerService > ( MailerService ) ; } ) ; it ( 'should be defined' , ( ) => { expect ( service ) . toBeDefined ( ) ; } ) ; it ( 'should set the logger context' , ( ) => { const constructorName = 'MailerService' ; expect ( loggerServiceMock . setContext ) . toBeCalledTimes ( 1 ) ; expect ( loggerServiceMock . setContext ) . toBeCalledWith ( constructorName ) ; } ) ; describe ( 'onModuleInit' , ( ) => { it ( 'should get the mailer mode from the config' , ( ) => { const configName = 'Mailer' ; const configMock = { transport : 'logs' } ; configServiceMock . get . mockReturnValueOnce ( configMock ) ; service . onModuleInit ( ) ; expect ( configServiceMock . get ) . toBeCalledTimes ( 1 ) ; expect ( configServiceMock . get ) . toBeCalledWith ( configName ) ; } ) ; it ( 'should create instance of the StdoutTransport with the logger instance if mailer is "logs"' , ( ) => { const configMock = { transport : 'logs' } ; configServiceMock . get . mockReturnValueOnce ( configMock ) ; service . onModuleInit ( ) ; expect ( StdoutTransportMock ) . toBeCalledTimes ( 1 ) ; expect ( StdoutTransportMock ) . toBeCalledWith ( loggerServiceMock ) ; } ) ; it ( 'should set the transport to SMPT if MailerConfig equal "smtp"' , ( ) => { const configMock = { transport : 'smtp' } ; configServiceMock . get . mockReturnValueOnce ( configMock ) ; service . onModuleInit ( ) ; expect ( service [ 'transport' ] ) . toBe ( smtpServiceMock ) ; } ) ; it ( 'should throw an error if mailer is unknown' , ( ) => { const configMock = { transport : 'pouet' } ; const error = new Error ( 'Invalid mailer "pouet"' ) ; configServiceMock . get . mockReturnValueOnce ( configMock ) ; try { service . onModuleInit ( ) ; } catch ( e ) { expect ( StdoutTransportMock ) . toBeCalledTimes ( 0 ) ; expect ( e ) . toBeInstanceOf ( Error ) ; expect ( e . message ) . toStrictEqual ( error . message ) ; } } ) ; } ) ; describe ( 'send' , ( ) => { beforeEach ( ( ) => { jest . clearAllMocks ( ) ; service [ 'transport' ] = { send : transportSendMock } ; } ) ; it ( 'should return a promise' , async ( ) => { const result = service . send ( emailParamsMock ) ; expect ( result ) . toBeInstanceOf ( Promise ) ; } ) ; it ( 'should call the transport "send" function with the mail parameters' , async ( ) => { await service . send ( emailParamsMock ) ; expect ( service [ 'transport' ] . send ) . toHaveBeenCalledTimes ( 1 ) ; expect ( service [ 'transport' ] . send ) . toHaveBeenCalledWith ( emailParamsMock ) ; } ) ; it ( 'should log an error if the transport throws' , async ( ) => { const error = Error ( 'oops' ) ; transportSendMock . mockImplementation ( ( ) => { throw error ; } ) ; await service . send ( emailParamsMock ) ; expect ( loggerServiceMock . error ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; } ) ; describe ( 'MailToSend' , ( ) => { const configName = 'Mailer' ; const fileName = 'file.ejs' ; const templateToRender = 'path/exists/and/is/last' ; const configMock = { templatePaths : [ 'path/does/not/exist' , 'path/does/exist' , templateToRender , ] , } ; const idpIdentityMock = { sub : 'some idpSub' , } as PartialExcept < IOidcIdentity , 'sub' > ; const spIdentityWithEmailMock = { sub : 'some spSub' , given_name : 'Edward' , family_name : 'TEACH' , email : undefined , } ; const sessionDataMock = { idpId : '42' , idpAcr : 'eidas3' , idpName : 'my favorite Idp' , idpIdentity : idpIdentityMock , spId : 'sp_id' , spAcr : 'eidas3' , spName : 'my great SP' , spIdentity : spIdentityWithEmailMock , } ; const connectNotificationEmailParametersMock = { idpName : sessionDataMock . idpName , spName : sessionDataMock . spName , givenName : spIdentityWithEmailMock . given_name , familyName : spIdentityWithEmailMock . family_name , today : 'Le 01/01/2021 à 14:14' , } ; const template = 'html file' ; const html = 'Hello world' ; beforeEach ( async ( ) => { jest . resetAllMocks ( ) ; jest . restoreAllMocks ( ) ; jest . clearAllMocks ( ) ; configServiceMock . get . mockReturnValueOnce ( configMock ) ; } ) ; it ( 'should call the config' , async ( ) => { templateServiceMock . getFilePath . mockReturnValueOnce ( configMock . templatePaths [ 2 ] ) ; templateServiceMock . readFile . mockResolvedValueOnce ( template ) ; templateServiceMock . render . mockReturnValueOnce ( html ) ; await service . mailToSend ( fileName , connectNotificationEmailParametersMock ) ; expect ( configServiceMock . get ) . toBeCalledTimes ( 1 ) ; expect ( configServiceMock . get ) . toBeCalledWith ( configName ) ; } ) ; it ( 'should call getFilePath from templateService' , async ( ) => { templateServiceMock . getFilePath . mockReturnValueOnce ( templateToRender ) ; templateServiceMock . readFile . mockResolvedValueOnce ( template ) ; templateServiceMock . render . mockReturnValueOnce ( html ) ; await service . mailToSend ( fileName , connectNotificationEmailParametersMock ) ; expect ( templateServiceMock . getFilePath ) . toHaveBeenCalledTimes ( 1 ) ; expect ( templateServiceMock . getFilePath ) . toHaveBeenCalledWith ( fileName , configMock . templatePaths ) ; } ) ; it ( 'should throw an error if the file path is not found' , ( ) => { templateServiceMock . getFilePath . mockReturnValueOnce ( undefined ) ; expect ( ( ) => service . mailToSend ( fileName , connectNotificationEmailParametersMock ) ) . rejects . toThrow ( TemplateNotFoundException ) ; } ) ; it ( 'should call readFile from templateService' , async ( ) => { templateServiceMock . getFilePath . mockReturnValueOnce ( templateToRender ) ; templateServiceMock . readFile . mockResolvedValueOnce ( template ) ; templateServiceMock . render . mockReturnValueOnce ( html ) ; await service . mailToSend ( fileName , connectNotificationEmailParametersMock ) ; expect ( templateServiceMock . readFile ) . toHaveBeenCalledTimes ( 1 ) ; expect ( templateServiceMock . readFile ) . toHaveBeenCalledWith ( templateToRender ) ; } ) ; it ( 'should call render from templateService' , async ( ) => { templateServiceMock . getFilePath . mockReturnValueOnce ( templateToRender ) ; templateServiceMock . readFile . mockResolvedValueOnce ( template ) ; templateServiceMock . render . mockReturnValueOnce ( html ) ; await service . mailToSend ( fileName , connectNotificationEmailParametersMock ) ; expect ( templateServiceMock . render ) . toHaveBeenCalledTimes ( 1 ) ; expect ( templateServiceMock . render ) . toHaveBeenCalledWith ( template , connectNotificationEmailParametersMock ) ; } ) ; it ( 'should return the html to render' , async ( ) => { templateServiceMock . getFilePath . mockReturnValueOnce ( configMock . templatePaths [ 2 ] ) ; templateServiceMock . readFile . mockResolvedValueOnce ( template ) ; templateServiceMock . render . mockReturnValueOnce ( html ) ; const result = await service . mailToSend ( fileName , connectNotificationEmailParametersMock ) ; expect ( result ) . toEqual ( html ) ; } ) ; } ) ; } ) ;