Skip to main content
Glama
container-logger.test.ts11.9 kB
/** * ContainerLogger Unit Tests * * Tests for the structured logging of container operations. * These tests verify log output format and remediation suggestions. * * @module __tests__/unit/containers/container-logger.test * * _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5_ */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ContainerLogger, createContainerLogger } from "../../../containers/container-logger"; describe("ContainerLogger", () => { let logger: ContainerLogger; let stderrWriteSpy: ReturnType<typeof vi.fn>; let originalNodeEnv: string | undefined; beforeEach(() => { // Capture stderr output stderrWriteSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true) as ReturnType< typeof vi.fn >; originalNodeEnv = process.env.NODE_ENV; // Enable verbose logging for tests logger = new ContainerLogger("containers", true); }); afterEach(() => { vi.clearAllMocks(); process.env.NODE_ENV = originalNodeEnv; }); /** * Helper to get the last logged message */ function getLastLogMessage(): string { const calls = stderrWriteSpy.mock.calls; if (calls.length === 0) return ""; return calls[calls.length - 1][0] as string; } /** * Helper to get all logged messages */ function getAllLogMessages(): string[] { return stderrWriteSpy.mock.calls.map((call) => call[0] as string); } describe("logStarting", () => { /** * Requirement 5.1: WHEN containers are starting THEN the Test Container Manager * SHALL log the container name and target port */ it("should log service name and compose file when starting", () => { logger.logStarting("postgres-test", "docker-compose.test.yml"); const message = getLastLogMessage(); expect(message).toContain("postgres-test"); expect(message).toContain("docker-compose.test.yml"); expect(message).toContain("Starting"); }); it("should include INFO level in log message", () => { logger.logStarting("ollama-test", "docker-compose.test.yml"); const message = getLastLogMessage(); expect(message).toContain("INFO"); }); it("should include timestamp in ISO format", () => { logger.logStarting("postgres-test", "docker-compose.test.yml"); const message = getLastLogMessage(); // ISO timestamp pattern: YYYY-MM-DDTHH:mm:ss.sssZ expect(message).toMatch(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\]/); }); it("should include prefix in log message", () => { const customLogger = new ContainerLogger("test-prefix", true); customLogger.logStarting("service", "compose.yml"); const message = getLastLogMessage(); expect(message).toContain("[test-prefix]"); }); }); describe("logHealthCheck", () => { /** * Requirement 5.2: WHEN health checks are in progress THEN the Test Container Manager * SHALL log the health check status every 5 seconds */ it("should log service name and health status", () => { logger.logHealthCheck("postgres-test", "healthy"); const message = getLastLogMessage(); expect(message).toContain("postgres-test"); expect(message).toContain("healthy"); expect(message).toContain("Health check"); }); it("should log starting health status", () => { logger.logHealthCheck("ollama-test", "starting"); const message = getLastLogMessage(); expect(message).toContain("starting"); }); it("should log unhealthy status", () => { logger.logHealthCheck("postgres-test", "unhealthy"); const message = getLastLogMessage(); expect(message).toContain("unhealthy"); }); it("should use DEBUG level for health checks", () => { logger.logHealthCheck("postgres-test", "healthy"); const message = getLastLogMessage(); expect(message).toContain("DEBUG"); }); it("should not log health checks in production mode without verbose", () => { process.env.NODE_ENV = "production"; const prodLogger = new ContainerLogger("containers", false); prodLogger.logHealthCheck("postgres-test", "healthy"); expect(stderrWriteSpy).not.toHaveBeenCalled(); }); }); describe("logReady", () => { /** * Requirement 5.3: WHEN containers are ready THEN the Test Container Manager * SHALL log the final connection details (host, port, database name) */ it("should log service name and connection details", () => { logger.logReady("postgres-test", "localhost:5433/thoughtmcp_test"); const message = getLastLogMessage(); expect(message).toContain("postgres-test"); expect(message).toContain("localhost:5433/thoughtmcp_test"); expect(message).toContain("ready"); }); it("should log Ollama connection details", () => { logger.logReady("ollama-test", "http://localhost:11435"); const message = getLastLogMessage(); expect(message).toContain("ollama-test"); expect(message).toContain("http://localhost:11435"); }); it("should use INFO level for ready messages", () => { logger.logReady("postgres-test", "localhost:5433"); const message = getLastLogMessage(); expect(message).toContain("INFO"); }); }); describe("logError", () => { /** * Requirement 5.4: WHEN container startup fails THEN the Test Container Manager * SHALL log the Docker error output and suggest remediation steps */ it("should log service name and error message", () => { logger.logError("postgres-test", "Failed to start container"); const messages = getAllLogMessages(); expect(messages[0]).toContain("postgres-test"); expect(messages[0]).toContain("Failed to start container"); expect(messages[0]).toContain("ERROR"); }); it("should include provided remediation suggestion", () => { logger.logError("postgres-test", "Connection failed", "Check if Docker is running"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("Suggestion"); expect(messages[1]).toContain("Check if Docker is running"); }); it("should auto-detect remediation for port already in use", () => { logger.logError("postgres-test", "Error: port already in use"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("Suggestion"); expect(messages[1]).toContain("TEST_DB_PORT"); }); it("should auto-detect remediation for permission denied", () => { logger.logError("postgres-test", "permission denied while connecting to Docker"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("docker group"); }); it("should auto-detect remediation for connection refused", () => { logger.logError("postgres-test", "connection refused"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("Docker daemon"); }); it("should auto-detect remediation for image not found", () => { logger.logError("postgres-test", "image not found: pgvector/pgvector:pg16"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("docker pull"); }); it("should auto-detect remediation for timeout errors", () => { logger.logError("postgres-test", "timeout waiting for container to be healthy"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("CONTAINER_STARTUP_TIMEOUT"); }); it("should auto-detect remediation for health check failures", () => { logger.logError("postgres-test", "health check failed after 60 seconds"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("docker compose"); }); it("should auto-detect remediation for out of memory", () => { logger.logError("postgres-test", "out of memory"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("Docker Desktop"); }); it("should auto-detect remediation for disk space issues", () => { logger.logError("postgres-test", "no disk space left on device"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("docker system prune"); }); it("should auto-detect remediation for missing compose file", () => { logger.logError("postgres-test", "no such file or directory: docker-compose.test.yml"); const messages = getAllLogMessages(); expect(messages.length).toBe(2); expect(messages[1]).toContain("docker-compose.test.yml"); }); it("should not add suggestion for unknown errors", () => { logger.logError("postgres-test", "Unknown error XYZ123"); const messages = getAllLogMessages(); expect(messages.length).toBe(1); }); it("should prefer provided suggestion over auto-detected", () => { logger.logError("postgres-test", "port already in use", "Custom suggestion"); const messages = getAllLogMessages(); expect(messages[1]).toContain("Custom suggestion"); expect(messages[1]).not.toContain("TEST_DB_PORT"); }); }); describe("logStopped", () => { /** * Requirement 5.5: WHEN containers are stopped THEN the Test Container Manager * SHALL log the cleanup status */ it("should log service name when stopped", () => { logger.logStopped("postgres-test"); const message = getLastLogMessage(); expect(message).toContain("postgres-test"); expect(message).toContain("stopped"); }); it("should use INFO level for stopped messages", () => { logger.logStopped("ollama-test"); const message = getLastLogMessage(); expect(message).toContain("INFO"); }); }); describe("createContainerLogger factory", () => { it("should create a ContainerLogger instance", () => { const factoryLogger = createContainerLogger(); expect(factoryLogger).toBeInstanceOf(ContainerLogger); }); it("should accept custom prefix", () => { const factoryLogger = createContainerLogger("custom-prefix", true); factoryLogger.logStarting("service", "compose.yml"); const message = getLastLogMessage(); expect(message).toContain("[custom-prefix]"); }); it("should accept verbose flag", () => { process.env.NODE_ENV = "production"; const verboseLogger = createContainerLogger("test", true); verboseLogger.logHealthCheck("service", "healthy"); expect(stderrWriteSpy).toHaveBeenCalled(); }); }); describe("log format consistency", () => { it("should have consistent format across all log methods", () => { logger.logStarting("service1", "compose.yml"); logger.logHealthCheck("service2", "healthy"); logger.logReady("service3", "localhost:5432"); logger.logError("service4", "error"); logger.logStopped("service5"); const messages = getAllLogMessages(); // All messages should have timestamp, level, and prefix for (const message of messages) { expect(message).toMatch(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\]/); expect(message).toMatch(/\[(INFO|DEBUG|ERROR|WARN)\s*\]/); expect(message).toContain("[containers]"); } }); it("should end each log message with newline", () => { logger.logStarting("service", "compose.yml"); const message = getLastLogMessage(); expect(message.endsWith("\n")).toBe(true); }); }); });

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/keyurgolani/ThoughtMcp'

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