diff --git a/Server/.nycrc b/Server/.nycrc index d7668b4c9..83b717bb5 100644 --- a/Server/.nycrc +++ b/Server/.nycrc @@ -1,6 +1,6 @@ { "all": true, - "include": ["controllers/*.js", "utils/*.js"], + "include": ["controllers/*.js", "utils/*.js", "service/*.js"], "exclude": ["**/*.test.js"], "reporter": ["html", "text", "lcov"], "sourceMap": false, diff --git a/Server/tests/services/emailService.test.js b/Server/tests/services/emailService.test.js new file mode 100644 index 000000000..1be064af7 --- /dev/null +++ b/Server/tests/services/emailService.test.js @@ -0,0 +1,218 @@ +import sinon from "sinon"; +import EmailService from "../../service/emailService.js"; + +describe("EmailService - Constructor", () => { + let settingsServiceMock; + let fsMock; + let pathMock; + let compileMock; + let mjml2htmlMock; + let nodemailerMock; + let loggerMock; + + beforeEach(() => { + settingsServiceMock = { + getSettings: sinon.stub().returns({ + systemEmailHost: "smtp.example.com", + systemEmailPort: 465, + systemEmailAddress: "test@example.com", + systemEmailPassword: "password", + }), + }; + + fsMock = { + readFileSync: sinon.stub().returns(""), + }; + + pathMock = { + join: sinon.stub().callsFake((...args) => args.join("/")), + }; + + compileMock = sinon.stub().returns(() => ""); + + mjml2htmlMock = sinon.stub().returns({ html: "" }); + + nodemailerMock = { + createTransport: sinon.stub().returns({ + sendMail: sinon.stub().resolves({ messageId: "12345" }), + }), + }; + + loggerMock = { + error: sinon.stub(), + }; + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should initialize template loaders and email transporter", () => { + const emailService = new EmailService( + settingsServiceMock, + fsMock, + pathMock, + compileMock, + mjml2htmlMock, + nodemailerMock, + loggerMock + ); + + // Verify that the settingsService is assigned correctly + expect(emailService.settingsService).to.equal(settingsServiceMock); + + // Verify that the template loaders are initialized + expect(emailService.templateLookup.welcomeEmailTemplate).to.be.a("function"); + expect(emailService.templateLookup.employeeActivationTemplate).to.be.a("function"); + + // Verify that the email transporter is initialized + expect(nodemailerMock.createTransport.calledOnce).to.be.true; + const emailConfig = nodemailerMock.createTransport.getCall(0).args[0]; + expect(emailConfig).to.deep.equal({ + host: "smtp.example.com", + port: 465, + secure: true, + auth: { + user: "test@example.com", + pass: "password", + }, + }); + }); + + it("should have undefined templates if FS fails", () => { + fsMock = { + readFileSync: sinon.stub().throws(new Error("File read error")), + }; + const emailService = new EmailService( + settingsServiceMock, + fsMock, + pathMock, + compileMock, + mjml2htmlMock, + nodemailerMock, + loggerMock + ); + expect(loggerMock.error.called).to.be.true; + const errorCalls = loggerMock.error.getCalls(); + const errorCall = errorCalls.find( + (call) => call.args[0] === "Error loading Email templates" + ); + expect(errorCall).to.not.be.undefined; + expect(emailService.settingsService).to.equal(settingsServiceMock); + expect(emailService.templateLookup.welcomeEmailTemplate).to.be.undefined; + expect(emailService.templateLookup.employeeActivationTemplate).to.be.undefined; + }); +}); + +describe("EmailService - buildAndSendEmail", () => { + let settingsServiceMock; + let fsMock; + let pathMock; + let compileMock; + let mjml2htmlMock; + let nodemailerMock; + let loggerMock; + let emailService; + beforeEach(() => { + settingsServiceMock = { + getSettings: sinon.stub().returns({ + systemEmailHost: "smtp.example.com", + systemEmailPort: 465, + systemEmailAddress: "test@example.com", + systemEmailPassword: "password", + }), + }; + + fsMock = { + readFileSync: sinon.stub().returns(""), + }; + + pathMock = { + join: sinon.stub().callsFake((...args) => args.join("/")), + }; + + compileMock = sinon.stub().returns(() => ""); + + mjml2htmlMock = sinon.stub().returns({ html: "" }); + + nodemailerMock = { + createTransport: sinon.stub().returns({ + sendMail: sinon.stub().resolves({ messageId: "12345" }), + }), + }; + + loggerMock = { + error: sinon.stub(), + }; + + emailService = new EmailService( + settingsServiceMock, + fsMock, + pathMock, + compileMock, + mjml2htmlMock, + nodemailerMock, + loggerMock + ); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should build and send email successfully", async () => { + const messageId = await emailService.buildAndSendEmail( + "welcomeEmailTemplate", + {}, + "recipient@example.com", + "Welcome" + ); + + expect(messageId).to.equal("12345"); + expect(nodemailerMock.createTransport().sendMail.calledOnce).to.be.true; + }); + + it("should log error if building HTML fails", async () => { + mjml2htmlMock.throws(new Error("MJML error")); + + const messageId = await emailService.buildAndSendEmail( + "welcomeEmailTemplate", + {}, + "recipient@example.com", + "Welcome" + ); + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.getCall(0).args[0]).to.equal("Error building Email HTML"); + }); + + it("should log error if sending email fails", async () => { + nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); + await emailService.buildAndSendEmail( + "welcomeEmailTemplate", + {}, + "recipient@example.com", + "Welcome" + ); + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.getCall(0).args[0]).to.equal("Error sending Email"); + }); + + it("should log error if both building HTML and sending email fail", async () => { + mjml2htmlMock.throws(new Error("MJML error")); + nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); + + const messageId = await emailService.buildAndSendEmail( + "welcomeEmailTemplate", + {}, + "recipient@example.com", + "Welcome" + ); + + expect(messageId).to.be.undefined; + expect(loggerMock.error.calledTwice).to.be.true; + expect(loggerMock.error.getCall(0).args[0]).to.equal("Error building Email HTML"); + expect(loggerMock.error.getCall(1).args[0]).to.equal("Error sending Email"); + }); + + it("should log an error if buildHtml fails", async () => {}); +});