feat(07-02): implement PrintConfig function with comment annotations

- Add AutoClasses map[TrafficClass]bool to LoadResult for tracking auto-assigned classes
- PrintConfig() returns commented TOML with header (source, date), [[rules]] section, [sounds.*] section
- waveformString() helper converts WaveformType back to string
- classAnnotation() returns default/override/auto-assigned per class
- Built-in classes emitted in AllClasses() order; user-defined classes sorted alphabetically
- Rules section emits [[rules]] blocks; port omitted when DstPort==0
- Tests: TestPrintConfigContainsAllClasses, TestPrintConfigSourcePath, TestPrintConfigNoSourcePath,
  TestPrintConfigContainsRules, TestPrintConfigRuleNoPort, TestPrintConfigDefaultAnnotation,
  TestPrintConfigOverrideAnnotation, TestPrintConfigAutoAssignedAnnotation
This commit is contained in:
2026-03-26 21:51:27 +01:00
parent d43914f869
commit b52e36b579
2 changed files with 295 additions and 10 deletions
+166
View File
@@ -392,6 +392,172 @@ class = "MyDeterministicClass"
}
}
// --- PrintConfig tests ---
// TestPrintConfigContainsAllClasses: defaults LoadResult produces output with all 14 class names.
func TestPrintConfigContainsAllClasses(t *testing.T) {
t.Chdir(t.TempDir())
result, err := config.Load("")
if err != nil {
t.Fatalf("Load: %v", err)
}
output := config.PrintConfig(result)
classNames := []string{
"ICMP", "DNS", "HTTPS", "HTTP", "SSH", "SMTP",
"NTP", "DHCP", "other-TCP", "other-UDP",
"unknown-1", "unknown-2", "unknown-3", "unknown-4",
}
for _, name := range classNames {
if !strings.Contains(output, name) {
t.Errorf("PrintConfig output missing class %q", name)
}
}
}
// TestPrintConfigSourcePath: LoadResult with ConfigPath set shows the path in header.
func TestPrintConfigSourcePath(t *testing.T) {
path := writeTOML(t, "[sounds.ICMP]\nfrequency = 100.0\n")
result, err := config.Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
output := config.PrintConfig(result)
if !strings.Contains(output, "# Config source: "+path) {
t.Errorf("expected output to contain '# Config source: %s', got:\n%s", path, output)
}
}
// TestPrintConfigNoSourcePath: LoadResult with no ConfigPath shows "none" in header.
func TestPrintConfigNoSourcePath(t *testing.T) {
t.Chdir(t.TempDir())
result, err := config.Load("")
if err != nil {
t.Fatalf("Load: %v", err)
}
output := config.PrintConfig(result)
if !strings.Contains(output, "# Config source: none") {
t.Errorf("expected output to contain '# Config source: none', got:\n%s", output)
}
}
// TestPrintConfigContainsRules: LoadResult with user rules emits [[rules]] section.
func TestPrintConfigContainsRules(t *testing.T) {
tomlContent := `
[[rules]]
port = 8080
protocol = "tcp"
class = "MyApp"
`
path := writeTOML(t, tomlContent)
result, err := config.Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
output := config.PrintConfig(result)
if !strings.Contains(output, "[[rules]]") {
t.Errorf("expected output to contain '[[rules]]', got:\n%s", output)
}
if !strings.Contains(output, "port = 8080") {
t.Errorf("expected output to contain 'port = 8080', got:\n%s", output)
}
if !strings.Contains(output, `protocol = "tcp"`) {
t.Errorf("expected output to contain 'protocol = \"tcp\"', got:\n%s", output)
}
if !strings.Contains(output, `class = "MyApp"`) {
t.Errorf("expected output to contain 'class = \"MyApp\"', got:\n%s", output)
}
}
// TestPrintConfigRuleNoPort: rule with DstPort==0 omits port line in output.
func TestPrintConfigRuleNoPort(t *testing.T) {
tomlContent := `
[[rules]]
protocol = "udp"
class = "AllUDP"
`
path := writeTOML(t, tomlContent)
result, err := config.Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
output := config.PrintConfig(result)
// Find the [[rules]] block and check no port line follows before the next blank line
rulesIdx := strings.Index(output, "[[rules]]")
if rulesIdx < 0 {
t.Fatal("expected [[rules]] in output")
}
rulesSection := output[rulesIdx:]
// Extract until the next blank line after [[rules]]
lines := strings.Split(rulesSection, "\n")
for _, line := range lines {
if strings.HasPrefix(strings.TrimSpace(line), "port =") {
t.Errorf("expected no 'port =' line for rule with DstPort=0, got line: %q", line)
}
if line == "" {
break // end of this rule block
}
}
}
// TestPrintConfigDefaultAnnotation: default ICMP entry annotated as (default).
func TestPrintConfigDefaultAnnotation(t *testing.T) {
t.Chdir(t.TempDir())
result, err := config.Load("")
if err != nil {
t.Fatalf("Load: %v", err)
}
output := config.PrintConfig(result)
if !strings.Contains(output, "(default)") {
t.Errorf("expected '(default)' annotation in output, got:\n%s", output)
}
// Specifically check ICMP line
if !strings.Contains(output, "ICMP") {
t.Errorf("expected ICMP in output")
}
}
// TestPrintConfigOverrideAnnotation: ICMP with changed BaseHz annotated as (override).
func TestPrintConfigOverrideAnnotation(t *testing.T) {
path := writeTOML(t, "[sounds.ICMP]\nfrequency = 100.0\n")
result, err := config.Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
output := config.PrintConfig(result)
// Find the ICMP comment line and check annotation
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, "# ICMP") {
if !strings.Contains(line, "(override)") {
t.Errorf("expected ICMP comment to contain '(override)', got: %q", line)
}
return
}
}
t.Error("did not find ICMP comment line in output")
}
// TestPrintConfigAutoAssignedAnnotation: user-defined class gets (auto-assigned) annotation.
func TestPrintConfigAutoAssignedAnnotation(t *testing.T) {
tomlContent := `
[[rules]]
protocol = "tcp"
class = "GameServer"
`
path := writeTOML(t, tomlContent)
result, err := config.Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
output := config.PrintConfig(result)
if !strings.Contains(output, "(auto-assigned)") {
t.Errorf("expected '(auto-assigned)' annotation in output, got:\n%s", output)
}
if !strings.Contains(output, "GameServer") {
t.Errorf("expected GameServer in output")
}
}
// TestAutoFreqSkipsBuiltins: TOML with [[rules]] (class="HTTPS", protocol="tcp", port=443)
// -> FreqCfgs["HTTPS"].BaseHz == 175.0 (the default), NOT an auto-assigned value.
func TestAutoFreqSkipsBuiltins(t *testing.T) {