package configkit_test import ( "reflect" "testing" configkit "go.maximumdirect.net/configkit" ) func TestPublicHistoricalPathsEnumerateContextualShapes(t *testing.T) { loaded, err := configkit.Load[map[string]any](configkit.Bytes("main", []byte( "retired: {child: 1}\nshape: {'0': base}\n'a/b~': {'': 2}\n", )), configkit.Options[map[string]any]{Overrides: []configkit.Override{ {Path: configkit.Keys("retired"), Value: nil, Flag: "--retired"}, {Path: configkit.Keys("shape"), Value: []any{"replacement"}, Flag: "--shape"}, }}) if err != nil { t.Fatalf("Load: %v", err) } metadata := loaded.Metadata() paths := metadata.HistoricalPaths() got := make([]string, len(paths)) for index, path := range paths { got[index] = path.String() if len(metadata.History(path)) == 0 { t.Errorf("enumerated path %s has no history", path) } } want := []string{"", "/a~1b~0", "/a~1b~0/", "/retired", "/retired/child", "/shape", "/shape/0"} if !reflect.DeepEqual(got, want) { t.Fatalf("historical paths = %v, want sorted unique pointers %v", got, want) } if _, ok := metadata.Lookup(paths[4]); ok { t.Fatal("removed descendant remains effective") } if _, ok := metadata.Lookup(paths[6]); !ok { t.Fatal("enumerated numeric path did not follow the effective sequence shape") } if len(metadata.History(paths[6])) == 0 { t.Fatal("enumerated numeric path lost history") } assertPublicShapeHistory(t, metadata, configkit.LayerCLI) if _, ok := metadata.Lookup(configkit.Keys("shape", "0")); ok { t.Fatal("mapping-key path resolved against the effective sequence") } if len((configkit.Metadata{}).HistoricalPaths()) != 0 { t.Fatal("zero metadata contains historical paths") } if (configkit.Metadata{}).History(configkit.Keys("shape")) != nil { t.Fatal("zero metadata contains history") } paths[1] = configkit.Keys("mutated") if again := metadata.HistoricalPaths(); !reflect.DeepEqual(pathStrings(again), want) { t.Fatalf("returned path slice mutated retained metadata: %v", pathStrings(again)) } } func TestPublicHistoryUsesResolutionShapeAttribution(t *testing.T) { loaded, err := configkit.Load[map[string]any](configkit.Bytes("main", []byte("shape: {'0': base}\n")), configkit.Options[map[string]any]{ Definition: configkit.Definition[map[string]any]{ Resolve: func(configuration *map[string]any, resolution *configkit.Resolution) error { (*configuration)["shape"] = []any{"replacement"} resolution.RecordNormalization(configkit.Keys("shape"), "replace numeric mapping key") return nil }, }, }) if err != nil { t.Fatalf("Load: %v", err) } assertPublicShapeHistory(t, loaded.Metadata(), configkit.LayerResolution) } func assertPublicShapeHistory(t *testing.T, metadata configkit.Metadata, operationLayer configkit.Layer) { t.Helper() keyPath := configkit.Keys("shape", "0") indexPath := configkit.Keys("shape").Index(0) keyHistory := metadata.History(keyPath) indexHistory := metadata.History(indexPath) if len(keyHistory) == 0 || len(indexHistory) == 0 { t.Fatalf("explicit shape histories = key %#v, index %#v", keyHistory, indexHistory) } var keyDefinition, keyReplacement, indexReplacement bool for _, change := range keyHistory { keyDefinition = keyDefinition || change.Origin.Layer == configkit.LayerMain keyReplacement = keyReplacement || change.Origin.Layer == operationLayer && change.Path.String() == "/shape" } for _, change := range indexHistory { if change.Origin.Layer == configkit.LayerMain { t.Fatalf("introduced index inherited mapping-key history: %#v", indexHistory) } indexReplacement = indexReplacement || change.Origin.Layer == operationLayer && change.Path.String() == "/shape" for _, previous := range change.Previous { if previous.Layer == configkit.LayerMain { t.Fatalf("introduced index inherited mapping-key previous origin: %#v", change) } } } if !keyDefinition || !keyReplacement || !indexReplacement { t.Fatalf("shape histories missed applicable definitions or replacement: key %#v, index %#v", keyHistory, indexHistory) } parsed, err := configkit.ParsePath("/shape/0") if err != nil { t.Fatal(err) } if parsedHistory := metadata.History(parsed); len(parsedHistory) == 0 { t.Fatal("parsed numeric query lost contextual history") } } func pathStrings(paths []configkit.Path) []string { result := make([]string, len(paths)) for index, path := range paths { result[index] = path.String() } return result }