diff --git a/.changes/unreleased/Bugfix-20260713-180016.yaml b/.changes/unreleased/Bugfix-20260713-180016.yaml new file mode 100644 index 00000000..72d737c0 --- /dev/null +++ b/.changes/unreleased/Bugfix-20260713-180016.yaml @@ -0,0 +1,5 @@ +kind: Bugfix +body: FilterPredicate.Validate now rejects any CaseSensitive value (true or false) on + predicate types that do not support case sensitivity (e.g. exists, matches), since + the API ignores the value and returns null for it. +time: 2026-07-13T18:00:16.000000Z diff --git a/filters.go b/filters.go index 9c58d3bf..15effd85 100644 --- a/filters.go +++ b/filters.go @@ -50,7 +50,10 @@ func (filterPredicate *FilterPredicate) validateCaseSensitivity() error { PredicateTypeEnumDoesNotMatch, PredicateTypeEnumSatisfiesJqExpression, } - if slices.Contains(knownNotCaseSensitiveTypes, filterPredicate.Type) && *filterPredicate.CaseSensitive { + // The API ignores CaseSensitive for these predicate types and returns null for it + // in responses, so any value set here (true or false) would silently disagree with + // the API response for clients that round-trip it. Reject any non-nil value. + if slices.Contains(knownNotCaseSensitiveTypes, filterPredicate.Type) { return fmt.Errorf("FilterPredicate type '%s' cannot have CaseSensitive value set", filterPredicate.Type) } return nil diff --git a/filters_test.go b/filters_test.go index dbf43eb0..5a7242fb 100644 --- a/filters_test.go +++ b/filters_test.go @@ -301,3 +301,66 @@ func TestDeleteFilter(t *testing.T) { // Assert autopilot.Equals(t, nil, err) } + +func TestFilterPredicateValidateCaseSensitivity(t *testing.T) { + boolPtr := func(b bool) *bool { return &b } + + testCases := map[string]struct { + predicate ol.FilterPredicate + wantErr bool + }{ + "nil on not-case-sensitive type is valid": { + predicate: ol.FilterPredicate{ + Key: ol.PredicateKeyEnumRepositoryIDs, + Type: ol.PredicateTypeEnumExists, + CaseSensitive: nil, + }, + wantErr: false, + }, + "true on not-case-sensitive type is an error": { + predicate: ol.FilterPredicate{ + Key: ol.PredicateKeyEnumRepositoryIDs, + Type: ol.PredicateTypeEnumExists, + CaseSensitive: boolPtr(true), + }, + wantErr: true, + }, + "false on not-case-sensitive type is an error": { + predicate: ol.FilterPredicate{ + Key: ol.PredicateKeyEnumRepositoryIDs, + Type: ol.PredicateTypeEnumExists, + CaseSensitive: boolPtr(false), + }, + wantErr: true, + }, + "true on case-sensitive type is valid": { + predicate: ol.FilterPredicate{ + Key: ol.PredicateKeyEnumLanguage, + Type: ol.PredicateTypeEnumEquals, + Value: "Go", + CaseSensitive: boolPtr(true), + }, + wantErr: false, + }, + "false on case-sensitive type is valid": { + predicate: ol.FilterPredicate{ + Key: ol.PredicateKeyEnumLanguage, + Type: ol.PredicateTypeEnumEquals, + Value: "Go", + CaseSensitive: boolPtr(false), + }, + wantErr: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + err := tc.predicate.Validate() + if tc.wantErr { + autopilot.Assert(t, err != nil, "expected an error but got nil") + } else { + autopilot.Equals(t, nil, err) + } + }) + } +}