package engine import ( "math/big" "reflect" "sort" "strings" "testing" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) func TestNormalizeSequenceIdentityFields(t *testing.T) { got, err := normalizeSequenceIdentityFields([]string{"name", "id", "id", ""}) if err != nil { t.Fatal(err) } if want := []string{"", "id", "name"}; !reflect.DeepEqual(got, want) { t.Fatalf("normalized fields = %#v, want %#v", got, want) } if _, err := normalizeSequenceIdentityFields(nil); err == nil { t.Fatal("empty field set unexpectedly succeeded") } } func TestNormalizeSequenceIdentityNumbers(t *testing.T) { tests := []struct { left, right string equal bool }{ {left: "1", right: "1.0", equal: true}, {left: "1", right: "1e0", equal: true}, {left: "0", right: "-0.000e999", equal: true}, {left: "0.0012300", right: "123e-5", equal: true}, {left: "9007199254740992", right: "9007199254740993", equal: false}, {left: "1.0000000000000000001", right: "1.0000000000000000002", equal: false}, } for _, test := range tests { left, leftErr := normalizeSequenceIdentityNumber(test.left) right, rightErr := normalizeSequenceIdentityNumber(test.right) if leftErr != nil || rightErr != nil { t.Fatalf("normalize(%q, %q) errors = %v, %v", test.left, test.right, leftErr, rightErr) } if (left == right) != test.equal { t.Errorf("normalize(%q) = %q; normalize(%q) = %q; equal = %t, want %t", test.left, left, test.right, right, left == right, test.equal) } } } func TestNormalizeSequenceIdentityNumberLargeExponentWithoutExpansion(t *testing.T) { exponent := "999999999999999999999999999999999999999999999999999999999999999999999999999999999999" left, err := normalizeSequenceIdentityNumber("1e" + exponent) if err != nil { t.Fatal(err) } plusOne, ok := new(big.Int).SetString(exponent, 10) if !ok { t.Fatal("test exponent did not parse") } plusOne.Add(plusOne, big.NewInt(1)) right, err := normalizeSequenceIdentityNumber("10e" + exponent) if err != nil { t.Fatal(err) } if want := "1e" + plusOne.String(); right != want { t.Fatalf("normalized large exponent = %q, want %q", right, want) } if left == right { t.Fatal("unequal large exponents normalized to equal values") } } func TestSequenceIdentityTupleUsesTypeTagsAndLengthBoundaries(t *testing.T) { tests := []struct { name string left []*document.Node right []*document.Node equal bool }{ {name: "empty string differs from null", left: []*document.Node{stringNode("")}, right: []*document.Node{nullNode()}, equal: false}, {name: "empty strings match exactly", left: []*document.Node{stringNode("")}, right: []*document.Node{stringNode("")}, equal: true}, {name: "explicit null matches explicit null", left: []*document.Node{nullNode()}, right: []*document.Node{nullNode()}, equal: true}, {name: "string and number differ", left: []*document.Node{stringNode("1")}, right: []*document.Node{numberNode("1.0")}, equal: false}, {name: "boolean and number differ", left: []*document.Node{booleanNode("true")}, right: []*document.Node{numberNode("1")}, equal: false}, {name: "compound boundaries are encoded", left: []*document.Node{stringNode("ab"), stringNode("c")}, right: []*document.Node{stringNode("a"), stringNode("bc")}, equal: false}, {name: "number spelling is normalized", left: []*document.Node{numberNode("1")}, right: []*document.Node{numberNode("1e0")}, equal: true}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { left, leftErr := sequenceIdentityTuple(test.left) right, rightErr := sequenceIdentityTuple(test.right) if leftErr != nil || rightErr != nil { t.Fatalf("tuple errors = %v, %v", leftErr, rightErr) } if (left == right) != test.equal { t.Fatalf("tuple equality = %t, want %t", left == right, test.equal) } }) } } func TestValidateSequenceIdentityUsesOneTupleIndexAndPreservesNodes(t *testing.T) { first := identityRecord(map[string]*document.Node{ "namespace": stringNode("support"), "id": numberNode("1.0"), }) second := identityRecord(map[string]*document.Node{ "id": numberNode("1e0"), "namespace": stringNode("support"), }) sequence := &document.Node{Kind: document.Sequence, Location: document.Location{SourceID: "source", Line: 1}, Sequence: []*document.Node{first, second}} _, diagnostics := validateSequenceIdentity(sequence, Keys("prompts"), []string{"namespace", "id", "id"}, sequenceIdentityDiagnosticCodes{invalid: CodeComposeInvalid, conflict: CodeComposeConflict}) if len(diagnostics) != 1 { t.Fatalf("diagnostics = %#v, want one duplicate diagnostic", diagnostics) } diagnostic := diagnostics[0] if diagnostic.Code != CodeComposeConflict || diagnostic.Path.String() != "/prompts" || diagnostic.Primary == nil || len(diagnostic.Related) != 1 { t.Fatalf("duplicate diagnostic = %#v", diagnostic) } if diagnostic.Message == "" || strings.Contains(diagnostic.Message, "support") || strings.Contains(diagnostic.Message, "1.0") { t.Fatalf("diagnostic unexpectedly contains identity data: %#v", diagnostic) } if first.Mapping[0].Value.Text != "1.0" || second.Mapping[0].Value.Text != "1e0" { t.Fatalf("identity validation rewrote authored values: %q, %q", first.Mapping[0].Value.Text, second.Mapping[0].Value.Text) } } func TestValidateSequenceIdentityReportsMalformedFieldsWithoutValues(t *testing.T) { tests := []struct { name string item *document.Node wantPath string }{ {name: "non-mapping item", item: stringNode("private"), wantPath: "/items/0"}, {name: "missing field", item: identityRecord(map[string]*document.Node{"other": stringNode("private")}), wantPath: "/items/0/id"}, {name: "mapping field", item: identityRecord(map[string]*document.Node{"id": {Kind: document.Mapping}}), wantPath: "/items/0/id"}, {name: "sequence field", item: identityRecord(map[string]*document.Node{"id": {Kind: document.Sequence}}), wantPath: "/items/0/id"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { sequence := &document.Node{Kind: document.Sequence, Sequence: []*document.Node{test.item}} _, diagnostics := validateSequenceIdentity(sequence, Keys("items"), []string{"id"}, sequenceIdentityDiagnosticCodes{invalid: CodeOptionsInvalid, conflict: CodeComposeConflict}) if len(diagnostics) != 1 || diagnostics[0].Code != CodeOptionsInvalid || diagnostics[0].Path.String() != test.wantPath { t.Fatalf("diagnostics = %#v", diagnostics) } if diagnostics[0].Message == "" || strings.Contains(diagnostics[0].Message, "private") { t.Fatalf("diagnostic exposed value or lacked message: %#v", diagnostics[0]) } }) } } func TestSequenceIdentityExcludesStructuralExtendsField(t *testing.T) { item := identityRecord(map[string]*document.Node{ "extends": nullNode(), "name": stringNode("ordinary payload"), }) sequence := &document.Node{Kind: document.Sequence, Sequence: []*document.Node{item}} _, diagnostics := validateSequenceIdentity(sequence, Keys("items"), []string{"extends"}, sequenceIdentityDiagnosticCodes{invalid: CodeComposeInvalid, conflict: CodeComposeConflict}) if len(diagnostics) != 1 || diagnostics[0].Path.String() != "/items/0/extends" || diagnostics[0].Code != CodeComposeInvalid { t.Fatalf("diagnostics = %#v, want structural field treated as absent", diagnostics) } } func TestSequenceIdentityUsesLiteralPunctuationFieldNames(t *testing.T) { item := identityRecord(map[string]*document.Node{"metadata.id": stringNode("literal"), "metadata": identityRecord(map[string]*document.Node{"id": stringNode("nested")})}) sequence := &document.Node{Kind: document.Sequence, Sequence: []*document.Node{item}} identities, diagnostics := validateSequenceIdentity(sequence, Keys("items"), []string{"metadata.id"}, sequenceIdentityDiagnosticCodes{invalid: CodeComposeInvalid, conflict: CodeComposeConflict}) if len(diagnostics) != 0 { t.Fatalf("diagnostics = %#v", diagnostics) } if len(identities) != 1 { t.Fatalf("identity index has %d entries, want 1", len(identities)) } } func TestValidateSequenceIdentityUsesCallerDiagnosticCodes(t *testing.T) { item := identityRecord(map[string]*document.Node{"id": stringNode("same")}) sequence := &document.Node{Kind: document.Sequence, Sequence: []*document.Node{item, document.Clone(item)}} _, diagnostics := validateSequenceIdentity(sequence, Keys("items"), []string{"id"}, sequenceIdentityDiagnosticCodes{invalid: CodeOptionsInvalid, conflict: CodeValidateMutation}) if len(diagnostics) != 1 || diagnostics[0].Code != CodeValidateMutation { t.Fatalf("diagnostics = %#v, want caller-selected conflict code", diagnostics) } } func identityRecord(fields map[string]*document.Node) *document.Node { entries := make([]document.Entry, 0, len(fields)) keys := make([]string, 0, len(fields)) for key := range fields { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { value := fields[key] entries = append(entries, document.Entry{Key: key, Value: value, ValueLocation: value.Location}) } return &document.Node{Kind: document.Mapping, Mapping: entries, Location: document.Location{SourceID: "source", Line: 2}} } func stringNode(value string) *document.Node { return &document.Node{Kind: document.String, Text: value} } func numberNode(value string) *document.Node { return &document.Node{Kind: document.Number, Text: value} } func booleanNode(value string) *document.Node { return &document.Node{Kind: document.Boolean, Text: value} } func nullNode() *document.Node { return &document.Node{Kind: document.Null} }