import { describe, expect, it } from "vitest"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; /** * PII-protection regression test for AuthService. * * Verifies that no `console.*` call remains in the file (those would * bypass the redact-paths Pino pipeline) and that we do not log * raw email / password / token / sessionToken fields through webLogger. */ describe("AuthService PII discipline", () => { const SOURCE = readFileSync(resolve(__dirname, "AuthService.ts"), "utf8"); it("contains no live console.* call sites", () => { const liveConsole = SOURCE.split("\n").filter((line, idx, all) => { if (!/console\.(log|error|warn|info|debug)\s*\(/.test(line)) return false; const trimmed = line.trimStart(); if (trimmed.startsWith("//") || trimmed.startsWith("*")) return false; const prev = idx > 0 ? all[idx - 1].trim() : ""; if (/eslint-disable-next-line[^\n]*no-console/.test(prev)) return false; if (/eslint-disable-line[^\n]*no-console/.test(line)) return false; return true; }); expect(liveConsole).toEqual([]); }); it("never passes plaintext email / password / token / sessionToken as authLogger fields", () => { // Scan property KEYS only (not values like `Boolean(token)` inside helpers). const pattern = /authLogger\.(info|warn|error|debug)\s*\(\s*\{([\s\S]*?)\}/g; const forbiddenKeys = ["email", "password", "sessionToken", "token", "passwordHash", "rawPassword"]; const offenders: string[] = []; let match: RegExpExecArray | null; while ((match = pattern.exec(SOURCE)) !== null) { const objContent = match[2]; for (const key of forbiddenKeys) { const keyRe = new RegExp(`(^|[\\s,{])${key}\\s*:`, "m"); if (keyRe.test(objContent)) offenders.push(`key "${key}" in ${match[0].slice(0, 80)}...`); } } expect(offenders, `authLogger must not log PII fields: ${offenders.join("; ")}`).toEqual([]); }); it("uses createScopedLogger('auth') for structured logging", () => { expect(SOURCE).toMatch(/createScopedLogger\(\s*["']auth["']\s*\)/); }); });