package main import ( "os" "strings" "testing" "time" ) func TestLoadConfig(t *testing.T) { tests := []struct { name string yamlContent string wantError bool checkFields func(*testing.T, Config) }{ { name: "valid config with all fields", yamlContent: `listen: - ":8080" checkpoints: "checkpoints" roots: "roots.pem" logs: - shortname: "test-log" inception: "2024-01-01T00:00:00Z" period: 300 poolsize: 1000 submissionprefix: "https://example.com/submit" monitoringprefix: "https://example.com/monitor" secret: "test.key" localdirectory: "test-dir" notafterstart: "2024-01-01T00:00:00Z" notafterlimit: "2025-01-01T00:00:00Z"`, wantError: false, checkFields: func(t *testing.T, config Config) { if len(config.Listen) != 1 || config.Listen[0] != ":8080" { t.Errorf("Expected Listen [\":8080\"], got %v", config.Listen) } if config.Checkpoints != "checkpoints" { t.Errorf("Expected Checkpoints \"checkpoints\", got %s", config.Checkpoints) } if len(config.Logs) != 1 { t.Errorf("Expected 1 log, got %d", len(config.Logs)) } log := config.Logs[0] if log.ShortName != "test-log" { t.Errorf("Expected ShortName \"test-log\", got %s", log.ShortName) } if log.Period != 300 { t.Errorf("Expected Period 300, got %d", log.Period) } if log.PoolSize != 1000 { t.Errorf("Expected PoolSize 1000, got %d", log.PoolSize) } if log.Origin != "example.com" { t.Errorf("Expected Origin \"example.com\", got %s", log.Origin) } }, }, { name: "config with defaults", yamlContent: `logs: - shortname: "minimal-log" submissionprefix: "https://test.example.com/submit" secret: "test.key" localdirectory: "test-dir"`, wantError: false, checkFields: func(t *testing.T, config Config) { if len(config.Listen) != 1 || config.Listen[0] != ":8080" { t.Errorf("Expected default Listen [\":8080\"], got %v", config.Listen) } if len(config.Logs) != 1 { t.Errorf("Expected 1 log, got %d", len(config.Logs)) } log := config.Logs[0] if log.Period != 200 { t.Errorf("Expected default Period 200, got %d", log.Period) } if log.PoolSize != 750 { t.Errorf("Expected default PoolSize 750, got %d", log.PoolSize) } if log.Origin != "test.example.com" { t.Errorf("Expected Origin \"test.example.com\", got %s", log.Origin) } }, }, { name: "invalid yaml", yamlContent: `invalid: yaml: content: - malformed`, wantError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tmpfile, err := os.CreateTemp("", "test-config-*.yaml") if err != nil { t.Fatal(err) } defer os.Remove(tmpfile.Name()) _, err = tmpfile.WriteString(tt.yamlContent) if err != nil { t.Fatal(err) } tmpfile.Close() if tt.wantError { // Can't easily test log.Fatalf without subprocess t.Skip("Cannot easily test log.Fatalf without subprocess") } else { config := loadConfig(tmpfile.Name()) if tt.checkFields != nil { tt.checkFields(t, config) } } }) } } func TestExtractHostname(t *testing.T) { tests := []struct { name string urlStr string want string wantErr bool }{ { name: "https URL", urlStr: "https://example.com/path", want: "example.com", wantErr: false, }, { name: "http URL", urlStr: "http://test.example.com:8080/path", want: "test.example.com:8080", wantErr: false, }, { name: "URL with port", urlStr: "https://log.example.com:9090/monitor", want: "log.example.com:9090", wantErr: false, }, { name: "invalid URL", urlStr: "://invalid-url", want: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := extractHostname(tt.urlStr) if (err != nil) != tt.wantErr { t.Errorf("extractHostname() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("extractHostname() = %v, want %v", got, tt.want) } }) } } func TestExtractPort(t *testing.T) { tests := []struct { name string listenAddr string want string }{ { name: "port only", listenAddr: ":8080", want: "8080", }, { name: "localhost with port", listenAddr: "localhost:9090", want: "9090", }, { name: "IPv6 with port", listenAddr: "[::]:8080", want: "8080", }, { name: "no port", listenAddr: "localhost", want: "", }, { name: "empty string", listenAddr: "", want: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := extractPort(tt.listenAddr) if got != tt.want { t.Errorf("extractPort() = %v, want %v", got, tt.want) } }) } } func TestColorizeUnifiedDiff(t *testing.T) { diff := `--- file1.txt +++ file2.txt @@ -1,3 +1,3 @@ line1 -old line +new line line3` result := colorizeUnifiedDiff(diff) if !strings.Contains(result, colorCyan) { t.Error("Expected diff to contain cyan color codes") } if !strings.Contains(result, colorYellow) { t.Error("Expected diff to contain yellow color codes") } if !strings.Contains(result, colorRed) { t.Error("Expected diff to contain red color codes") } if !strings.Contains(result, colorGreen) { t.Error("Expected diff to contain green color codes") } if !strings.Contains(result, colorReset) { t.Error("Expected diff to contain reset color codes") } } func TestWriteFileWithStatus(t *testing.T) { tmpDir := t.TempDir() testFile := tmpDir + "/test.txt" content := []byte("test content") err := writeFileWithStatus(testFile, content, false, true, false) if err != nil { t.Errorf("writeFileWithStatus() error = %v", err) } writtenContent, err := os.ReadFile(testFile) if err != nil { t.Fatalf("Failed to read written file: %v", err) } if string(writtenContent) != string(content) { t.Errorf("Written content = %s, want %s", string(writtenContent), string(content)) } err = writeFileWithStatus(testFile, content, false, false, false) if err != nil { t.Errorf("writeFileWithStatus() with allowWrite=false should not error: %v", err) } } func TestTimeFormats(t *testing.T) { testTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) expected := "2024-01-01T12:00:00Z" formatted := testTime.Format("2006-01-02T15:04:05Z") if formatted != expected { t.Errorf("Time format = %s, want %s", formatted, expected) } }