package config import ( "path/filepath" "testing" "time" ) func TestValidateRejectsNonLoopbackByDefault(t *testing.T) { t.Parallel() cfg := Config{ HTTPAddress: "0.0.0.0:8080", DatabaseDSN: "file:test.db", MediaMTXURL: "http://127.0.0.1:9997", ReconcileInterval: 1, ProbeInterval: 1, } if err := cfg.Validate(); err == nil { t.Fatal("expected non-loopback bind to be rejected") } cfg.AllowNonLoopback = true if err := cfg.Validate(); err != nil { t.Fatalf("explicit non-loopback opt-in failed: %v", err) } } func TestValidateReconcileLeaseAndOrphanProductionBoundary(t *testing.T) { base := Config{ HTTPAddress: "127.0.0.1:8080", DatabaseDriver: "postgres", DatabaseDSN: "postgres://sense-runtime@127.0.0.1/yovision?sslmode=disable", MediaMTXURL: "http://127.0.0.1:9997", ReconcileInterval: time.Second, ProbeInterval: time.Second, ReconcileLeaseDuration: 30 * time.Second, ReconcileOperationTimeout: 20 * time.Second, OrphanScanEnabled: true, OrphanScanInterval: time.Minute, InstanceID: "ins_edge-01", } if err := base.Validate(); err != nil { t.Fatalf("valid multi-instance configuration failed: %v", err) } invalidTimeout := base invalidTimeout.ReconcileOperationTimeout = invalidTimeout.ReconcileLeaseDuration if err := invalidTimeout.Validate(); err == nil { t.Fatal("operation timeout equal to the lease was accepted") } invalidInstance := base invalidInstance.InstanceID = "tenant/site" if err := invalidInstance.Validate(); err == nil { t.Fatal("unbounded instance label was accepted") } sqlite := base sqlite.DatabaseDriver, sqlite.DatabaseDSN = "sqlite", "file:test.db" if err := sqlite.Validate(); err == nil { t.Fatal("orphan scanner was accepted on SQLite") } } func TestValidateRejectsCredentialsInMediaMTXURL(t *testing.T) { t.Parallel() cfg := Config{ HTTPAddress: "127.0.0.1:8080", DatabaseDSN: "file:test.db", MediaMTXURL: "http://" + "user" + ":" + "redacted" + "@127.0.0.1:9997", ReconcileInterval: 1, ProbeInterval: 1, } if err := cfg.Validate(); err == nil { t.Fatal("expected credentials in MediaMTX URL to be rejected") } } func TestValidateONVIFModeAndRewritePort(t *testing.T) { t.Parallel() cfg := Config{ HTTPAddress: "127.0.0.1:8080", DatabaseDSN: "file:test.db", MediaMTXURL: "http://127.0.0.1:9997", ReconcileInterval: 1, ProbeInterval: 1, ONVIFMode: "standard", RTSPRewriteHost: "127.0.0.1", RTSPRewritePort: 10554, } if err := cfg.Validate(); err != nil { t.Fatalf("valid ONVIF configuration failed: %v", err) } cfg.ONVIFMode = "vendor" if err := cfg.Validate(); err == nil { t.Fatal("unknown ONVIF mode must be rejected") } cfg.ONVIFMode = "standard" cfg.RTSPRewritePort = 65536 if err := cfg.Validate(); err == nil { t.Fatal("invalid RTSP rewrite port must be rejected") } } func TestLoadRTSPStripQueryOptIn(t *testing.T) { t.Setenv("SENSE_ONVIF_RTSP_STRIP_QUERY", "true") cfg, err := Load() if err != nil { t.Fatal(err) } if !cfg.RTSPStripQuery { t.Fatal("explicit RTSP query stripping was not loaded") } } func TestValidateDatabaseDriver(t *testing.T) { t.Parallel() cfg := Config{ HTTPAddress: "127.0.0.1:8080", DatabaseDriver: "postgres", DatabaseDSN: "file:test.db", MediaMTXURL: "http://127.0.0.1:9997", ReconcileInterval: 1, ProbeInterval: 1, } if err := cfg.Validate(); err == nil { t.Fatal("postgres driver must reject the SQLite default DSN") } cfg.DatabaseDSN = "postgres://sense-runtime@127.0.0.1/yovision?sslmode=disable" if err := cfg.Validate(); err != nil { t.Fatalf("valid PostgreSQL selection failed: %v", err) } cfg.DatabaseDriver = "mysql" if err := cfg.Validate(); err == nil { t.Fatal("unknown database driver must be rejected") } } func TestValidateControlAPIRequiresPostgresAndExternalSecurityFiles(t *testing.T) { cfg := Config{ HTTPAddress: "127.0.0.1:8080", DatabaseDriver: "sqlite", DatabaseDSN: "file:test.db", MediaMTXURL: "http://127.0.0.1:9997", ReconcileInterval: 1, ProbeInterval: 1, ControlAPIEnabled: true, ControlAuthMode: "static-sha256", ControlAuthFile: filepath.Join(t.TempDir(), "sense-auth.json"), ControlCursorKeyFile: filepath.Join(t.TempDir(), "sense-cursor.key"), } if err := cfg.Validate(); err == nil { t.Fatal("Control API was accepted on SQLite") } cfg.DatabaseDriver = "postgres" cfg.DatabaseDSN = "postgres://sense-runtime@127.0.0.1/yovision?sslmode=disable" if err := cfg.Validate(); err != nil { t.Fatalf("valid Control API configuration failed: %v", err) } cfg.ControlAuthFile = "relative-auth.json" if err := cfg.Validate(); err == nil { t.Fatal("repository-relative authentication file was accepted") } } func TestValidateControlAPINonLoopbackNeedsSeparateRiskAcceptance(t *testing.T) { cfg := Config{ HTTPAddress: "0.0.0.0:8080", AllowNonLoopback: true, DatabaseDriver: "postgres", DatabaseDSN: "postgres://sense-runtime@127.0.0.1/yovision?sslmode=disable", MediaMTXURL: "http://127.0.0.1:9997", ReconcileInterval: 1, ProbeInterval: 1, ControlAPIEnabled: true, ControlAuthMode: "static-sha256", ControlAuthFile: filepath.Join(t.TempDir(), "sense-auth.json"), ControlCursorKeyFile: filepath.Join(t.TempDir(), "sense-cursor.key"), } if err := cfg.Validate(); err == nil { t.Fatal("non-loopback plaintext Control API was accepted without explicit risk acceptance") } cfg.ControlAllowInsecureHTTP = true if err := cfg.Validate(); err != nil { t.Fatalf("explicit non-loopback Control API risk acceptance failed: %v", err) } } func TestValidateAuditRelaySecurityBoundary(t *testing.T) { base := Config{ HTTPAddress: "127.0.0.1:8080", DatabaseDriver: "postgres", DatabaseDSN: "postgres://sense-runtime@127.0.0.1/yovision?sslmode=disable", MediaMTXURL: "http://127.0.0.1:9997", ReconcileInterval: time.Second, ProbeInterval: time.Second, AuditRelayEnabled: true, AuditRelayURL: "http://127.0.0.1:8081/internal/v1/audit-events:batch", AuditRelayKeyFile: filepath.Join(t.TempDir(), "relay-keys.json"), AuditRelayKeyID: "sense-a", AuditRelayInterval: time.Second, } if err := base.Validate(); err != nil { t.Fatalf("valid loopback relay rejected: %v", err) } remoteHTTP := base remoteHTTP.AuditRelayURL = "http://bell.example/internal/v1/audit-events:batch" if err := remoteHTTP.Validate(); err == nil { t.Fatal("remote plaintext relay was accepted") } sqlite := base sqlite.DatabaseDriver, sqlite.DatabaseDSN = "sqlite", "file:test.db" if err := sqlite.Validate(); err == nil { t.Fatal("SQLite audit relay was accepted") } relativeKey := base relativeKey.AuditRelayKeyFile = "relay-keys.json" if err := relativeKey.Validate(); err == nil { t.Fatal("repository-relative relay key was accepted") } }