Newer
Older
express-blog / test / units / utils / logging / object_expansion / writeLog.js
const { expect } = require("chai");
const sinon = require("sinon");
const { Writable } = require("stream");

// Mock dependencies
const mockLogStreams = {
  info: new Writable({ write() {} }),
  error: new Writable({ write() {} }),
  warn: new Writable({ write() {} }),
  debug: new Writable({ write() {} }),
  notice: new Writable({ write() {} }),
};

const mockSessionTransport = {
  write: sinon.stub(),
};

// Import the modules under test
const { writeLog } = require("../../../../../src/utils/logging/consolePatch");
describe("writeLog function", () => {
  let consoleLogStub; // Declare stub for this describe block

  beforeEach(() => {
    // Stub console.log specifically for this describe block
    consoleLogStub = sinon.stub(console, "log");
  });

  afterEach(() => {
    // Restore console.log stub after each test in this block
    consoleLogStub.restore();
  });

  it("should never log [object Object] for simple objects", () => {
    const testObject = { name: "test", value: 42 };

    writeLog(
      "INFO",
      mockLogStreams.info,
      console.log,
      mockSessionTransport,
      testObject
    );

    // Check console output
    expect(consoleLogStub.called).to.be.true;
    const consoleArgs = consoleLogStub.getCall(0).args;
    expect(consoleArgs).to.exist;
    const outputString = consoleArgs.join(" ");
    expect(outputString).to.not.include("[object Object]");
    expect(outputString).to.include("name");
    expect(outputString).to.include("test");
    expect(outputString).to.include("value");
    expect(outputString).to.include("42");

    // Check stream output
    expect(streamWriteStubs.info.called).to.be.true;
    const streamOutput = streamWriteStubs.info.getCall(0).args[0];
    expect(streamOutput).to.not.include("[object Object]");
    expect(streamOutput).to.include("name");
    expect(streamOutput).to.include("test");
  });

  it("should properly expand nested objects", () => {
    const nestedObject = {
      user: {
        id: 123,
        profile: {
          name: "John Doe",
          settings: { theme: "dark", notifications: true },
        },
      },
    };

    writeLog(
      "INFO",
      mockLogStreams.info,
      console.log,
      mockSessionTransport,
      nestedObject
    );

    expect(consoleLogStub.called).to.be.true;
    const consoleArgs = consoleLogStub.getCall(0).args;
    expect(consoleArgs).to.exist;
    const outputString = consoleArgs.join(" ");
    expect(outputString).to.not.include("[object Object]");
    expect(outputString).to.include("John Doe");
    expect(outputString).to.include("theme");
    expect(outputString).to.include("dark");
    expect(outputString).to.include("notifications");
  });

  it("should handle circular references without [object Object]", () => {
    const circularObj = { name: "circular" };
    circularObj.self = circularObj;
    circularObj.nested = { parent: circularObj };

    writeLog(
      "INFO",
      mockLogStreams.info,
      console.log,
      mockSessionTransport,
      circularObj
    );

    expect(consoleLogStub.called).to.be.true;
    const consoleArgs = consoleLogStub.getCall(0).args;
    expect(consoleArgs).to.exist;
    const outputString = consoleArgs.join(" ");
    expect(outputString).to.not.include("[object Object]");
    expect(outputString).to.include("name");
    expect(outputString).to.include("circular");
    // Should handle circular reference gracefully
    expect(outputString).to.include("self");
  });

  it("should expand arrays containing objects", () => {
    const arrayWithObjects = [
      { id: 1, name: "first" },
      { id: 2, name: "second", nested: { value: "test" } },
    ];

    writeLog(
      "INFO",
      mockLogStreams.info,
      console.log,
      mockSessionTransport,
      arrayWithObjects
    );

    expect(consoleLogStub.called).to.be.true;
    const consoleArgs = consoleLogStub.getCall(0).args;
    expect(consoleArgs).to.exist;
    const outputString = consoleArgs.join(" ");
    expect(outputString).to.not.include("[object Object]");
    expect(outputString).to.include("first");
    expect(outputString).to.include("second");
    expect(outputString).to.include("nested");
    expect(outputString).to.include("test");
  });

  it("should handle mixed argument types without [object Object]", () => {
    const mixedArgs = [
      "String message",
      { obj: "value" },
      42,
      ["array", "items"],
      { deeply: { nested: { object: "here" } } },
    ];

    writeLog(
      "INFO",
      mockLogStreams.info,
      console.log,
      mockSessionTransport,
      ...mixedArgs
    );

    expect(consoleLogStub.called).to.be.true;
    const consoleArgs = consoleLogStub.getCall(0).args;
    expect(consoleArgs).to.exist;
    const outputString = consoleArgs.join(" ");
    expect(outputString).to.not.include("[object Object]");
    expect(outputString).to.include("String message");
    expect(outputString).to.include("obj");
    expect(outputString).to.include("value");
    expect(outputString).to.include("deeply");
    expect(outputString).to.include("here");
  });

  it("should handle Error objects without [object Object]", () => {
    const error = new Error("Test error");
    error.customProperty = { details: "additional info" };

    // Stub console.error for this test (local stub, not interfering with console.log stub)
    const consoleErrorStub = sinon.stub(console, "error");

    writeLog(
      "ERROR",
      mockLogStreams.error,
      console.error,
      mockSessionTransport,
      error
    );

    expect(consoleErrorStub.called).to.be.true;
    const consoleArgs = consoleErrorStub.getCall(0).args;
    expect(consoleArgs).to.exist;
    const outputString = consoleArgs.join(" ");
    expect(outputString).to.not.include("[object Object]");
    expect(outputString).to.include("Test error");

    consoleErrorStub.restore();
  });

  it("should handle objects with special properties", () => {
    const specialObj = {
      toString: () => "custom toString",
      valueOf: () => 99,
      [Symbol.toStringTag]: "CustomObject",
      normalProp: "normal value",
    };

    writeLog(
      "INFO",
      mockLogStreams.info,
      console.log,
      mockSessionTransport,
      specialObj
    );

    expect(consoleLogStub.called).to.be.true;
    const consoleArgs = consoleLogStub.getCall(0).args;
    expect(consoleArgs).to.exist;
    const outputString = consoleArgs.join(" ");
    expect(outputString).to.not.include("[object Object]");
    expect(outputString).to.include("normalProp");
    expect(outputString).to.include("normal value");
  });
});