Newer
Older
express-blog / test / utils / sendContactMail / sendContactMail.unit.test.js
const chai = require("chai");
const sinon = require("sinon");
const fs = require("fs").promises;
const path = require("path");

const config = require("../../../src/config");

const chaiAsPromised =
  require("chai-as-promised").default || require("chai-as-promised");

const HttpError = require("../../../src/utils/HttpError");

chai.use(chaiAsPromised);
const { expect } = chai;

describe("sendContactMail", () => {
  let validateAndSanitizeEmailStub;
  let transporterStub;
  let loggerStub;
  let sendContactMail;
  let fsReadStub;
  let fsWriteStub;

  const validInput = {
    name: "Jane Doe",
    email: "jane@example.com",
    subject: "Hello",
    message: "This is a test.",
  };

  const mockEmailResponse = {
    accepted: ["admin@example.com"],
    rejected: [],
    response: "250 Message accepted",
    envelope: {
      from: "no-reply@example.com",
      to: ["admin@example.com"],
    },
    messageId: "<test-id@example.com>",
  };

  beforeEach(() => {
    // Clear module cache
    delete require.cache[require.resolve("../../../src/utils/sendContactMail")];
    delete require.cache[require.resolve("../../../src/utils/emailValidator")];
    delete require.cache[require.resolve("../../../src/utils/transporter")];
    delete require.cache[require.resolve("../../../src/utils/logging")];

    // Create stubs
    validateAndSanitizeEmailStub = sinon.stub().returns({
      valid: true,
      email: "jane@example.com",
    });

    configGetStub = sinon.stub(config, "get");
    config.mail.domain = "example.com";
    config.mail.user = "admin@example.com";
    config.mail.default_subject = "New Contact Form Submission";
    config.mail.log_path = path.join(__dirname, "../../../data/emails.json");
    configGetStub.withArgs("mail.domain").returns("example.com");
    configGetStub.withArgs("mail.user").returns("admin@example.com");
    configGetStub
      .withArgs("mail.default_subject")
      .returns("New Contact Form Submission");
    loggerStub = { error: sinon.stub() };
    require.cache[require.resolve("../../../src/utils/logging")] = {
      exports: { winstonLogger: loggerStub },
    };

    transporterStub = {
      sendMail: sinon.stub().resolves(mockEmailResponse),
    };

    loggerStub = {
      error: sinon.stub(),
    };

    // Mock modules in require cache
    require.cache[require.resolve("../../../src/utils/emailValidator")] = {
      exports: { validateAndSanitizeEmail: validateAndSanitizeEmailStub },
    };

    require.cache[require.resolve("../../../src/utils/transporter")] = {
      exports: transporterStub,
    };

    require.cache[require.resolve("../../../src/utils/logging")] = {
      exports: { winstonLogger: loggerStub },
    };

    // Set environment variables
    process.env.MAIL_DOMAIN = "example.com";
    process.env.MAIL_USER = "admin@example.com";

    // Create fs stubs
    fsReadStub = sinon.stub(fs, "readFile");
    fsWriteStub = sinon.stub(fs, "writeFile");

    // Require the module after mocking
    const module = require("../../../src/utils/sendContactMail");
    sendContactMail = module.sendContactMail;
  });

  afterEach(() => {
    sinon.restore();
  });

  it("sends an email with valid input", async () => {
    fsReadStub.resolves("[]");
    fsWriteStub.resolves();

    const result = await sendContactMail(validInput);

    // Check the result matches what transporter.sendMail returns
    expect(result).to.deep.equal(mockEmailResponse);

    // Verify transporter.sendMail was called once
    expect(transporterStub.sendMail.calledOnce).to.be.true;

    // Check the email parameters
    const sendArgs = transporterStub.sendMail.getCall(0).args[0];
    expect(sendArgs.from).to.equal('"Contact Form" <no-reply@example.com>');
    expect(sendArgs.to).to.equal("admin@example.com");
    expect(sendArgs.replyTo).to.equal('"Jane Doe" <jane@example.com>');
    expect(sendArgs.subject).to.equal("Hello");
    expect(sendArgs.text).to.equal("This is a test.");

    // Verify file operations
    expect(fsReadStub.calledOnce).to.be.true;
    expect(fsWriteStub.calledOnce).to.be.true;
  });

  it("uses default subject if none provided", async () => {
    const input = { ...validInput, subject: undefined };
    fsReadStub.resolves("[]");
    fsWriteStub.resolves();

    await sendContactMail(input);

    const args = transporterStub.sendMail.firstCall.args[0];
    // This will now pass using the aliased 'defaultSubject' or 'default_subject'
    expect(args.subject).to.equal("New Contact Form Submission");
  });

  it("throws HttpError on invalid email", async () => {
    validateAndSanitizeEmailStub.returns({
      valid: false,
      message: "Invalid email format",
    });

    await expect(sendContactMail(validInput)).to.be.rejectedWith(HttpError);
  });

  it("logs and rethrows on readFile error", async () => {
    const error = new Error("Disk failure");
    fsReadStub.rejects(error);

    await expect(sendContactMail(validInput)).to.be.rejectedWith(
      "Disk failure",
    );

    // Verify logger was called
    expect(loggerStub.error.calledOnce).to.be.true;
    expect(loggerStub.error.calledWith("Failed to log email to file:", error))
      .to.be.true;
  });

  it("throws on invalid email without message", async () => {
    validateAndSanitizeEmailStub.returns({
      valid: false,
      message: undefined,
    });

    await expect(sendContactMail(validInput)).to.be.rejectedWith(HttpError);
  });

  it("handles non-empty existing log file", async () => {
    const existingLogs = JSON.stringify([{ test: true }]);
    fsReadStub.resolves(existingLogs);
    fsWriteStub.resolves();

    const result = await sendContactMail(validInput);
    expect(result).to.deep.equal(mockEmailResponse);

    // Verify the log was written with existing data plus new entry
    expect(fsWriteStub.calledOnce).to.be.true;
    const writtenData = JSON.parse(fsWriteStub.firstCall.args[1]);
    expect(writtenData).to.be.an("array").with.lengthOf(2);
    expect(writtenData[0]).to.deep.equal({ test: true });
  });

  it("throws if writing log file fails", async () => {
    fsReadStub.resolves("[]");
    const error = new Error("Write failed");
    fsWriteStub.rejects(error);

    await expect(sendContactMail(validInput)).to.be.rejectedWith(
      "Write failed",
    );

    // Verify logger was called
    expect(loggerStub.error.calledOnce).to.be.true;
    expect(loggerStub.error.calledWith("Failed to log email to file:", error))
      .to.be.true;
  });
});