package config import ( "encoding/json" "net/url" "os" "path/filepath" "strings" "testing" ) func writeTempConfig(t *testing.T, content string) string { t.Helper() path := filepath.Join(t.TempDir(), "config.json") if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatal(err) } return path } func structuredConfigJSON() string { return strings.Replace(validConfigJSON(), `"rtsp_url_env": "SILVER_POSE_RTSP_URL",`, `"host": "192.0.2.10", "port": 554, "channel": "102", "username": "admin", "password": "p@ss/w:d",`, 1) } func TestStructuredSourceBuildsLosslessEncodedURL(t *testing.T) { cfg, err := Load(writeTempConfig(t, structuredConfigJSON())) if err != nil { t.Fatalf("load: %v", err) } if strings.Contains(cfg.Source.URL, "p@ss") { t.Fatalf("password @ was not percent-encoded: %q", cfg.Source.URL) } parsed, err := url.Parse(cfg.Source.URL) if err != nil { t.Fatalf("built URL does not parse: %v", err) } if parsed.Hostname() != "192.0.2.10" || parsed.Port() != "554" { t.Fatalf("host/port = %q/%q", parsed.Hostname(), parsed.Port()) } if parsed.User.Username() != "admin" { t.Fatalf("username = %q", parsed.User.Username()) } if password, _ := parsed.User.Password(); password != "p@ss/w:d" { t.Fatalf("password did not round-trip: %q", password) } if parsed.Path != "/Streaming/Channels/102" { t.Fatalf("path = %q", parsed.Path) } } func TestStructuredSourceRequiresPassword(t *testing.T) { noPassword := strings.Replace(structuredConfigJSON(), `"password": "p@ss/w:d",`, "", 1) if _, err := Load(writeTempConfig(t, noPassword)); err == nil || !strings.Contains(err.Error(), "password") { t.Fatalf("expected password error, got %v", err) } } func TestStructuredCredentialsExcludedFromConfigVersion(t *testing.T) { first, err := Load(writeTempConfig(t, structuredConfigJSON())) if err != nil { t.Fatal(err) } other := strings.Replace(structuredConfigJSON(), `"password": "p@ss/w:d"`, `"password": "different"`, 1) second, err := Load(writeTempConfig(t, other)) if err != nil { t.Fatal(err) } if first.Source.URL == second.Source.URL { t.Fatal("test setup did not change the credential") } if first.RuntimeConfigVersion != second.RuntimeConfigVersion { t.Fatal("runtime config version changed with a credential") } } func TestPublicExampleHasNoEmbeddedCredentials(t *testing.T) { data, err := os.ReadFile(filepath.Join("..", "..", "config.example.json")) if err != nil { t.Fatal(err) } var document struct { Source map[string]json.RawMessage `json:"source"` } if err := json.Unmarshal(data, &document); err != nil { t.Fatal(err) } for _, forbidden := range []string{"host", "username", "password", "url", "rtsp_url"} { if _, present := document.Source[forbidden]; present { t.Fatalf("public example leaked credential field %q", forbidden) } } if _, present := document.Source["rtsp_url_env"]; !present { t.Fatal("public example missing rtsp_url_env") } } func TestWriteLocalCameraSourceRoundTrips(t *testing.T) { path := writeTempConfig(t, validConfigJSON()) if err := WriteLocalCameraSource(path, "hik", "192.0.2.20", 554, "102", "operator", "s@cret", "tcp", 5, true); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatalf("reload: %v", err) } if cfg.Source.ID != "hik" { t.Fatalf("id = %q", cfg.Source.ID) } parsed, _ := url.Parse(cfg.Source.URL) if parsed.Hostname() != "192.0.2.20" { t.Fatalf("host = %q", parsed.Hostname()) } if password, _ := parsed.User.Password(); password != "s@cret" { t.Fatalf("password = %q", password) } } func TestWriteLocalEventTuningRoundTrips(t *testing.T) { path := writeTempConfig(t, validConfigJSON()) t.Setenv("SILVER_POSE_RTSP_URL", "rtsp://demo.invalid/live") if err := WriteLocalEventTuning(path, 0.5, 2.5, 35, 0.3, true, true); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatalf("reload: %v", err) } if cfg.Event.ConfirmWindowSeconds != 2.5 || cfg.Event.HorizontalAngleThresholdDegrees != 35 { t.Fatalf("event tuning not applied: %+v", cfg.Event) } if !cfg.Event.RequireRapidDrop || !cfg.Event.RequireLowerBody { t.Fatalf("boolean tuning not applied: %+v", cfg.Event) } if cfg.Model.ConfidenceThreshold != 0.3 { t.Fatalf("model confidence = %v", cfg.Model.ConfidenceThreshold) } }