package unit; import support.Moad0004Algorithm; import support.Moad0004Algorithm.Result; import java.util.Map; /** * Unit tests for MOAD-0004: A Logged Secret. * * Proves from first principles: * 1. Defective: Authorization and Cookie headers appear verbatim in the log line — * bearer token and session cookie exposed in cleartext. * 2. Defective: leak count equals the number of credential headers present. * 3. Fixed: credential header values are replaced with [REDACTED] — * log line contains header names (for debugging) but not values. * 4. Fixed: non-credential headers (Host, Content-Type) pass through unchanged. * 5. Both handle empty header maps without error. * * No build tool required. Compile and run: * * cd tests * java -m jdk.compiler/com.sun.tools.javac.Main -cp . \ * support/Moad0004Algorithm.java unit/Moad0004UnitTest.java * java -cp . unit.Moad0004UnitTest */ public class Moad0004UnitTest { private static int passed = 0; private static int failed = 0; public static void main(String[] args) { System.out.println("=== Moad0004UnitTest (A Logged Secret) ===\n"); System.out.println("-- Correctness: both handle headers without throwing --"); testBothHandleEmpty(); System.out.println("\n-- Defect: credential headers appear in cleartext log --"); testDefectiveAuthorizationLeaks(); testDefectiveCookieLeaks(); testDefectiveCountsAllCredentialHeaders(); testDefectiveNonCredentialHeadersPresent(); System.out.println("\n-- Fix: credential headers redacted, non-credential headers intact --"); testFixedAuthorizationRedacted(); testFixedCookieRedacted(); testFixedNonCredentialHeadersPassThrough(); testFixedZeroLeakCount(); System.out.printf("\n%d passed, %d failed%n", passed, failed); if (failed > 0) System.exit(1); } // ── Correctness ─────────────────────────────────────────────────────────── static void testBothHandleEmpty() { Map empty = Map.of(); Result def = Moad0004Algorithm.logDefective(empty); Result fix = Moad0004Algorithm.logFixed(empty); assertEqual("defective: empty headers, zero leaks", 0, def.credentialLeakCount); assertEqual("fixed: empty headers, zero leaks", 0, fix.credentialLeakCount); } // ── Defect ──────────────────────────────────────────────────────────────── static void testDefectiveAuthorizationLeaks() { Map headers = Moad0004Algorithm.sampleHeaders(); Result result = Moad0004Algorithm.logDefective(headers); // DEFECT: bearer token appears in the log line verbatim assertContains("defective: Authorization value in log (bearer token exposed)", result.logLine, "eyJhbGciOiJSUzI1NiJ9.secret.token"); } static void testDefectiveCookieLeaks() { Map headers = Moad0004Algorithm.sampleHeaders(); Result result = Moad0004Algorithm.logDefective(headers); // DEFECT: session cookie and CSRF token appear in the log line assertContains("defective: Cookie value in log (session cookie exposed)", result.logLine, "session=abc123def456"); } static void testDefectiveCountsAllCredentialHeaders() { Map headers = Moad0004Algorithm.sampleHeaders(); Result result = Moad0004Algorithm.logDefective(headers); // sampleHeaders() contains: Authorization, Cookie, X-Api-Key = 3 credential headers assertEqual("defective: counts 3 credential header leaks", 3, result.credentialLeakCount); } static void testDefectiveNonCredentialHeadersPresent() { Map headers = Moad0004Algorithm.sampleHeaders(); Result result = Moad0004Algorithm.logDefective(headers); // Non-credential headers should appear (correct behavior for logging) assertContains("defective: Host header present in log", result.logLine, "api.example.com"); assertContains("defective: X-Request-Id present in log", result.logLine, "req-7f3a9c"); } // ── Fix ─────────────────────────────────────────────────────────────────── static void testFixedAuthorizationRedacted() { Map headers = Moad0004Algorithm.sampleHeaders(); Result result = Moad0004Algorithm.logFixed(headers); // FIX: bearer token must NOT appear in the log line assertNotContains("fixed: bearer token NOT in log", result.logLine, "eyJhbGciOiJSUzI1NiJ9.secret.token"); // The header name should still appear (useful for debugging) assertContains("fixed: Authorization header name present (redacted value)", result.logLine, "Authorization"); } static void testFixedCookieRedacted() { Map headers = Moad0004Algorithm.sampleHeaders(); Result result = Moad0004Algorithm.logFixed(headers); // FIX: session cookie must NOT appear assertNotContains("fixed: session cookie NOT in log", result.logLine, "session=abc123def456"); // The header name should still appear assertContains("fixed: Cookie header name present", result.logLine, "Cookie"); } static void testFixedNonCredentialHeadersPassThrough() { Map headers = Moad0004Algorithm.sampleHeaders(); Result result = Moad0004Algorithm.logFixed(headers); // Non-credential headers must pass through unchanged assertContains("fixed: Host value passes through", result.logLine, "api.example.com"); assertContains("fixed: X-Request-Id passes through", result.logLine, "req-7f3a9c"); assertContains("fixed: Content-Type passes through", result.logLine, "application/json"); } static void testFixedZeroLeakCount() { Map headers = Moad0004Algorithm.sampleHeaders(); Result result = Moad0004Algorithm.logFixed(headers); assertEqual("fixed: zero credential leaks", 0, result.credentialLeakCount); } // ── Helpers ─────────────────────────────────────────────────────────────── static void assertEqual(String label, int expected, int actual) { if (expected == actual) { System.out.printf(" PASS: %s%n", label); passed++; } else { System.out.printf(" FAIL: %s — expected %d, got %d%n", label, expected, actual); failed++; } } static void assertContains(String label, String haystack, String needle) { if (haystack.contains(needle)) { System.out.printf(" PASS: %s%n", label); passed++; } else { System.out.printf(" FAIL: %s — '%s' not found in: %s%n", label, needle, haystack); failed++; } } static void assertNotContains(String label, String haystack, String needle) { if (!haystack.contains(needle)) { System.out.printf(" PASS: %s%n", label); passed++; } else { System.out.printf(" FAIL: %s — '%s' should NOT be in: %s%n", label, needle, haystack); failed++; } } }