Audit scheduled import and inheritance lifecycles
This commit is contained in:
135
facade_import_lifecycle_test.go
Normal file
135
facade_import_lifecycle_test.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package configkit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiscoveredItemHelpersPreserveNestedInheritedResourceEvidence(t *testing.T) {
|
||||||
|
const canary = "PRIVATE-CONSUMED-HEADER-CANARY"
|
||||||
|
filesystem := NewFilesystem("bundle", fstest.MapFS{
|
||||||
|
"bundle/config/main.yml": {Data: []byte(`configkit:
|
||||||
|
imports:
|
||||||
|
- dir: records
|
||||||
|
pattern: '*-item.yml'
|
||||||
|
destination: [services, records]
|
||||||
|
placement: sequence-item
|
||||||
|
services:
|
||||||
|
records:
|
||||||
|
- id: authored
|
||||||
|
version: authored
|
||||||
|
`)},
|
||||||
|
"bundle/config/records/a-item.yml": {Data: []byte(`configkit:
|
||||||
|
imports:
|
||||||
|
- path: helper.yml
|
||||||
|
version: ` + canary + `
|
||||||
|
entries:
|
||||||
|
parent:
|
||||||
|
resource: icon.txt
|
||||||
|
child:
|
||||||
|
extends: parent
|
||||||
|
`)},
|
||||||
|
"bundle/config/records/helper.yml": {Data: []byte("id: helper-supplied\n")},
|
||||||
|
"bundle/config/records/icon.txt": {Data: []byte("icon payload")},
|
||||||
|
})
|
||||||
|
result, err := Load(filesystem.File("bundle/config/main.yml"), Options[map[string]any]{
|
||||||
|
Imports: true,
|
||||||
|
Filesystem: &FilesystemContext{Root: "bundle", FS: &filesystem},
|
||||||
|
Definition: Definition[map[string]any]{
|
||||||
|
Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }},
|
||||||
|
SequenceCompositions: []SequenceComposition{{Path: Keys("services", "records"), IdentityField: "id"}},
|
||||||
|
Secret: func(candidate Path) bool {
|
||||||
|
return candidate.String() == "/services/records/0/version"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SourceVersionMode: SourceVersionModeIfPresent,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
var loadError *LoadError
|
||||||
|
if errors.As(err, &loadError) {
|
||||||
|
t.Fatalf("load discovered item: %v; diagnostics: %#v", err, loadError.Diagnostics())
|
||||||
|
}
|
||||||
|
t.Fatalf("load discovered item: %v", err)
|
||||||
|
}
|
||||||
|
services := result.Config["services"].(map[string]any)
|
||||||
|
records := services["records"].([]any)
|
||||||
|
if len(records) != 2 || records[0].(map[string]any)["id"] != "helper-supplied" || records[0].(map[string]any)["version"] != nil || records[1].(map[string]any)["version"] != "authored" {
|
||||||
|
t.Fatalf("nested records = %#v", records)
|
||||||
|
}
|
||||||
|
childPath := Keys("services", "records").Index(0).Key("entries").Key("child").Key("resource")
|
||||||
|
child := records[0].(map[string]any)["entries"].(map[string]any)["child"].(map[string]any)
|
||||||
|
if child["resource"] != "icon.txt" {
|
||||||
|
t.Fatalf("inherited relative resource = %#v", child)
|
||||||
|
}
|
||||||
|
|
||||||
|
sources := result.Sources()
|
||||||
|
filesByID := make(map[string]string, len(sources))
|
||||||
|
for _, source := range sources {
|
||||||
|
filesByID[source.ID] = source.Path
|
||||||
|
}
|
||||||
|
var itemSource SourceInfo
|
||||||
|
for _, source := range sources {
|
||||||
|
if source.Path == "bundle/config/records/a-item.yml" {
|
||||||
|
itemSource = source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if itemSource.VersionCheck == nil || !itemSource.VersionCheck.Version.Present || !itemSource.VersionCheck.Redacted || itemSource.VersionCheck.Version.Text != "" {
|
||||||
|
t.Fatalf("consumed header inspection = %#v", itemSource.VersionCheck)
|
||||||
|
}
|
||||||
|
idInfo, ok := result.Metadata().Lookup(Keys("services", "records").Index(0).Key("id"))
|
||||||
|
if !ok || len(idInfo.Origin.Locations) != 1 || filesByID[idInfo.Origin.Locations[0].SourceID] != "bundle/config/records/helper.yml" {
|
||||||
|
t.Fatalf("assigned helper ID origin = %#v", idInfo)
|
||||||
|
}
|
||||||
|
resourceInfo, ok := result.Metadata().Lookup(childPath)
|
||||||
|
if !ok || len(resourceInfo.Origin.Locations) != 1 || filesByID[resourceInfo.Origin.Locations[0].SourceID] != "bundle/config/records/a-item.yml" {
|
||||||
|
t.Fatalf("inherited resource metadata = %#v", resourceInfo)
|
||||||
|
}
|
||||||
|
explanation, ok := result.Explain(childPath)
|
||||||
|
if !ok || explanation.Effective.YAML != "icon.txt\n" || len(explanation.Origin.Locations) != 1 || explanation.Origin.Locations[0].SourceID != resourceInfo.Origin.Locations[0].SourceID {
|
||||||
|
t.Fatalf("inherited resource explanation = %#v", explanation)
|
||||||
|
}
|
||||||
|
context, ok := result.SourceContext(resourceInfo.Origin.Locations[0].SourceID)
|
||||||
|
if !ok || context.FS == nil || context.BaseDir != "config/records" {
|
||||||
|
t.Fatalf("inherited resource context = %#v, %v", context, ok)
|
||||||
|
}
|
||||||
|
contents, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, child["resource"].(string)))
|
||||||
|
if err != nil || string(contents) != "icon payload" {
|
||||||
|
t.Fatalf("inherited resource read = %q, %v", contents, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Load(filesystem.File("bundle/config/main.yml"), Options[map[string]any]{
|
||||||
|
Imports: true,
|
||||||
|
Filesystem: &FilesystemContext{Root: "bundle", FS: &filesystem},
|
||||||
|
Definition: Definition[map[string]any]{
|
||||||
|
Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }},
|
||||||
|
SequenceCompositions: []SequenceComposition{{Path: Keys("services", "records"), IdentityField: "version"}},
|
||||||
|
Secret: func(candidate Path) bool {
|
||||||
|
return candidate.String() == "/services/records/0/version"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SourceVersionMode: SourceVersionModeIfPresent,
|
||||||
|
})
|
||||||
|
loadError, ok := err.(*LoadError)
|
||||||
|
if !ok || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != CodeComposeInvalid {
|
||||||
|
t.Fatalf("consumed header supplied an identity: %v", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(err.Error(), canary) {
|
||||||
|
t.Fatalf("diagnostic exposed the consumed secret header: %v", err)
|
||||||
|
}
|
||||||
|
var failedItemSource SourceInfo
|
||||||
|
foundFailedItem := false
|
||||||
|
for _, source := range loadError.Sources() {
|
||||||
|
if source.Path == "bundle/config/records/a-item.yml" {
|
||||||
|
failedItemSource = source
|
||||||
|
foundFailedItem = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundFailedItem || failedItemSource.VersionCheck == nil || failedItemSource.VersionCheck.Version.Text != "" || !failedItemSource.VersionCheck.Redacted {
|
||||||
|
t.Fatalf("failed-load source inspection exposed or omitted header evidence: %#v", failedItemSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
77
internal/engine/dependency_integration_test.go
Normal file
77
internal/engine/dependency_integration_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImportAndInheritanceIntegrationsUseEquivalentReadySchedules(t *testing.T) {
|
||||||
|
names := []string{"a-base", "b-independent", "z-dependent"}
|
||||||
|
keys := make(map[string]importNodeKey, len(names))
|
||||||
|
contributions := make([]acquiredContribution, len(names))
|
||||||
|
for index, name := range names {
|
||||||
|
key := importNodeKey{
|
||||||
|
namespaceKind: "host", namespaceAnchor: "/shared",
|
||||||
|
resourceKind: "file", resourcePath: "/" + name,
|
||||||
|
}
|
||||||
|
keys[name] = key
|
||||||
|
contributions[index] = acquiredContribution{
|
||||||
|
identity: key,
|
||||||
|
source: SourceInfo{ID: "source-" + name, Path: name},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var want []string
|
||||||
|
for _, reverse := range []bool{false, true} {
|
||||||
|
graph := newImportInclusionGraph()
|
||||||
|
orderedNames := append([]string(nil), names...)
|
||||||
|
if reverse {
|
||||||
|
orderedNames[0], orderedNames[2] = orderedNames[2], orderedNames[0]
|
||||||
|
}
|
||||||
|
for _, name := range orderedNames {
|
||||||
|
graph.addNode(importGraphNode{key: keys[name], sourceID: "source-" + name, role: importRoleMapping})
|
||||||
|
}
|
||||||
|
graph.addEdge(keys["z-dependent"], keys["a-base"], Location{})
|
||||||
|
|
||||||
|
scheduled := scheduleContributionSlots(contributions, []int{0, 1, 2}, graph)
|
||||||
|
got := make([]string, len(scheduled))
|
||||||
|
for index, contribution := range scheduled {
|
||||||
|
got[index] = contribution.identity.resourcePath[1:]
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, []string{"a-base", "b-independent", "z-dependent"}) {
|
||||||
|
t.Fatalf("import schedule reverse=%t: %v", reverse, got)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(
|
||||||
|
[]string{contributions[0].source.ID, contributions[1].source.ID, contributions[2].source.ID},
|
||||||
|
[]string{"source-a-base", "source-b-independent", "source-z-dependent"},
|
||||||
|
) {
|
||||||
|
t.Fatal("scheduling mutated acquisition source records")
|
||||||
|
}
|
||||||
|
|
||||||
|
inheritanceText := "a-base: {value: ready}\nb-independent: {value: independent}\nz-dependent: {extends: a-base}\n"
|
||||||
|
if reverse {
|
||||||
|
inheritanceText = "z-dependent: {extends: a-base}\nb-independent: {value: independent}\na-base: {value: ready}\n"
|
||||||
|
}
|
||||||
|
scope := parseCompositionDocument(t, "inheritance", inheritanceText)
|
||||||
|
inheritance, diagnostics := analyzeInheritanceGraph(scope, Path{})
|
||||||
|
if len(diagnostics) != 0 {
|
||||||
|
t.Fatalf("analyze inheritance reverse=%t: %#v", reverse, diagnostics)
|
||||||
|
}
|
||||||
|
order, diagnostics := inheritance.schedule()
|
||||||
|
if len(diagnostics) != 0 {
|
||||||
|
t.Fatalf("schedule inheritance reverse=%t: %#v", reverse, diagnostics)
|
||||||
|
}
|
||||||
|
inheritanceNames := make([]string, len(order))
|
||||||
|
for index, node := range order {
|
||||||
|
inheritanceNames[index] = inheritance.nodes[node].name
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(inheritanceNames, got) {
|
||||||
|
t.Fatalf("equivalent graph schedules differ reverse=%t: imports=%v inheritance=%v", reverse, got, inheritanceNames)
|
||||||
|
}
|
||||||
|
if want == nil {
|
||||||
|
want = got
|
||||||
|
} else if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("import graph construction changed schedule: got %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user