package store import ( "strings" "testing" ) func TestCreateLogScanRule_Validates(t *testing.T) { s := newTestStore(t) cases := []struct { name string in LogScanRule wantErr string }{ { name: "missing name", in: LogScanRule{Pattern: "x"}, wantErr: "name is required", }, { name: "missing pattern", in: LogScanRule{Name: "n"}, wantErr: "pattern is required", }, { name: "bad severity", in: LogScanRule{Name: "n", Pattern: "x", Severity: "loud"}, wantErr: "invalid severity", }, { name: "bad streams", in: LogScanRule{Name: "n", Pattern: "x", Streams: "both"}, wantErr: "invalid streams", }, { name: "negative cooldown", in: LogScanRule{Name: "n", Pattern: "x", CooldownSeconds: -1}, wantErr: "cooldown_seconds must be", }, { name: "override without workload", in: LogScanRule{Name: "n", Pattern: "x", OverridesID: 5}, wantErr: "override row requires workload_id", }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { _, err := s.CreateLogScanRule(c.in) if err == nil { t.Fatalf("expected error containing %q, got nil", c.wantErr) } if !strings.Contains(err.Error(), c.wantErr) { t.Fatalf("error mismatch: got %q want substring %q", err.Error(), c.wantErr) } }) } } func TestCreateAndGetLogScanRule(t *testing.T) { s := newTestStore(t) r, err := s.CreateLogScanRule(LogScanRule{ Name: "panics", Pattern: `\bpanic\b`, Severity: "error", Streams: "stderr", CooldownSeconds: 30, Enabled: true, }) if err != nil { t.Fatalf("create: %v", err) } if r.ID == 0 { t.Fatal("id should be set") } got, err := s.GetLogScanRule(r.ID) if err != nil { t.Fatalf("get: %v", err) } if got.Pattern != `\bpanic\b` { t.Errorf("pattern mismatch: %q", got.Pattern) } if !got.Enabled { t.Error("enabled lost on round-trip") } } func TestEffectiveLogScanRules(t *testing.T) { s := newTestStore(t) g, _ := s.CreateLogScanRule(LogScanRule{ Name: "global", Pattern: "panic", Severity: "warn", Streams: "all", Enabled: true, }) _, _ = s.CreateLogScanRule(LogScanRule{ Name: "w1-only", Pattern: "slow_query", WorkloadID: "w1", Severity: "info", Streams: "all", Enabled: true, }) _, _ = s.CreateLogScanRule(LogScanRule{ Name: "override-for-w1", Pattern: "panic", WorkloadID: "w1", OverridesID: g.ID, Severity: "error", Streams: "all", Enabled: true, }) w1, err := s.EffectiveLogScanRules("w1") if err != nil { t.Fatalf("effective w1: %v", err) } if len(w1) != 2 { t.Fatalf("w1 effective should be 2 (override + addition), got %d", len(w1)) } // First entry replaces the global with the override (error severity). if w1[0].Severity != "error" { t.Errorf("override severity not applied: %q", w1[0].Severity) } w2, err := s.EffectiveLogScanRules("w2") if err != nil { t.Fatalf("effective w2: %v", err) } if len(w2) != 1 { t.Fatalf("w2 effective should be 1 (just the global), got %d", len(w2)) } if w2[0].Severity != "warn" { t.Errorf("w2 should see original severity: %q", w2[0].Severity) } } func TestDeleteLogScanRule_CascadesOverrides(t *testing.T) { s := newTestStore(t) g, _ := s.CreateLogScanRule(LogScanRule{ Name: "global", Pattern: "panic", Severity: "warn", Streams: "all", Enabled: true, }) ov, _ := s.CreateLogScanRule(LogScanRule{ Name: "override", Pattern: "panic", WorkloadID: "w1", OverridesID: g.ID, Severity: "error", Streams: "all", Enabled: true, }) if err := s.DeleteLogScanRule(g.ID); err != nil { t.Fatalf("delete: %v", err) } if _, err := s.GetLogScanRule(ov.ID); err == nil { t.Error("override should be cascade-deleted with its global") } } func TestUpdateLogScanRule(t *testing.T) { s := newTestStore(t) r, _ := s.CreateLogScanRule(LogScanRule{ Name: "n", Pattern: "x", Severity: "warn", Streams: "all", Enabled: true, }) r.Pattern = "y" r.Enabled = false got, err := s.UpdateLogScanRule(r) if err != nil { t.Fatalf("update: %v", err) } if got.Pattern != "y" { t.Errorf("pattern not updated: %q", got.Pattern) } if got.Enabled { t.Error("enabled=false not applied") } }