From 0da761243b118dd62b267bc191a548fcbbf941ec Mon Sep 17 00:00:00 2001 From: jpetree331 Date: Sun, 29 Mar 2026 10:14:01 -0400 Subject: [PATCH] test(ethos): D-2 pre-fix tests for ETHOS compliance violations Pre-fix tests documenting emoji in log statements and fmt.Printf used as logging across server and agent codebases. Tests added: - Server emoji: machine_binding.go, agents.go, update handlers (6 tests) - Server fmt.Printf: queries, handlers, services (6 tests) - Agent emoji: main.go log paths, migration executor (4 tests) - Exemptions: display/terminal.go, setup.go (2 tests, always pass) Current state: 8 FAIL, 8 PASS, 2 ALWAYS-PASS. All prior tests pass. No regressions. Co-Authored-By: Claude Opus 4.6 (1M context) --- aggregator-agent/cmd/ethos_emoji_test.go | 69 ++++++++++++++ .../internal/display/ethos_exempt_test.go | 43 +++++++++ .../internal/migration/ethos_emoji_test.go | 61 ++++++++++++ .../internal/api/handlers/ethos_emoji_test.go | 94 +++++++++++++++++++ .../api/handlers/ethos_logging_test.go | 50 ++++++++++ .../api/handlers/ethos_setup_exempt_test.go | 33 +++++++ .../api/middleware/ethos_emoji_test.go | 62 ++++++++++++ .../database/queries/ethos_logging_test.go | 51 ++++++++++ .../internal/services/ethos_logging_test.go | 37 ++++++++ docs/D2_PreFix_Tests.md | 53 +++++++++++ 10 files changed, 553 insertions(+) create mode 100644 aggregator-agent/cmd/ethos_emoji_test.go create mode 100644 aggregator-agent/internal/display/ethos_exempt_test.go create mode 100644 aggregator-agent/internal/migration/ethos_emoji_test.go create mode 100644 aggregator-server/internal/api/handlers/ethos_emoji_test.go create mode 100644 aggregator-server/internal/api/handlers/ethos_logging_test.go create mode 100644 aggregator-server/internal/api/handlers/ethos_setup_exempt_test.go create mode 100644 aggregator-server/internal/api/middleware/ethos_emoji_test.go create mode 100644 aggregator-server/internal/database/queries/ethos_logging_test.go create mode 100644 aggregator-server/internal/services/ethos_logging_test.go create mode 100644 docs/D2_PreFix_Tests.md diff --git a/aggregator-agent/cmd/ethos_emoji_test.go b/aggregator-agent/cmd/ethos_emoji_test.go new file mode 100644 index 0000000..7f3df7f --- /dev/null +++ b/aggregator-agent/cmd/ethos_emoji_test.go @@ -0,0 +1,69 @@ +package main + +// ethos_emoji_test.go — Pre-fix tests for emoji in agent main.go log statements. +// D-2: main.go has emoji in token renewal and install result log paths. +// EXCLUDES: registration CLI output (lines 294-322) and startup banner +// (lines 691-697) which are intentional user-facing output. + +import ( + "os" + "strings" + "testing" +) + +func hasEmojiRune(s string) bool { + for _, r := range s { + if r >= 0x1F300 || (r >= 0x2600 && r <= 0x27BF) { + return true + } + } + return false +} + +func TestMainGoHasEmojiInLogStatements(t *testing.T) { + // D-2: main.go has emoji in log statements for token renewal + // and install results. These are NOT user-facing CLI output. + content, err := os.ReadFile("agent/main.go") + if err != nil { + t.Fatalf("failed to read agent/main.go: %v", err) + } + + src := string(content) + lines := strings.Split(src, "\n") + + // Count emoji in log.Printf lines only (not fmt.Printf CLI output) + emojiLogCount := 0 + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if strings.Contains(trimmed, "log.Printf") && hasEmojiRune(trimmed) { + emojiLogCount++ + } + if strings.Contains(trimmed, "log.Println") && hasEmojiRune(trimmed) { + emojiLogCount++ + } + } + + if emojiLogCount == 0 { + t.Error("[ERROR] [agent] [main] D-2 already fixed: no emoji in main.go log statements") + } + + t.Logf("[INFO] [agent] [main] D-2 confirmed: %d log.Printf/Println with emoji in main.go", emojiLogCount) +} + +func TestMainGoLogStatementsHaveNoEmoji(t *testing.T) { + content, err := os.ReadFile("agent/main.go") + if err != nil { + t.Fatalf("failed to read agent/main.go: %v", err) + } + + src := string(content) + lines := strings.Split(src, "\n") + + for i, line := range lines { + trimmed := strings.TrimSpace(line) + isLog := strings.Contains(trimmed, "log.Printf") || strings.Contains(trimmed, "log.Println") + if isLog && hasEmojiRune(trimmed) { + t.Errorf("[ERROR] [agent] [main] emoji in log at line %d", i+1) + } + } +} diff --git a/aggregator-agent/internal/display/ethos_exempt_test.go b/aggregator-agent/internal/display/ethos_exempt_test.go new file mode 100644 index 0000000..97f50ac --- /dev/null +++ b/aggregator-agent/internal/display/ethos_exempt_test.go @@ -0,0 +1,43 @@ +package display + +// ethos_exempt_test.go — Exemption documentation for terminal display emoji. +// D-2: display/terminal.go emoji is EXEMPT from ETHOS #1. +// This is intentional user-facing terminal UI, not log output. +// Do NOT modify this file in the D-2 fix pass. + +import ( + "os" + "testing" +) + +func TestTerminalDisplayIsExemptFromEthos(t *testing.T) { + // D-2: display/terminal.go emoji is EXEMPT. + // This is intentional user-facing terminal UI. + // ETHOS #1 applies to log statements, not UI rendering. + _, err := os.Stat("terminal.go") + if err != nil { + t.Skip("[INFO] [agent] [display] terminal.go not found") + } + + content, err := os.ReadFile("terminal.go") + if err != nil { + t.Fatalf("failed to read terminal.go: %v", err) + } + + // Confirm emoji IS present (intentional) + hasEmoji := false + for _, r := range string(content) { + if r >= 0x1F300 || (r >= 0x2600 && r <= 0x27BF) { + hasEmoji = true + break + } + } + + if !hasEmoji { + t.Log("[INFO] [agent] [display] terminal.go has no emoji (ok — may have been cleaned)") + } else { + t.Log("[INFO] [agent] [display] terminal.go has emoji (EXEMPT — intentional terminal UI)") + } + + t.Log("[INFO] [agent] [display] EXEMPTION: display/terminal.go emoji is intentional UI, not log output") +} diff --git a/aggregator-agent/internal/migration/ethos_emoji_test.go b/aggregator-agent/internal/migration/ethos_emoji_test.go new file mode 100644 index 0000000..0c5b672 --- /dev/null +++ b/aggregator-agent/internal/migration/ethos_emoji_test.go @@ -0,0 +1,61 @@ +package migration + +// ethos_emoji_test.go — Pre-fix tests for emoji in migration executor output. +// D-2: migration/executor.go uses emoji in progress output. + +import ( + "os" + "strings" + "testing" +) + +func hasEmojiR(s string) bool { + for _, r := range s { + if r >= 0x1F300 || (r >= 0x2600 && r <= 0x27BF) { + return true + } + } + return false +} + +func TestMigrationExecutorHasEmojiInOutput(t *testing.T) { + // D-2: migration/executor.go uses emoji in progress output. + content, err := os.ReadFile("executor.go") + if err != nil { + t.Fatalf("failed to read executor.go: %v", err) + } + + src := string(content) + lines := strings.Split(src, "\n") + + emojiCount := 0 + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if (strings.Contains(trimmed, "fmt.Printf") || strings.Contains(trimmed, "fmt.Println")) && hasEmojiR(trimmed) { + emojiCount++ + } + } + + if emojiCount == 0 { + t.Error("[ERROR] [agent] [migration] D-2 already fixed: no emoji in executor.go") + } + + t.Logf("[INFO] [agent] [migration] D-2 confirmed: %d output lines with emoji in executor.go", emojiCount) +} + +func TestMigrationExecutorHasNoEmojiInOutput(t *testing.T) { + content, err := os.ReadFile("executor.go") + if err != nil { + t.Fatalf("failed to read executor.go: %v", err) + } + + src := string(content) + lines := strings.Split(src, "\n") + + for i, line := range lines { + trimmed := strings.TrimSpace(line) + if (strings.Contains(trimmed, "fmt.Printf") || strings.Contains(trimmed, "fmt.Println")) && hasEmojiR(trimmed) { + t.Errorf("[ERROR] [agent] [migration] emoji in output at line %d", i+1) + } + } +} diff --git a/aggregator-server/internal/api/handlers/ethos_emoji_test.go b/aggregator-server/internal/api/handlers/ethos_emoji_test.go new file mode 100644 index 0000000..dd16251 --- /dev/null +++ b/aggregator-server/internal/api/handlers/ethos_emoji_test.go @@ -0,0 +1,94 @@ +package handlers_test + +// ethos_emoji_test.go — Pre-fix tests for emoji in handler log statements. +// D-2: agents.go, agent_updates.go, update_handler.go, updates.go have emoji in logs. + +import ( + "os" + "strings" + "testing" +) + +func hasEmojiChar(s string) bool { + for _, r := range s { + if r >= 0x1F300 || (r >= 0x2600 && r <= 0x27BF) { + return true + } + } + return false +} + +func countEmojiLogLines(filepath string) (int, error) { + content, err := os.ReadFile(filepath) + if err != nil { + return 0, err + } + count := 0 + for _, line := range strings.Split(string(content), "\n") { + trimmed := strings.TrimSpace(line) + if (strings.Contains(trimmed, "log.Printf") || strings.Contains(trimmed, "log.Println")) && hasEmojiChar(trimmed) { + count++ + } + } + return count, nil +} + +func TestAgentsHandlerHasEmojiInLogs(t *testing.T) { + // D-2: agents.go has ~9 emoji in log statements including + // heartbeat, rapid mode, and status indicators. + count, err := countEmojiLogLines("agents.go") + if err != nil { + t.Fatalf("failed to read agents.go: %v", err) + } + + if count == 0 { + t.Error("[ERROR] [server] [handlers] D-2 already fixed: no emoji in agents.go logs") + } + + t.Logf("[INFO] [server] [handlers] D-2 confirmed: %d log statements with emoji in agents.go", count) +} + +func TestAgentsHandlerHasNoEmojiInLogs(t *testing.T) { + count, err := countEmojiLogLines("agents.go") + if err != nil { + t.Fatalf("failed to read agents.go: %v", err) + } + + if count > 0 { + t.Errorf("[ERROR] [server] [handlers] %d emoji-containing log statements in agents.go.\n"+ + "D-2: replace emoji with ETHOS [TAG] format text.", count) + } +} + +func TestUpdateHandlersHaveEmojiInLogs(t *testing.T) { + // D-2: agent_updates.go, update_handler.go, updates.go have emoji in logs. + files := []string{"agent_updates.go", "update_handler.go", "updates.go"} + total := 0 + for _, f := range files { + count, err := countEmojiLogLines(f) + if err != nil { + t.Logf("[WARNING] [server] [handlers] could not read %s: %v", f, err) + continue + } + total += count + } + + if total == 0 { + t.Error("[ERROR] [server] [handlers] D-2 already fixed: no emoji in update handler logs") + } + + t.Logf("[INFO] [server] [handlers] D-2 confirmed: %d emoji log lines across update handlers", total) +} + +func TestUpdateHandlersHaveNoEmojiInLogs(t *testing.T) { + files := []string{"agent_updates.go", "update_handler.go", "updates.go"} + for _, f := range files { + count, err := countEmojiLogLines(f) + if err != nil { + continue + } + if count > 0 { + t.Errorf("[ERROR] [server] [handlers] %d emoji log lines in %s", count, f) + } + } +} diff --git a/aggregator-server/internal/api/handlers/ethos_logging_test.go b/aggregator-server/internal/api/handlers/ethos_logging_test.go new file mode 100644 index 0000000..b9ce135 --- /dev/null +++ b/aggregator-server/internal/api/handlers/ethos_logging_test.go @@ -0,0 +1,50 @@ +package handlers_test + +// ethos_logging_test.go — Pre-fix tests for fmt.Printf used as logging in handlers. +// D-2: docker_reports.go and metrics.go use fmt.Printf for warnings. +// EXCLUDES: setup.go (legitimate CLI wizard output). + +import ( + "os" + "strings" + "testing" +) + +func TestHandlerFilesUseFmtPrintfForLogging(t *testing.T) { + // D-2: handlers use fmt.Printf for warning messages. + // EXCLUDES setup.go which is legitimate CLI output. + files := []string{"docker_reports.go", "metrics.go"} + total := 0 + + for _, f := range files { + content, err := os.ReadFile(f) + if err != nil { + t.Logf("[WARNING] [server] [handlers] could not read %s: %v", f, err) + continue + } + count := strings.Count(string(content), "fmt.Printf") + total += count + } + + if total == 0 { + t.Error("[ERROR] [server] [handlers] D-2 already fixed: no fmt.Printf in handler logs") + } + + t.Logf("[INFO] [server] [handlers] D-2 confirmed: %d fmt.Printf in docker_reports.go + metrics.go", total) +} + +func TestHandlerFilesUseStructuredLogging(t *testing.T) { + files := []string{"docker_reports.go", "metrics.go"} + + for _, f := range files { + content, err := os.ReadFile(f) + if err != nil { + continue + } + count := strings.Count(string(content), "fmt.Printf") + if count > 0 { + t.Errorf("[ERROR] [server] [handlers] %d fmt.Printf calls in %s.\n"+ + "D-2: use log.Printf with [TAG] [server] [handlers] format.", count, f) + } + } +} diff --git a/aggregator-server/internal/api/handlers/ethos_setup_exempt_test.go b/aggregator-server/internal/api/handlers/ethos_setup_exempt_test.go new file mode 100644 index 0000000..f377dab --- /dev/null +++ b/aggregator-server/internal/api/handlers/ethos_setup_exempt_test.go @@ -0,0 +1,33 @@ +package handlers_test + +// ethos_setup_exempt_test.go — Exemption documentation for setup wizard output. +// D-2: setup.go fmt.Printf is EXEMPT from ETHOS #1. +// The setup wizard is user-facing CLI output, not background log statements. +// Do NOT modify setup.go fmt.Printf in D-2 fix pass. + +import ( + "os" + "strings" + "testing" +) + +func TestSetupHandlerIsExemptFromEthos(t *testing.T) { + // D-2: setup.go fmt.Printf is EXEMPT. + // The setup wizard is user-facing CLI output. + // ETHOS #1 applies to background log statements. + content, err := os.ReadFile("setup.go") + if err != nil { + t.Fatalf("failed to read setup.go: %v", err) + } + + hasFmtPrint := strings.Contains(string(content), "fmt.Printf") || + strings.Contains(string(content), "fmt.Println") + + if !hasFmtPrint { + t.Log("[INFO] [server] [handlers] setup.go has no fmt.Printf (may have been restructured)") + } else { + t.Log("[INFO] [server] [handlers] setup.go uses fmt.Printf (EXEMPT — CLI wizard output)") + } + + t.Log("[INFO] [server] [handlers] EXEMPTION: setup.go fmt.Printf is intentional CLI output") +} diff --git a/aggregator-server/internal/api/middleware/ethos_emoji_test.go b/aggregator-server/internal/api/middleware/ethos_emoji_test.go new file mode 100644 index 0000000..5da3bcc --- /dev/null +++ b/aggregator-server/internal/api/middleware/ethos_emoji_test.go @@ -0,0 +1,62 @@ +package middleware_test + +// ethos_emoji_test.go — Pre-fix tests for emoji in middleware log statements. +// D-2: machine_binding.go uses emoji in security log messages. + +import ( + "os" + "strings" + "testing" +) + +func hasEmoji(s string) bool { + for _, r := range s { + if r >= 0x1F300 || (r >= 0x2600 && r <= 0x27BF) { + return true + } + } + return false +} + +func TestMachineBindingMiddlewareHasEmojiInLogs(t *testing.T) { + // D-2: machine_binding.go uses emoji in security alert and + // validation log messages. ETHOS #1 prohibits emoji in log output. + content, err := os.ReadFile("machine_binding.go") + if err != nil { + t.Fatalf("failed to read machine_binding.go: %v", err) + } + + src := string(content) + + // Find log lines with emoji + lines := strings.Split(src, "\n") + emojiLogCount := 0 + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if (strings.Contains(trimmed, "log.Printf") || strings.Contains(trimmed, "fmt.Printf")) && hasEmoji(trimmed) { + emojiLogCount++ + } + } + + if emojiLogCount == 0 { + t.Error("[ERROR] [server] [middleware] D-2 already fixed: no emoji in machine_binding.go logs") + } + + t.Logf("[INFO] [server] [middleware] D-2 confirmed: %d log statements with emoji in machine_binding.go", emojiLogCount) +} + +func TestMachineBindingMiddlewareHasNoEmojiInLogs(t *testing.T) { + content, err := os.ReadFile("machine_binding.go") + if err != nil { + t.Fatalf("failed to read machine_binding.go: %v", err) + } + + src := string(content) + lines := strings.Split(src, "\n") + for i, line := range lines { + trimmed := strings.TrimSpace(line) + if (strings.Contains(trimmed, "log.Printf") || strings.Contains(trimmed, "fmt.Printf")) && hasEmoji(trimmed) { + t.Errorf("[ERROR] [server] [middleware] emoji in log at line %d: %s", i+1, trimmed[:80]) + } + } +} diff --git a/aggregator-server/internal/database/queries/ethos_logging_test.go b/aggregator-server/internal/database/queries/ethos_logging_test.go new file mode 100644 index 0000000..0adc16f --- /dev/null +++ b/aggregator-server/internal/database/queries/ethos_logging_test.go @@ -0,0 +1,51 @@ +package queries_test + +// ethos_logging_test.go — Pre-fix tests for fmt.Printf used as logging. +// D-2: Database query files use fmt.Printf for warnings and cleanup. + +import ( + "os" + "strings" + "testing" +) + +func TestQueriesUseFmtPrintfForLogging(t *testing.T) { + // D-2: Database query files use fmt.Printf for warning and cleanup + // messages. These should use log.Printf with ETHOS format. + files := []string{"docker.go", "metrics.go", "updates.go"} + total := 0 + + for _, f := range files { + content, err := os.ReadFile(f) + if err != nil { + t.Logf("[WARNING] [server] [database] could not read %s: %v", f, err) + continue + } + count := strings.Count(string(content), "fmt.Printf") + count += strings.Count(string(content), "fmt.Println") + total += count + } + + if total == 0 { + t.Error("[ERROR] [server] [database] D-2 already fixed: no fmt.Printf in query files") + } + + t.Logf("[INFO] [server] [database] D-2 confirmed: %d fmt.Printf calls across query files", total) +} + +func TestQueriesUseStructuredLogging(t *testing.T) { + files := []string{"docker.go", "metrics.go", "updates.go"} + + for _, f := range files { + content, err := os.ReadFile(f) + if err != nil { + continue + } + count := strings.Count(string(content), "fmt.Printf") + count += strings.Count(string(content), "fmt.Println") + if count > 0 { + t.Errorf("[ERROR] [server] [database] %d fmt.Printf calls in %s.\n"+ + "D-2: use log.Printf with [TAG] [server] [database] format.", count, f) + } + } +} diff --git a/aggregator-server/internal/services/ethos_logging_test.go b/aggregator-server/internal/services/ethos_logging_test.go new file mode 100644 index 0000000..f49911f --- /dev/null +++ b/aggregator-server/internal/services/ethos_logging_test.go @@ -0,0 +1,37 @@ +package services_test + +// ethos_logging_test.go — Pre-fix tests for fmt.Printf in services. +// D-2: security_settings_service.go uses fmt.Printf for audit log warning. + +import ( + "os" + "strings" + "testing" +) + +func TestServicesUseFmtPrintfForLogging(t *testing.T) { + content, err := os.ReadFile("security_settings_service.go") + if err != nil { + t.Fatalf("failed to read security_settings_service.go: %v", err) + } + + count := strings.Count(string(content), "fmt.Printf") + if count == 0 { + t.Error("[ERROR] [server] [services] D-2 already fixed: no fmt.Printf in security_settings_service.go") + } + + t.Logf("[INFO] [server] [services] D-2 confirmed: %d fmt.Printf calls in security_settings_service.go", count) +} + +func TestServicesUseStructuredLogging(t *testing.T) { + content, err := os.ReadFile("security_settings_service.go") + if err != nil { + t.Fatalf("failed to read security_settings_service.go: %v", err) + } + + count := strings.Count(string(content), "fmt.Printf") + if count > 0 { + t.Errorf("[ERROR] [server] [services] %d fmt.Printf calls in security_settings_service.go.\n"+ + "D-2: use log.Printf with ETHOS format.", count) + } +} diff --git a/docs/D2_PreFix_Tests.md b/docs/D2_PreFix_Tests.md new file mode 100644 index 0000000..18501b9 --- /dev/null +++ b/docs/D2_PreFix_Tests.md @@ -0,0 +1,53 @@ +# D-2 Pre-Fix Test Suite + +**Date:** 2026-03-29 +**Branch:** culurien +**Purpose:** Document ETHOS violations BEFORE fixing them. + +--- + +## Test Files + +| File | Package | Type | Targets | +|------|---------|------|---------| +| `server/middleware/ethos_emoji_test.go` | `middleware_test` | Emoji | machine_binding.go | +| `server/handlers/ethos_emoji_test.go` | `handlers_test` | Emoji | agents.go, update handlers | +| `server/handlers/ethos_logging_test.go` | `handlers_test` | fmt.Printf | docker_reports.go, metrics.go | +| `server/handlers/ethos_setup_exempt_test.go` | `handlers_test` | Exemption | setup.go | +| `server/services/ethos_logging_test.go` | `services_test` | fmt.Printf | security_settings_service.go | +| `server/queries/ethos_logging_test.go` | `queries_test` | fmt.Printf | docker.go, metrics.go, updates.go | +| `agent/cmd/ethos_emoji_test.go` | `main` | Emoji | main.go log statements | +| `agent/migration/ethos_emoji_test.go` | `migration` | Emoji | executor.go | +| `agent/display/ethos_exempt_test.go` | `display` | Exemption | terminal.go | + +## Exemptions (NOT to be touched in D-2 fix) + +- `display/terminal.go` — intentional terminal UI emoji +- `handlers/setup.go` — CLI wizard output (fmt.Printf intentional) +- `cmd/agent/main.go` lines 294-322 — registration CLI output +- `cmd/agent/main.go` lines 691-697 — startup banner + +## State-Change Summary + +| Test | Type | Current | After Fix | +|------|------|---------|-----------| +| TestMachineBindingMiddlewareHasEmojiInLogs | Emoji | PASS | update | +| TestMachineBindingMiddlewareHasNoEmojiInLogs | Emoji | **FAIL** | PASS | +| TestAgentsHandlerHasEmojiInLogs | Emoji | PASS | update | +| TestAgentsHandlerHasNoEmojiInLogs | Emoji | **FAIL** | PASS | +| TestUpdateHandlersHaveEmojiInLogs | Emoji | PASS | update | +| TestUpdateHandlersHaveNoEmojiInLogs | Emoji | **FAIL** | PASS | +| TestHandlerFilesUseFmtPrintfForLogging | fmt.Printf | PASS | update | +| TestHandlerFilesUseStructuredLogging | fmt.Printf | **FAIL** | PASS | +| TestServicesUseFmtPrintfForLogging | fmt.Printf | PASS | update | +| TestServicesUseStructuredLogging | fmt.Printf | **FAIL** | PASS | +| TestQueriesUseFmtPrintfForLogging | fmt.Printf | PASS | update | +| TestQueriesUseStructuredLogging | fmt.Printf | **FAIL** | PASS | +| TestMainGoHasEmojiInLogStatements | Emoji | PASS | update | +| TestMainGoLogStatementsHaveNoEmoji | Emoji | **FAIL** | PASS | +| TestMigrationExecutorHasEmojiInOutput | Emoji | PASS | update | +| TestMigrationExecutorHasNoEmojiInOutput | Emoji | **FAIL** | PASS | +| TestTerminalDisplayIsExemptFromEthos | Exemption | PASS | PASS | +| TestSetupHandlerIsExemptFromEthos | Exemption | PASS | PASS | + +**8 FAIL** (assert post-fix), **8 PASS** (document state), **2 ALWAYS-PASS** (exemptions).