package main import ( "net/url" "testing" "time" ) func TestParseFilters_empty(t *testing.T) { filters := ParseFilters(url.Values{}) if len(filters) != 0 { t.Fatalf("expected no filters, got %d", len(filters)) } } func TestParseFilters_single(t *testing.T) { v := url.Values{"filter": {"payload.ref:refs/heads/main"}} filters := ParseFilters(v) if len(filters) != 1 { t.Fatalf("expected 1 filter, got %d", len(filters)) } if filters[0].Path != "payload.ref" { t.Errorf("expected path payload.ref, got %s", filters[0].Path) } if filters[0].Value != "refs/heads/main" { t.Errorf("expected value refs/heads/main, got %s", filters[0].Value) } } func TestParseFilters_multiple(t *testing.T) { v := url.Values{"filter": { "payload.ref:refs/heads/main", "headers.X-GitHub-Event:push", }} filters := ParseFilters(v) if len(filters) != 2 { t.Fatalf("expected 2 filters, got %d", len(filters)) } } func TestParseFilters_colonInValue(t *testing.T) { v := url.Values{"filter": {"payload.url:https://example.com"}} filters := ParseFilters(v) if len(filters) != 1 { t.Fatalf("expected 1 filter, got %d", len(filters)) } if filters[0].Value != "https://example.com" { t.Errorf("expected value with colon preserved, got %s", filters[0].Value) } } func TestMatchAll_emptyFilters(t *testing.T) { event := &Event{ Payload: map[string]any{"ref": "refs/heads/main"}, } if !MatchAll(nil, event) { t.Error("empty filters should match everything") } } func TestMatchAll_singleMatch(t *testing.T) { event := &Event{ Payload: map[string]any{"ref": "refs/heads/main"}, } filters := []Filter{{Path: "payload.ref", Value: "refs/heads/main"}} if !MatchAll(filters, event) { t.Error("expected filter to match") } } func TestMatchAll_singleNoMatch(t *testing.T) { event := &Event{ Payload: map[string]any{"ref": "refs/heads/develop"}, } filters := []Filter{{Path: "payload.ref", Value: "refs/heads/main"}} if MatchAll(filters, event) { t.Error("expected filter not to match") } } func TestMatchAll_multipleAllMatch(t *testing.T) { event := &Event{ Headers: map[string]string{"X-GitHub-Event": "push"}, Payload: map[string]any{"ref": "refs/heads/main"}, } filters := []Filter{ {Path: "payload.ref", Value: "refs/heads/main"}, {Path: "headers.X-GitHub-Event", Value: "push"}, } if !MatchAll(filters, event) { t.Error("expected all filters to match") } } func TestMatchAll_multipleOneFails(t *testing.T) { event := &Event{ Headers: map[string]string{"X-GitHub-Event": "push"}, Payload: map[string]any{"ref": "refs/heads/develop"}, } filters := []Filter{ {Path: "payload.ref", Value: "refs/heads/main"}, {Path: "headers.X-GitHub-Event", Value: "push"}, } if MatchAll(filters, event) { t.Error("expected AND filter to fail when one doesn't match") } } func TestMatchAll_nestedDotPath(t *testing.T) { event := &Event{ Payload: map[string]any{ "repository": map[string]any{ "full_name": "chrisguidry/docketeer", }, }, } filters := []Filter{{Path: "payload.repository.full_name", Value: "chrisguidry/docketeer"}} if !MatchAll(filters, event) { t.Error("expected nested dot path to match") } } func TestMatchAll_missingField(t *testing.T) { event := &Event{ Payload: map[string]any{"ref": "refs/heads/main"}, } filters := []Filter{{Path: "payload.nonexistent.field", Value: "anything"}} if MatchAll(filters, event) { t.Error("missing field should not match") } } func TestMatchAll_topLevelFields(t *testing.T) { event := &Event{ ID: "abc-123", Path: "github.com/chrisguidry/docketeer", } filters := []Filter{{Path: "path", Value: "github.com/chrisguidry/docketeer"}} if !MatchAll(filters, event) { t.Error("expected top-level path field to match") } } func TestMatchAll_idField(t *testing.T) { event := &Event{ID: "abc-123"} filters := []Filter{{Path: "id", Value: "abc-123"}} if !MatchAll(filters, event) { t.Error("expected id field to match") } } func TestMatchAll_timestampField(t *testing.T) { event := &Event{Timestamp: time.Date(2026, 3, 4, 12, 0, 0, 0, time.UTC)} filters := []Filter{{Path: "timestamp", Value: "2026-03-04T12:00:00Z"}} if !MatchAll(filters, event) { t.Error("expected timestamp field to match") } } func TestMatchAll_headersNeedsTwoParts(t *testing.T) { event := &Event{Headers: map[string]string{"X-Foo": "bar"}} filters := []Filter{{Path: "headers", Value: "anything"}} if MatchAll(filters, event) { t.Error("headers without key should not match") } } func TestMatchAll_unknownTopLevel(t *testing.T) { event := &Event{} filters := []Filter{{Path: "nonexistent", Value: "anything"}} if MatchAll(filters, event) { t.Error("unknown top-level field should not match") } } func TestMatchAll_emptyPath(t *testing.T) { event := &Event{} filters := []Filter{{Path: "", Value: "anything"}} if MatchAll(filters, event) { t.Error("empty path should not match") } } func TestMatchAll_payloadNonMapNavigation(t *testing.T) { event := &Event{Payload: "just a string"} filters := []Filter{{Path: "payload.deep.field", Value: "anything"}} if MatchAll(filters, event) { t.Error("navigating into non-map payload should not match") } } func TestParseFilters_invalidFormat(t *testing.T) { v := url.Values{"filter": {"no-colon-here"}} filters := ParseFilters(v) if len(filters) != 0 { t.Errorf("expected 0 filters for invalid format, got %d", len(filters)) } } func TestMatchAll_idWithSubpath(t *testing.T) { event := &Event{ID: "abc-123"} filters := []Filter{{Path: "id.sub", Value: "anything"}} if MatchAll(filters, event) { t.Error("id with subpath should not match") } } func TestMatchAll_pathWithSubpath(t *testing.T) { event := &Event{Path: "some/path"} filters := []Filter{{Path: "path.sub", Value: "anything"}} if MatchAll(filters, event) { t.Error("path with subpath should not match") } } func TestMatchAll_timestampWithSubpath(t *testing.T) { event := &Event{} filters := []Filter{{Path: "timestamp.sub", Value: "anything"}} if MatchAll(filters, event) { t.Error("timestamp with subpath should not match") } } func TestMatchAll_payloadLeafValue(t *testing.T) { event := &Event{ Payload: map[string]any{"count": 42}, } filters := []Filter{{Path: "payload.count", Value: "42"}} if !MatchAll(filters, event) { t.Error("expected numeric leaf value to match via fmt.Sprintf") } }