Validate final registered sequence identities

This commit is contained in:
2026-10-23 21:43:54 +00:00
parent 41b5dff69b
commit 5af71a7533
7 changed files with 300 additions and 32 deletions

View File

@@ -388,7 +388,7 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
}
})
t.Run("replacement overlay validates identities and CLI may introduce duplicates", func(t *testing.T) {
t.Run("replacement overlay and CLI validate final identities", func(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "items: [{id: base}]\n")
writeImportFile(t, directory, "overlays/testing.yml", "items: [{id: duplicate}, {id: duplicate}]\n")
@@ -401,8 +401,8 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
Definition: definition,
Overrides: []Override{{Path: Keys("items"), Flag: "--items", Value: []any{map[string]any{"id": "duplicate"}, map[string]any{"id": "duplicate"}}}},
})
if err != nil || len(result.Config.Items) != 2 {
t.Fatalf("replacement CLI value = %#v, %v", result, err)
if result != nil || loadCode(t, err) != CodeSequenceConflict {
t.Fatalf("duplicate CLI value = %#v, %v", result, err)
}
resolved := definition
@@ -412,31 +412,23 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
return nil
}
result, err = testLoad[config](Bytes("main", []byte("items: [{id: first}, {id: second}]\n")), Options[config]{Definition: resolved})
if err != nil {
t.Fatalf("resolver replacement error code = %s", loadCode(t, err))
}
if len(result.Config.Items) != 2 {
t.Fatalf("resolver replacement = %#v, %v", result, err)
if result != nil || loadCode(t, err) != CodeSequenceConflict {
t.Fatalf("resolver duplicate = %#v, %v", result, err)
}
})
t.Run("application validation can reject replacement duplicates", func(t *testing.T) {
t.Run("final identity validation precedes application validation", func(t *testing.T) {
checked := definition
validated := false
checked.Validate = func(value *config, validation *Validation) {
seen := make(map[string]bool)
for index, entry := range value.Items {
if seen[entry.ID] {
validation.Error(Keys("items").Index(index).Key("id"), "identity must be unique")
}
seen[entry.ID] = true
}
validated = true
}
_, err := testLoad[config](Bytes("main", []byte("items: [{id: base}]\n")), Options[config]{
Definition: checked,
Overrides: []Override{{Path: Keys("items"), Flag: "--items", Value: []any{map[string]any{"id": "duplicate"}, map[string]any{"id": "duplicate"}}}},
})
if got := loadCode(t, err); got != CodeValidateError {
t.Fatalf("application validation error = %s", got)
if got := loadCode(t, err); got != CodeSequenceConflict || validated {
t.Fatalf("duplicate identity result = %s, validation callback invoked %v", got, validated)
}
})
}