Preserve explicitness across resolution shape changes

This commit is contained in:
2026-10-26 14:36:15 +00:00
parent 28ef9b6200
commit a893c7716c
7 changed files with 125 additions and 13 deletions

View File

@@ -390,6 +390,105 @@ func TestResolutionPresenceUsesEntryShapeAcrossDynamicChanges(t *testing.T) {
}
}
func TestResolutionMetadataExplicitnessUsesEntryShape(t *testing.T) {
t.Parallel()
type config struct {
Shape any `yaml:"shape"`
}
tests := []struct {
name string
input string
entryPath Path
finalPath Path
shapeChanges bool
mutate func(*config)
wantNull bool
wantOrigin ChangeKind
wantOriginLayer Layer
wantExplicit bool
}{
{
name: "mapping key becomes sequence index beneath changed ancestor",
input: "shape: {'0': {leaf: old}}\n",
entryPath: Keys("shape", "0", "leaf"),
finalPath: Keys("shape").Index(0).Key("leaf"),
shapeChanges: true,
mutate: func(configuration *config) { configuration.Shape = []any{map[string]any{"leaf": "new"}} },
wantOrigin: ChangeNormalization,
wantOriginLayer: LayerResolution,
},
{
name: "sequence index becomes mapping key beneath changed ancestor",
input: "shape: [{leaf: old}]\n",
entryPath: Keys("shape").Index(0).Key("leaf"),
finalPath: Keys("shape", "0", "leaf"),
shapeChanges: true,
mutate: func(configuration *config) { configuration.Shape = map[string]any{"0": map[string]any{"leaf": "new"}} },
wantOrigin: ChangeNormalization,
wantOriginLayer: LayerResolution,
},
{
name: "same-shape ancestor normalization retains presence and null",
input: "shape: {nested: {leaf: old}}\n",
entryPath: Keys("shape", "nested", "leaf"),
finalPath: Keys("shape", "nested", "leaf"),
mutate: func(configuration *config) {
configuration.Shape = map[string]any{"nested": map[string]any{"leaf": nil}}
},
wantNull: true,
wantOrigin: ChangeNormalization,
wantOriginLayer: LayerResolution,
wantExplicit: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var callbackErr error
result, err := testLoad(Bytes("shape-change.yml", []byte(test.input)), Options[config]{Definition: Definition[config]{
Resolve: func(configuration *config, resolution *Resolution) error {
checkEntry := func() error {
info, found := resolution.InputInfo(test.entryPath)
if !found || !info.Explicit || !resolution.IsExplicit(test.entryPath) {
return errors.New("resolution entry view lost its original shape")
}
if test.shapeChanges {
if _, found := resolution.InputInfo(test.finalPath); found || resolution.IsExplicit(test.finalPath) {
return errors.New("resolution entry view adopted the final shape")
}
}
return nil
}
if err := checkEntry(); err != nil {
callbackErr = err
return err
}
resolution.Record(Keys("shape"), ChangeNormalization, "normalize shape")
test.mutate(configuration)
if err := checkEntry(); err != nil {
callbackErr = err
return err
}
return nil
},
}})
if callbackErr != nil {
t.Fatal(callbackErr)
}
if err != nil {
t.Fatal(err)
}
info, found := result.Metadata().Lookup(test.finalPath)
if !found || info.Explicit != test.wantExplicit || result.Metadata().IsExplicit(test.finalPath) != test.wantExplicit {
t.Fatalf("final metadata = %#v, found %v, IsExplicit %v; want explicit %v", info, found, result.Metadata().IsExplicit(test.finalPath), test.wantExplicit)
}
if info.Null != test.wantNull || info.Origin.Layer != test.wantOriginLayer || info.Origin.Operation != test.wantOrigin {
t.Fatalf("final metadata null/origin = %v/%#v", info.Null, info.Origin)
}
})
}
}
func TestResolutionEntryQueriesExposeDetachedLoadInput(t *testing.T) {
t.Parallel()