import { describe, it, expect } from "vitest"; import { sanitizeForLogging } from "./log-sanitizer"; describe("sanitizeForLogging", () => { describe("String sanitization", () => { it("should redact API keys", () => { const input = "Error with API key: sk_live_1234567890abcdef"; const result = sanitizeForLogging(input); expect(result).toBe("Error with API key: [REDACTED]"); }); it("should redact bearer tokens", () => { const input = "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; const result = sanitizeForLogging(input); expect(result).toBe("Authorization: Bearer [REDACTED]"); }); it("should redact Discord bot tokens", () => { const input = "Bot token: MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs"; const result = sanitizeForLogging(input); expect(result).toBe("Bot token: [REDACTED]"); }); it("should redact passwords in strings", () => { const input = 'Connection failed with password="secret123"'; const result = sanitizeForLogging(input); expect(result).toBe('Connection failed with password="[REDACTED]"'); }); it("should redact email addresses", () => { const input = "User email: user@example.com failed to authenticate"; const result = sanitizeForLogging(input); expect(result).toBe("User email: [REDACTED] failed to authenticate"); }); it("should redact database connection strings", () => { const input = "postgresql://user:password123@localhost:5432/mydb"; const result = sanitizeForLogging(input); expect(result).toBe("postgresql://user:[REDACTED]@localhost:5432/mydb"); }); it("should redact authorization headers", () => { const input = "Authorization: Basic dXNlcjpwYXNzd29yZA=="; const result = sanitizeForLogging(input); expect(result).toBe("Authorization: Basic [REDACTED]"); }); it("should preserve non-sensitive strings", () => { const input = "This is a regular log message without secrets"; const result = sanitizeForLogging(input); expect(result).toBe("This is a regular log message without secrets"); }); it("should redact environment variable style secrets", () => { const input = "API_KEY=abc123def456 failed"; const result = sanitizeForLogging(input); expect(result).toBe("API_KEY=[REDACTED] failed"); }); it("should redact multiple secrets in one string", () => { const input = "token=xyz123 and password=secret456"; const result = sanitizeForLogging(input); expect(result).toBe("token=[REDACTED] and password=[REDACTED]"); }); }); describe("Object sanitization", () => { it("should redact secrets in flat objects", () => { const input = { message: "Error occurred", apiKey: "sk_live_1234567890", token: "Bearer abc123", }; const result = sanitizeForLogging(input); expect(result).toEqual({ message: "Error occurred", apiKey: "[REDACTED]", token: "[REDACTED]", }); }); it("should redact secrets in nested objects", () => { const input = { error: { message: "Auth failed", credentials: { username: "admin", password: "secret123", }, }, }; const result = sanitizeForLogging(input); expect(result).toEqual({ error: { message: "Auth failed", credentials: { username: "admin", password: "[REDACTED]", }, }, }); }); it("should redact secrets based on key names", () => { const input = { apiKey: "secret", api_key: "secret", API_KEY: "secret", bearerToken: "token", accessToken: "token", password: "pass", secret: "secret", client_secret: "secret", }; const result = sanitizeForLogging(input); expect(result).toEqual({ apiKey: "[REDACTED]", api_key: "[REDACTED]", API_KEY: "[REDACTED]", bearerToken: "[REDACTED]", accessToken: "[REDACTED]", password: "[REDACTED]", secret: "[REDACTED]", client_secret: "[REDACTED]", }); }); it("should preserve non-sensitive object properties", () => { const input = { message: "Test message", statusCode: 500, timestamp: new Date("2024-01-01"), count: 42, }; const result = sanitizeForLogging(input); expect(result).toEqual({ message: "Test message", statusCode: 500, timestamp: new Date("2024-01-01"), count: 42, }); }); it("should handle objects with null and undefined values", () => { const input = { message: "Error", token: null, apiKey: undefined, data: "value", }; const result = sanitizeForLogging(input); expect(result).toEqual({ message: "Error", token: null, apiKey: undefined, data: "value", }); }); }); describe("Array sanitization", () => { it("should sanitize strings in arrays", () => { const input = ["normal message", "token=abc123", "another message"]; const result = sanitizeForLogging(input); expect(result).toEqual(["normal message", "token=[REDACTED]", "another message"]); }); it("should sanitize objects in arrays", () => { const input = [ { message: "ok" }, { message: "error", apiKey: "secret123" }, { message: "info" }, ]; const result = sanitizeForLogging(input); expect(result).toEqual([ { message: "ok" }, { message: "error", apiKey: "[REDACTED]" }, { message: "info" }, ]); }); it("should handle nested arrays", () => { const input = [["token=abc"], ["password=xyz"]]; const result = sanitizeForLogging(input); expect(result).toEqual([["token=[REDACTED]"], ["password=[REDACTED]"]]); }); }); describe("Error object sanitization", () => { it("should sanitize Error objects", () => { const error = new Error("Auth failed with token abc123"); const result = sanitizeForLogging(error); expect(result.message).toBe("Auth failed with token [REDACTED]"); expect(result.name).toBe("Error"); }); it("should sanitize custom error properties", () => { const error = new Error("Request failed"); (error as any).config = { headers: { Authorization: "Bearer secret123", }, }; const result = sanitizeForLogging(error); expect(result.config.headers.Authorization).toBe("[REDACTED]"); }); it("should handle errors with nested objects", () => { const error = new Error("Discord error"); (error as any).response = { status: 401, data: { message: "Invalid token", token: "abc123", }, }; const result = sanitizeForLogging(error); expect(result.response.data.token).toBe("[REDACTED]"); }); }); describe("Edge cases", () => { it("should handle null input", () => { const result = sanitizeForLogging(null); expect(result).toBeNull(); }); it("should handle undefined input", () => { const result = sanitizeForLogging(undefined); expect(result).toBeUndefined(); }); it("should handle numbers", () => { const result = sanitizeForLogging(42); expect(result).toBe(42); }); it("should handle booleans", () => { const result = sanitizeForLogging(true); expect(result).toBe(true); }); it("should handle empty objects", () => { const result = sanitizeForLogging({}); expect(result).toEqual({}); }); it("should handle empty arrays", () => { const result = sanitizeForLogging([]); expect(result).toEqual([]); }); it("should handle circular references", () => { const obj: any = { name: "test" }; obj.self = obj; const result = sanitizeForLogging(obj); expect(result.name).toBe("test"); expect(result.self).toBe("[Circular Reference]"); }); it("should handle large objects without performance issues", () => { const largeObj: any = {}; for (let i = 0; i < 1000; i++) { largeObj[`key${i}`] = `value${i}`; } largeObj.password = "secret123"; const start = Date.now(); const result = sanitizeForLogging(largeObj); const duration = Date.now() - start; expect(result.password).toBe("[REDACTED]"); expect(duration).toBeLessThan(500); // Should complete in under 500ms }); }); describe("Discord-specific cases", () => { it("should sanitize Discord bot token format", () => { const input = { error: "Failed to connect", token: "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs", }; const result = sanitizeForLogging(input); expect(result.token).toBe("[REDACTED]"); }); it("should sanitize Discord error with config", () => { const error = { message: "Request failed", config: { headers: { Authorization: "Bot MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs", }, }, }; const result = sanitizeForLogging(error); expect(result.config.headers.Authorization).toBe("[REDACTED]"); }); it("should sanitize workspace IDs if configured", () => { const input = { message: "Job dispatched", workspaceId: "ws_123456789", }; const result = sanitizeForLogging(input); // Workspace IDs are preserved by default (not considered sensitive) // Can be redacted if needed in future expect(result.workspaceId).toBe("ws_123456789"); }); }); });