216 lines
5.4 KiB
Go
216 lines
5.4 KiB
Go
package topdata
|
|
|
|
import (
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestUnsupportedObjectKeysAreSorted(t *testing.T) {
|
|
obj := map[string]any{
|
|
"row": map[string]any{},
|
|
"when_present": []any{},
|
|
"aaa": true,
|
|
}
|
|
|
|
got := unsupportedObjectKeys(
|
|
obj,
|
|
"row",
|
|
"require_present",
|
|
"any_present",
|
|
"unless_present",
|
|
)
|
|
want := []string{"aaa", "when_present"}
|
|
if !slices.Equal(got, want) {
|
|
t.Fatalf("unsupportedObjectKeys() = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestUnsupportedObjectKeysErrorNamesObjectAndContract(t *testing.T) {
|
|
err := unsupportedObjectKeysError(
|
|
"global injection 10",
|
|
map[string]any{"row": map[string]any{}, "when_present": []any{}},
|
|
"row",
|
|
"require_present",
|
|
"any_present",
|
|
"unless_present",
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected unsupported-property error")
|
|
}
|
|
for _, want := range []string{
|
|
"global injection 10",
|
|
`"when_present"`,
|
|
"row",
|
|
"require_present",
|
|
"any_present",
|
|
"unless_present",
|
|
} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Fatalf("error %q does not contain %q", err, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseGlobalConditionGroups(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
injection map[string]any
|
|
want globalConditionGroups
|
|
wantError string
|
|
}{
|
|
{
|
|
name: "all groups",
|
|
injection: map[string]any{
|
|
"require_present": []any{
|
|
map[string]any{"field": "FeatIndex", "id": "feat:required"},
|
|
},
|
|
"any_present": []any{
|
|
map[string]any{"field": "FeatIndex", "id": "feat:first"},
|
|
map[string]any{"field": "FeatIndex", "id": "feat:second"},
|
|
},
|
|
"unless_present": []any{
|
|
map[string]any{"field": "FeatIndex", "id": "feat:blocked"},
|
|
},
|
|
},
|
|
want: globalConditionGroups{
|
|
RequirePresent: []globalCondition{
|
|
{Field: "FeatIndex", ID: "feat:required"},
|
|
},
|
|
AnyPresent: []globalCondition{
|
|
{Field: "FeatIndex", ID: "feat:first"},
|
|
{Field: "FeatIndex", ID: "feat:second"},
|
|
},
|
|
UnlessPresent: []globalCondition{
|
|
{Field: "FeatIndex", ID: "feat:blocked"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "empty existing groups remain valid",
|
|
injection: map[string]any{
|
|
"require_present": []any{},
|
|
"unless_present": []any{},
|
|
},
|
|
want: globalConditionGroups{
|
|
RequirePresent: []globalCondition{},
|
|
UnlessPresent: []globalCondition{},
|
|
},
|
|
},
|
|
{
|
|
name: "any present must be array",
|
|
injection: map[string]any{
|
|
"any_present": "feat:first",
|
|
},
|
|
wantError: "any_present must be an array",
|
|
},
|
|
{
|
|
name: "any present must not be empty",
|
|
injection: map[string]any{
|
|
"any_present": []any{},
|
|
},
|
|
wantError: "any_present must contain at least one condition",
|
|
},
|
|
{
|
|
name: "condition is closed",
|
|
injection: map[string]any{
|
|
"require_present": []any{
|
|
map[string]any{
|
|
"field": "FeatIndex",
|
|
"id": "feat:required",
|
|
"typo": true,
|
|
},
|
|
},
|
|
},
|
|
wantError: `require_present[0] contains unsupported key "typo"`,
|
|
},
|
|
{
|
|
name: "field is required",
|
|
injection: map[string]any{
|
|
"require_present": []any{
|
|
map[string]any{"id": "feat:required"},
|
|
},
|
|
},
|
|
wantError: "require_present[0].field is required",
|
|
},
|
|
{
|
|
name: "id is required",
|
|
injection: map[string]any{
|
|
"require_present": []any{
|
|
map[string]any{"field": "FeatIndex"},
|
|
},
|
|
},
|
|
wantError: "require_present[0].id is required",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, err := parseGlobalConditionGroups(tc.injection)
|
|
if tc.wantError != "" {
|
|
if err == nil {
|
|
t.Fatalf("parseGlobalConditionGroups() error = nil, want %q", tc.wantError)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantError) {
|
|
t.Fatalf("parseGlobalConditionGroups() error = %q, want contains %q", err, tc.wantError)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("parseGlobalConditionGroups() error = %v", err)
|
|
}
|
|
if !reflect.DeepEqual(got, tc.want) {
|
|
t.Fatalf("parseGlobalConditionGroups() = %#v, want %#v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("absent groups remain valid", func(t *testing.T) {
|
|
got, err := parseGlobalConditionGroups(map[string]any{})
|
|
if err != nil {
|
|
t.Fatalf("parseGlobalConditionGroups() error = %v", err)
|
|
}
|
|
if !reflect.DeepEqual(got, globalConditionGroups{}) {
|
|
t.Fatalf("parseGlobalConditionGroups() = %#v, want zero value", got)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGlobalConditionGroupsMatch(t *testing.T) {
|
|
rowsWithoutBlocked := []map[string]any{
|
|
{"FeatIndex": map[string]any{"id": "feat:required"}},
|
|
{"FeatIndex": map[string]any{"id": "feat:second"}},
|
|
}
|
|
rowsWithBlocked := append([]map[string]any(nil), rowsWithoutBlocked...)
|
|
rowsWithBlocked = append(rowsWithBlocked, map[string]any{"FeatIndex": map[string]any{"id": "feat:blocked"}})
|
|
|
|
if !globalConditionGroupsMatch(globalConditionGroups{
|
|
RequirePresent: []globalCondition{
|
|
{Field: "FeatIndex", ID: "feat:required"},
|
|
},
|
|
AnyPresent: []globalCondition{
|
|
{Field: "FeatIndex", ID: "feat:first"},
|
|
{Field: "FeatIndex", ID: "feat:second"},
|
|
},
|
|
}, rowsWithoutBlocked) {
|
|
t.Fatal("expected required + later any_present alternative to match")
|
|
}
|
|
|
|
if globalConditionGroupsMatch(globalConditionGroups{
|
|
AnyPresent: []globalCondition{
|
|
{Field: "FeatIndex", ID: "feat:missing"},
|
|
},
|
|
}, rowsWithoutBlocked) {
|
|
t.Fatal("expected missing any_present alternatives to reject")
|
|
}
|
|
|
|
if globalConditionGroupsMatch(globalConditionGroups{
|
|
UnlessPresent: []globalCondition{
|
|
{Field: "FeatIndex", ID: "feat:blocked"},
|
|
},
|
|
}, rowsWithBlocked) {
|
|
t.Fatal("expected unless_present match to reject")
|
|
}
|
|
}
|