package store import "testing" func TestExtractStructured(t *testing.T) { boolPtr := func(b bool) *bool { return &b } tests := []struct { name string query string remaining string statusSQL string statusArgs []any source string flagged *bool tag string }{ { name: "plain text only", query: "admin login", remaining: "admin login", }, { name: "flagged true", query: "flagged:true", remaining: "", flagged: boolPtr(true), }, { name: "flagged false", query: "flagged:no", remaining: "", flagged: boolPtr(false), }, { name: "not a real flagged token falls through as text", query: "flagged:maybe", remaining: "flagged:maybe", }, { name: "exact status", query: "status:404", remaining: "", statusSQL: "status_code = ?", statusArgs: []any{404}, }, { name: "status with operator", query: "status:>=400", remaining: "", statusSQL: "status_code >= ?", statusArgs: []any{400}, }, { name: "status not-equal", query: "status:!=200", remaining: "", statusSQL: "status_code != ?", statusArgs: []any{200}, }, { name: "status range shorthand", query: "status:4xx", remaining: "", statusSQL: "status_code >= ? AND status_code < ?", statusArgs: []any{400, 500}, }, { name: "source filter", query: "source:repeater", remaining: "", source: "repeater", }, { name: "tag filter", query: "tag:jwt", remaining: "", tag: "jwt", }, { name: "tag filter preserves case", query: "tag:JWT", remaining: "", tag: "JWT", }, { name: "combined with free text", query: "admin status:>=400 source:proxy", remaining: "admin", statusSQL: "status_code >= ?", statusArgs: []any{400}, source: "proxy", }, { name: "not a real status token falls through as text", query: "status:banana", remaining: "status:banana", }, { name: "malformed 4-digit status falls through as text", query: "status:4004", remaining: "status:4004", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { remaining, pred := extractStructured(tt.query) if remaining != tt.remaining { t.Errorf("remaining = %q, want %q", remaining, tt.remaining) } if pred.statusSQL != tt.statusSQL { t.Errorf("statusSQL = %q, want %q", pred.statusSQL, tt.statusSQL) } if len(pred.statusArgs) != len(tt.statusArgs) { t.Fatalf("statusArgs = %v, want %v", pred.statusArgs, tt.statusArgs) } for i := range pred.statusArgs { if pred.statusArgs[i] != tt.statusArgs[i] { t.Errorf("statusArgs[%d] = %v, want %v", i, pred.statusArgs[i], tt.statusArgs[i]) } } if pred.source != tt.source { t.Errorf("source = %q, want %q", pred.source, tt.source) } if pred.tag != tt.tag { t.Errorf("tag = %q, want %q", pred.tag, tt.tag) } switch { case pred.flagged == nil && tt.flagged == nil: case pred.flagged == nil || tt.flagged == nil: t.Errorf("flagged = %v, want %v", pred.flagged, tt.flagged) case *pred.flagged != *tt.flagged: t.Errorf("flagged = %v, want %v", *pred.flagged, *tt.flagged) } }) } }