package configkit_test import ( "errors" "os" "path/filepath" "reflect" "strings" "testing" configkit "go.maximumdirect.net/configkit" ) func TestResultSourceLookup(t *testing.T) { directory, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatal(err) } mainPath := writeSourceLookupFile(t, directory, "main.yml", `version: MAIN-SOURCE-CANARY configkit: imports: - path: imported.yml - dir: absent main_value: main `) importedPath := writeSourceLookupFile(t, directory, "imported.yml", `version: IMPORTED-SOURCE-CANARY imported_value: imported `) underlayPath := writeSourceLookupFile(t, directory, "underlay.yml", "underlay_value: underlay\n") definition := configkit.Definition[map[string]any]{ Version: &configkit.VersionRule{ Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil }, }, Sensitive: func(path configkit.Path) bool { return path.String() == "/version" }, } result, err := configkit.LoadLayered(configkit.Input{Source: configkit.File(mainPath), Imports: true}, configkit.LayeredOptions[map[string]any]{ Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.File(underlayPath)}}}}, Definition: definition, SourceVersionMode: configkit.SourceVersionModeIfPresent, }) if err != nil || result == nil { var loadError *configkit.LoadError if errors.As(err, &loadError) { t.Fatalf("LoadLayered = %#v, %v, diagnostics %#v", result, err, loadError.Diagnostics()) } t.Fatalf("LoadLayered = %#v, %v", result, err) } sources := result.Sources() if len(sources) != 3 { t.Fatalf("sources = %#v", sources) } for _, want := range sources { got, ok := result.Source(want.ID) if !ok || !reflect.DeepEqual(got, want) { t.Fatalf("Source(%q) = %#v, %v; Sources entry = %#v", want.ID, got, ok, want) } } main, ok := result.Source(sources[0].ID) if !ok || main.Layer != configkit.LayerMain || len(main.EmptyImportSelections) != 1 || main.VersionEvidence == nil || !main.VersionEvidence.Redacted || main.VersionEvidence.Version.Text != "" || main.VersionEvidence.Location == nil || main.VersionEvidence.Location.SourceID != main.ID { t.Fatalf("main source evidence = %#v, %v", main, ok) } var imported, underlay bool for _, source := range sources { if source.Path == importedPath { imported = source.Layer == configkit.LayerMain && source.ImportedFrom != nil && source.VersionEvidence != nil && source.VersionEvidence.Location != nil && source.VersionEvidence.Location.SourceID == source.ID } if source.Path == underlayPath { underlay = source.Layer == configkit.LayerUnderlay } } if !imported || !underlay { t.Fatalf("imported or underlay source evidence missing: %#v", sources) } if got, ok := result.Source(""); ok || !reflect.DeepEqual(got, configkit.SourceInfo{}) { t.Fatalf("empty source ID = %#v, %v", got, ok) } if got, ok := result.Source("unknown-source"); ok || !reflect.DeepEqual(got, configkit.SourceInfo{}) { t.Fatalf("unknown source ID = %#v, %v", got, ok) } var nilResult *configkit.Result[map[string]any] if got, ok := nilResult.Source("source-1"); ok || !reflect.DeepEqual(got, configkit.SourceInfo{}) { t.Fatalf("nil result lookup = %#v, %v", got, ok) } if got, ok := (&configkit.Result[map[string]any]{}).Source("source-1"); ok || !reflect.DeepEqual(got, configkit.SourceInfo{}) { t.Fatalf("zero result lookup = %#v, %v", got, ok) } main.EmptyImportSelections[0].Path = configkit.Keys("mutated") main.EmptyImportSelections[0].Location.Line = 99 main.VersionEvidence.Path = configkit.Keys("mutated") main.VersionEvidence.Location.Line = 99 main.VersionEvidence.Version.Text = "mutated" for _, source := range sources { if source.ImportedFrom != nil { found, _ := result.Source(source.ID) found.ImportedFrom.Line = 99 break } } if got := result.Sources(); !reflect.DeepEqual(got, sources) { t.Fatalf("lookup mutations changed enumeration: %#v", got) } for _, want := range sources { if got, ok := result.Source(want.ID); !ok || !reflect.DeepEqual(got, want) { t.Fatalf("repeated Source(%q) = %#v, %v; want %#v", want.ID, got, ok, want) } } for _, path := range []string{mainPath, importedPath, underlayPath} { if err := os.Remove(path); err != nil { t.Fatal(err) } } if got, ok := result.Source(sources[0].ID); !ok || !reflect.DeepEqual(got, sources[0]) { t.Fatalf("source lookup after inputs were removed = %#v, %v", got, ok) } } func TestLoadErrorSourceLookup(t *testing.T) { directory := t.TempDir() mainPath := writeSourceLookupFile(t, directory, "main.yml", `configkit: imports: - path: item.yml destination: [items] placement: sequence-item `) writeSourceLookupFile(t, directory, "item.yml", `configkit: imports: - path: helper.yml destination: [metadata] `) const canary = "FAILED-SOURCE-LOOKUP-CANARY" helperPath := writeSourceLookupFile(t, directory, "helper.yml", `version: `+canary+` configkit: imports: - dir: absent - path: missing.yml required: true key: value `) definition := configkit.Definition[map[string]any]{ Version: &configkit.VersionRule{ Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil }, }, Sensitive: func(path configkit.Path) bool { return strings.HasSuffix(path.String(), "/version") }, } _, err := configkit.Load(configkit.File(mainPath), configkit.Options[map[string]any]{ Imports: true, Definition: definition, SourceVersionMode: configkit.SourceVersionModeIfPresent, }) var loadError *configkit.LoadError if !errors.As(err, &loadError) { t.Fatalf("load error = %T %v", err, err) } if strings.Contains(err.Error(), canary) { t.Fatalf("load error disclosed source-version canary: %v", err) } sources := loadError.Sources() if len(sources) < 3 { t.Fatalf("partial source inventory = %#v", sources) } for _, want := range sources { got, ok := loadError.Source(want.ID) if !ok || !reflect.DeepEqual(got, want) { t.Fatalf("Source(%q) = %#v, %v; Sources entry = %#v", want.ID, got, ok, want) } } canonicalHelper, err := filepath.EvalSymlinks(helperPath) if err != nil { t.Fatal(err) } var helper configkit.SourceInfo for _, source := range sources { if source.Path == canonicalHelper { helper = source break } } if helper.ID == "" || !helper.DestinationIsFallback || helper.Destination.String() != "/items" || len(helper.EmptyImportSelections) != 1 || helper.VersionEvidence == nil || !helper.VersionEvidence.Redacted || helper.VersionEvidence.Version.Text != "" || helper.VersionEvidence.Location == nil { t.Fatalf("partial helper source evidence = %#v", helper) } if got, ok := loadError.Source(""); ok || !reflect.DeepEqual(got, configkit.SourceInfo{}) { t.Fatalf("empty source ID = %#v, %v", got, ok) } if got, ok := loadError.Source("unknown-source"); ok || !reflect.DeepEqual(got, configkit.SourceInfo{}) { t.Fatalf("unknown source ID = %#v, %v", got, ok) } var nilError *configkit.LoadError if got, ok := nilError.Source(helper.ID); ok || !reflect.DeepEqual(got, configkit.SourceInfo{}) { t.Fatalf("nil load error lookup = %#v, %v", got, ok) } if got, ok := (&configkit.LoadError{}).Source(helper.ID); ok || !reflect.DeepEqual(got, configkit.SourceInfo{}) { t.Fatalf("zero load error lookup = %#v, %v", got, ok) } mutated, ok := loadError.Source(helper.ID) if !ok { t.Fatal("partial helper lookup missing") } mutated.Destination = configkit.Keys("mutated") mutated.ImportedFrom.Line = 99 mutated.EmptyImportSelections[0].Path = configkit.Keys("mutated") mutated.EmptyImportSelections[0].Location.Line = 99 mutated.VersionEvidence.Path = configkit.Keys("mutated") mutated.VersionEvidence.Location.Line = 99 mutated.VersionEvidence.Version.Text = "mutated" if got := loadError.Sources(); !reflect.DeepEqual(got, sources) { t.Fatalf("lookup mutations changed error enumeration: %#v", got) } if got, ok := loadError.Source(helper.ID); !ok || !reflect.DeepEqual(got, helper) { t.Fatalf("repeated partial lookup = %#v, %v; want %#v", got, ok, helper) } } func writeSourceLookupFile(t *testing.T, directory, name, contents string) string { t.Helper() path := filepath.Join(directory, name) if err := os.WriteFile(path, []byte(contents), 0o600); err != nil { t.Fatal(err) } return path }