Files
configkit/source_lookup_test.go

227 lines
8.2 KiB
Go

package configkit_test
import (
"errors"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
)
func TestResultSourceLookup(t *testing.T) {
directory := t.TempDir()
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
}