Skip to main content
Glama
debug.test.js15.1 kB
/** * Tests for debug utilities * * Comprehensive test coverage for debug logging, performance timing, * and development utilities. */ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { debug, startTimer } from "@/utils/debug.js"; import { Config } from "@/config/Config.js"; describe("Debug Utilities", () => { let originalConsole; let _consoleLogSpy; let _consoleWarnSpy; let consoleErrorSpy; let originalNodeEnv; let originalDebug; beforeEach(() => { vi.clearAllMocks(); // Store original console methods originalConsole = { ...console }; // Create console spies - debug utility uses console.error to avoid STDIO interference _consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); _consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); // Store original environment variables originalNodeEnv = process.env.NODE_ENV; originalDebug = process.env.DEBUG; // Reset Config singleton to pick up new environment variables Config.reset(); }); afterEach(() => { // Restore console methods console.log = originalConsole.log; console.warn = originalConsole.warn; console.error = originalConsole.error; // Restore environment variables process.env.NODE_ENV = originalNodeEnv; if (originalDebug !== undefined) { process.env.DEBUG = originalDebug; Config.reset(); // Reload config with new env vars } else { delete process.env.DEBUG; } // Reset Config singleton after test Config.reset(); vi.restoreAllMocks(); }); describe("Debug Logger", () => { it("should log messages when in debug mode", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars Config.reset(); // Reload config with new env vars debug.log("Test debug message"); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Test debug message")); }); it("should not log messages in production", () => { process.env.DEBUG = "false"; Config.reset(); // Reload config with new env vars Config.reset(); // Reload config with new env vars Config.reset(); // Reload config with new env vars debug.log("Test debug message"); expect(consoleErrorSpy).not.toHaveBeenCalled(); }); it("should log messages when DEBUG environment variable is set", () => { process.env.NODE_ENV = "production"; process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars debug.log("Test debug message"); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Test debug message")); delete process.env.DEBUG; }); it("should handle object logging", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars const testObject = { id: 123, name: "Test Object", data: { nested: "value" }, }; debug.log("Object test", testObject); // debug.log formats everything into a single string with timestamp expect(consoleErrorSpy).toHaveBeenCalled(); const callArg = consoleErrorSpy.mock.calls[0][0]; expect(callArg).toContain("Object test"); expect(callArg).toContain("Test Object"); }); it("should handle array logging", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars const testArray = [1, 2, 3, "test", { key: "value" }]; debug.log("Array test", testArray); // debug.log formats everything into a single string with timestamp expect(consoleErrorSpy).toHaveBeenCalled(); const callArg = consoleErrorSpy.mock.calls[0][0]; expect(callArg).toContain("Array test"); }); it("should handle multiple arguments", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars debug.log("Multiple", "arguments", { test: true }, 123); // debug.log formats everything into a single string with timestamp expect(consoleErrorSpy).toHaveBeenCalled(); const callArg = consoleErrorSpy.mock.calls[0][0]; expect(callArg).toContain("Multiple"); expect(callArg).toContain("arguments"); expect(callArg).toContain("123"); }); it("should handle null and undefined values", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars debug.log("Null test", null); debug.log("Undefined test", undefined); // debug.log formats everything into a single string with timestamp expect(consoleErrorSpy).toHaveBeenCalledTimes(2); expect(consoleErrorSpy.mock.calls[0][0]).toContain("Null test"); expect(consoleErrorSpy.mock.calls[1][0]).toContain("Undefined test"); }); it("should add timestamp to log messages", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars debug.log("Timestamp test"); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringMatching(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\]/), ); delete process.env.DEBUG; }); it("should handle empty messages", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars debug.log(""); debug.log(); expect(consoleErrorSpy).toHaveBeenCalledTimes(2); delete process.env.DEBUG; }); it("should handle error objects", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars const error = new Error("Test error"); error.code = "TEST_ERROR"; error.statusCode = 500; debug.log("Error test", error); // debug.log formats everything into a single string with timestamp expect(consoleErrorSpy).toHaveBeenCalled(); const callArg = consoleErrorSpy.mock.calls[0][0]; expect(callArg).toContain("Error test"); }); it("should handle circular references safely", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars const circular = { name: "circular" }; circular.self = circular; // Currently formatMessage uses JSON.stringify which throws on circular refs // This is a known limitation - formatMessage should use safeStringify instead expect(() => { debug.log("Circular test", circular); }).toThrow(/circular structure/i); }); }); describe("Performance Timer", () => { it("should create a timer object", () => { const timer = startTimer(); expect(timer).toHaveProperty("end"); expect(typeof timer.end).toBe("function"); }); it("should measure elapsed time", async () => { const timer = startTimer(); // Wait for a small delay await new Promise((resolve) => setTimeout(resolve, 10)); const elapsed = timer.end(); expect(elapsed).toBeGreaterThan(5); expect(elapsed).toBeLessThan(50); // Should be around 10ms }); it("should return time in milliseconds", () => { const timer = startTimer(); const elapsed = timer.end(); expect(typeof elapsed).toBe("number"); expect(elapsed).toBeGreaterThanOrEqual(0); }); it("should handle multiple calls to end()", () => { const timer = startTimer(); const elapsed1 = timer.end(); const elapsed2 = timer.end(); expect(elapsed1).toBeGreaterThanOrEqual(0); expect(elapsed2).toBeGreaterThanOrEqual(elapsed1); }); it("should create independent timers", async () => { const timer1 = startTimer(); await new Promise((resolve) => setTimeout(resolve, 5)); const timer2 = startTimer(); await new Promise((resolve) => setTimeout(resolve, 10)); const elapsed1 = timer1.end(); const elapsed2 = timer2.end(); expect(elapsed1).toBeGreaterThan(elapsed2); }); it("should handle high precision timing", () => { const timer = startTimer(); // Do some synchronous work let _sum = 0; for (let i = 0; i < 1000; i++) { _sum += i; } const elapsed = timer.end(); expect(elapsed).toBeGreaterThanOrEqual(0); expect(typeof elapsed).toBe("number"); expect(Number.isFinite(elapsed)).toBe(true); }); it("should work with async operations", async () => { const timer = startTimer(); await Promise.resolve("test"); await new Promise((resolve) => setTimeout(resolve, 1)); const elapsed = timer.end(); expect(elapsed).toBeGreaterThan(0); }); }); describe("Debug Conditional Logging", () => { it("should respect NODE_ENV=test", () => { process.env.NODE_ENV = "test"; debug.log("Test environment message"); // In test environment, debug logging might be disabled // This tests the conditional logic expect(consoleErrorSpy.mock.calls.length).toBeGreaterThanOrEqual(0); }); it("should respect DEBUG=false explicitly", () => { process.env.DEBUG = "false"; Config.reset(); // Reload config with new env vars Config.reset(); // Reload config with new env vars debug.log("Debug disabled message"); // Should not log when explicitly disabled expect(consoleErrorSpy).not.toHaveBeenCalled(); delete process.env.DEBUG; }); it("should handle various truthy DEBUG values", () => { // Only "true" enables debug in the config consoleErrorSpy.mockClear(); process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars debug.log("Debug with true"); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Debug with true")); delete process.env.DEBUG; }); it("should handle various falsy DEBUG values", () => { const falsyValues = ["false", "0", "no", "off", undefined]; falsyValues.forEach((value) => { consoleErrorSpy.mockClear(); if (value === undefined) { delete process.env.DEBUG; } else { process.env.DEBUG = value; } Config.reset(); // Reload config with new env vars after each change debug.log(`Debug with ${value}`); expect(consoleErrorSpy).not.toHaveBeenCalled(); }); }); }); describe("Error Handling in Debug", () => { it("should handle logging errors gracefully", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars // Mock console.error to throw an error consoleErrorSpy.mockImplementation(() => { throw new Error("Console error"); }); expect(() => { debug.log("This should not crash"); }).toThrow(); // Will throw since console.error throws delete process.env.DEBUG; }); it("should handle JSON serialization errors", () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars const problematicObject = {}; // Create a circular reference that would break JSON.stringify problematicObject.circular = problematicObject; // Currently formatMessage uses JSON.stringify which throws on circular refs // This is a known limitation - formatMessage should use safeStringify instead expect(() => { debug.log("Problematic object", problematicObject); }).toThrow(/circular structure/i); }); }); describe("Performance in Production", () => { it("should have minimal overhead when disabled", () => { process.env.NODE_ENV = "production"; delete process.env.DEBUG; const timer = startTimer(); // Make many debug calls - should be fast since they're disabled for (let i = 0; i < 1000; i++) { debug.log("Disabled message", i, { data: "test" }); } const elapsed = timer.end(); // Should be very fast since debug is disabled expect(elapsed).toBeLessThan(100); expect(consoleErrorSpy).not.toHaveBeenCalled(); }); it("should create minimal timer overhead", () => { const iterations = 1000; const timers = []; const startTime = Date.now(); for (let i = 0; i < iterations; i++) { const timer = startTimer(); timers.push(timer.end()); } const totalTime = Date.now() - startTime; expect(totalTime).toBeLessThan(100); // Should be very fast expect(timers).toHaveLength(iterations); expect(timers.every((time) => typeof time === "number")).toBe(true); }); }); describe("Integration Patterns", () => { it("should work with async/await patterns", async () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars const timer = startTimer(); const result = await (async () => { debug.log("Starting async operation"); await new Promise((resolve) => setTimeout(resolve, 5)); debug.log("Async operation complete"); return "success"; })(); const elapsed = timer.end(); expect(result).toBe("success"); expect(elapsed).toBeGreaterThanOrEqual(4); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Starting async operation")); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Async operation complete")); delete process.env.DEBUG; }); it("should work with Promise chains", async () => { process.env.DEBUG = "true"; Config.reset(); // Reload config with new env vars const result = await Promise.resolve("initial") .then((value) => { debug.log("First then", value); return `${value}-processed`; }) .then((value) => { debug.log("Second then", value); return `${value}-final`; }) .catch((error) => { debug.log("Promise error", error); throw error; }); expect(result).toBe("initial-processed-final"); expect(consoleErrorSpy).toHaveBeenCalledTimes(2); expect(consoleErrorSpy.mock.calls[0][0]).toContain("First then"); expect(consoleErrorSpy.mock.calls[0][0]).toContain("initial"); expect(consoleErrorSpy.mock.calls[1][0]).toContain("Second then"); expect(consoleErrorSpy.mock.calls[1][0]).toContain("initial-processed"); }); it("should handle nested timer scenarios", async () => { const outerTimer = startTimer(); await new Promise((resolve) => setTimeout(resolve, 5)); const innerTimer = startTimer(); await new Promise((resolve) => setTimeout(resolve, 10)); const innerElapsed = innerTimer.end(); const outerElapsed = outerTimer.end(); expect(innerElapsed).toBeGreaterThan(8); expect(outerElapsed).toBeGreaterThan(innerElapsed); expect(outerElapsed).toBeGreaterThan(13); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docdyhr/mcp-wordpress'

If you have feedback or need assistance with the MCP directory API, please join our Discord server