diff --git a/config/config_test.go b/config/config_test.go index 4af4d8b..bb583da 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -600,6 +600,91 @@ func TestLoadResultConfigPath(t *testing.T) { } } +// --- Tests for Phase 11 Plan 02 (GRP-02, GRP-03) --- + +// TestPrintConfigGroupHeaders verifies GRP-02: group section headers appear in canonical order. +func TestPrintConfigGroupHeaders(t *testing.T) { + t.Chdir(t.TempDir()) + result, err := config.Load("") + if err != nil { + t.Fatalf("Load: %v", err) + } + output := config.PrintConfig(result) + + // Verify all populated group headers appear + expectedGroups := []string{"# Infrastructure", "# Web", "# Mail", "# Remote Access", "# File Transfer", "# Database", "# VoIP", "# Unknown"} + for _, header := range expectedGroups { + if !strings.Contains(output, header+"\n") { + t.Errorf("PrintConfig output missing group header %q", header) + } + } + + // Verify canonical order: Infrastructure before Web before Mail etc. + infraIdx := strings.Index(output, "# Infrastructure\n") + webIdx := strings.Index(output, "# Web\n") + mailIdx := strings.Index(output, "# Mail\n") + remoteIdx := strings.Index(output, "# Remote Access\n") + ftIdx := strings.Index(output, "# File Transfer\n") + dbIdx := strings.Index(output, "# Database\n") + voipIdx := strings.Index(output, "# VoIP\n") + unknownIdx := strings.Index(output, "# Unknown\n") + + if infraIdx >= webIdx || webIdx >= mailIdx || mailIdx >= remoteIdx || + remoteIdx >= ftIdx || ftIdx >= dbIdx || dbIdx >= voipIdx || voipIdx >= unknownIdx { + t.Errorf("Group headers not in canonical order: infra=%d web=%d mail=%d remote=%d ft=%d db=%d voip=%d unknown=%d", + infraIdx, webIdx, mailIdx, remoteIdx, ftIdx, dbIdx, voipIdx, unknownIdx) + } +} + +// TestLoadGroupOverride verifies GRP-03: [groups] reassigns a class to a different group. +func TestLoadGroupOverride(t *testing.T) { + path := writeTOML(t, "[groups]\nIMAP = \"Web\"\n") + result, err := config.Load(path) + if err != nil { + t.Fatalf("Load: %v", err) + } + cfg := result.FreqCfgs[classify.ClassIMAP] + if cfg.Group != "Web" { + t.Errorf("IMAP Group: got %q, want %q", cfg.Group, "Web") + } + // Frequency and waveform unchanged (D-08) + defaultCfg := synth.ClassFreqConfigs[classify.ClassIMAP] + if cfg.BaseHz != defaultCfg.BaseHz { + t.Errorf("IMAP BaseHz changed: got %v, want %v (should be unchanged by group reassignment)", cfg.BaseHz, defaultCfg.BaseHz) + } +} + +// TestLoadGroupUnknownClass verifies D-09: unknown class in [groups] produces no error. +func TestLoadGroupUnknownClass(t *testing.T) { + path := writeTOML(t, "[groups]\nBOGUS = \"Web\"\n") + result, err := config.Load(path) + if err != nil { + t.Fatalf("Load should not error on unknown [groups] class: %v", err) + } + // Should still have all default classes + if len(result.FreqCfgs) != len(classify.AllClasses()) { + t.Errorf("FreqCfgs len: got %d, want %d", len(result.FreqCfgs), len(classify.AllClasses())) + } +} + +// TestPrintConfigGroupReassignment verifies PrintConfig reflects [groups] reassignment. +func TestPrintConfigGroupReassignment(t *testing.T) { + path := writeTOML(t, "[groups]\nIMAP = \"Web\"\n") + result, err := config.Load(path) + if err != nil { + t.Fatalf("Load: %v", err) + } + output := config.PrintConfig(result) + + // Find the "# Web" section and check IMAP appears after it + webIdx := strings.Index(output, "# Web\n") + mailIdx := strings.Index(output, "# Mail\n") + imapIdx := strings.Index(output, "[sounds.IMAP]") + if imapIdx < webIdx || imapIdx > mailIdx { + t.Errorf("IMAP (reassigned to Web) should appear between Web and Mail headers; web=%d imap=%d mail=%d", webIdx, imapIdx, mailIdx) + } +} + // TestLoadNoConfigReturnsLoadResult: Load("") in empty dir returns LoadResult with // len(FreqCfgs)==14, len(UserRules)==0, ConfigPath=="". func TestLoadNoConfigReturnsLoadResult(t *testing.T) {