Migrate structural and inspection fixtures
This commit is contained in:
@@ -200,6 +200,8 @@ Resource tests; defer unrelated stale structural fixtures to Stage 6.
|
||||
|
||||
## Stage 6 — Migrate engine structural and inspection fixtures
|
||||
|
||||
**Status:** Complete. The engine suite passes, including composition, inheritance, and source-evidence regressions.
|
||||
|
||||
**Scope:** remaining engine composition, collection, inheritance, lifecycle,
|
||||
history, provenance, explanation, sensitivity, and rendering tests/benchmarks.
|
||||
|
||||
|
||||
@@ -7,6 +7,28 @@ import (
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
func TestNestedConfigDataSurvivesCompositionAndPrecedence(t *testing.T) {
|
||||
loaded, err := testGroupedLoad(loadRequest[map[string]any]{
|
||||
main: loadInput{source: Bytes("main", []byte("service: {config: {enabled: false, count: 0}}\n"))},
|
||||
underlays: []loadGroup{{inputs: []loadInput{
|
||||
{source: Bytes("left", []byte("service: {config: {enabled: true, empty: ''}}\n"))},
|
||||
{source: Bytes("right", []byte("service: {config: {count: 3, optional: null}}\n"))},
|
||||
}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
values := loaded.Config["service"].(map[string]any)["config"].(map[string]any)
|
||||
if values["enabled"] != false || values["count"] != int64(0) || values["empty"] != "" || values["optional"] != nil {
|
||||
t.Fatalf("composed data = %#v", values)
|
||||
}
|
||||
for _, key := range []string{"enabled", "count", "empty", "optional"} {
|
||||
if info, ok := loaded.Metadata().Lookup(Keys("service", "config", key)); !ok || !info.Explicit {
|
||||
t.Errorf("presence for %s = %#v, %t", key, info, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedCollectionInitializationIsIndependentOfAncestorPresence(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, ancestor := range []string{"{}", "group: {}", "group: null"} {
|
||||
@@ -24,7 +46,7 @@ func TestNestedCollectionInitializationIsIndependentOfAncestorPresence(t *testin
|
||||
{mode: "upsert", key: ", key: id", value: "[]", valid: true},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%s/%s/%s/%s", ancestor, test.mode, test.key, test.value), func(t *testing.T) {
|
||||
main := "configkit:\n sequences: [{destination: [group, items], mode: " + test.mode + test.key + "}]\ngroup: {items: " + test.value + "}\n"
|
||||
main := "config:\n sequences: [{destination: [group, items], mode: " + test.mode + test.key + "}]\ngroup: {items: " + test.value + "}\n"
|
||||
loaded, err := testGroupedLoad(loadRequest[map[string]any]{
|
||||
main: loadInput{source: Bytes("main", []byte(main))},
|
||||
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte(ancestor))}}}},
|
||||
@@ -60,13 +82,13 @@ func TestAddedAncestorTransfersNestedRecordPolicies(t *testing.T) {
|
||||
} `yaml:"group"`
|
||||
}
|
||||
files := fstest.MapFS{
|
||||
"main.yml": {Data: []byte(`configkit:
|
||||
"main.yml": {Data: []byte(`config:
|
||||
sequences: [{destination: [group, items], key: id}]
|
||||
imports: [{path: base.yml, destination: [group, items], placement: sequence-item}]
|
||||
group:
|
||||
items: [{id: child, extends: {id: base}, steps: [{name: child}]}]
|
||||
`)},
|
||||
"base.yml": {Data: []byte("configkit:\n sequences: [{destination: [steps], key: name}]\nid: base\nsteps: [{name: base}]\n")},
|
||||
"base.yml": {Data: []byte("config:\n sequences: [{destination: [steps], key: name}]\nid: base\nsteps: [{name: base}]\n")},
|
||||
}
|
||||
loaded, err := testGroupedLoad(loadRequest[config]{
|
||||
main: loadInput{source: NewFilesystem("catalog", files).File("main.yml"), imports: true},
|
||||
@@ -88,7 +110,7 @@ group:
|
||||
|
||||
func TestInheritanceChecksCollectionOperationUnderAddedAncestor(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := testLoad[map[string]any](Bytes("inheritance", []byte(`configkit:
|
||||
_, err := testLoad[map[string]any](Bytes("inheritance", []byte(`config:
|
||||
sequences: [{destination: [child, group, items], mode: append}]
|
||||
base: {}
|
||||
child:
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestResolveCollectionPolicyReconcilesDeclarationsBeforeComposition(t *testing.T) {
|
||||
declarations := extractSequenceDeclarationsYAML(t, `configkit:
|
||||
declarations := extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: [namespace, id]
|
||||
@@ -40,7 +40,7 @@ func TestResolveCollectionPolicyRejectsConflictingPeersAtBothSites(t *testing.T)
|
||||
}{
|
||||
{
|
||||
name: "identity fields",
|
||||
body: `configkit:
|
||||
body: `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: id
|
||||
@@ -50,7 +50,7 @@ func TestResolveCollectionPolicyRejectsConflictingPeersAtBothSites(t *testing.T)
|
||||
},
|
||||
{
|
||||
name: "explicit modes",
|
||||
body: `configkit:
|
||||
body: `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
mode: append
|
||||
@@ -85,7 +85,7 @@ func TestResolveCollectionPolicySelectsCurrentThenRetainedExplicitMode(t *testin
|
||||
explicitModePresent: true,
|
||||
modeLocation: &Location{SourceID: "lower", Line: 2},
|
||||
}
|
||||
current := extractSequenceDeclarationsYAML(t, `configkit:
|
||||
current := extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
mode: upsert
|
||||
@@ -105,7 +105,7 @@ func TestResolveCollectionPolicySelectsCurrentThenRetainedExplicitMode(t *testin
|
||||
}
|
||||
|
||||
func TestResolveCollectionPolicyInfersWithoutPersistingMode(t *testing.T) {
|
||||
keyOnly := extractSequenceDeclarationsYAML(t, `configkit:
|
||||
keyOnly := extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: id
|
||||
@@ -120,7 +120,7 @@ func TestResolveCollectionPolicyInfersWithoutPersistingMode(t *testing.T) {
|
||||
t.Fatalf("application identity was not kept distinct from YAML policy: %#v, %#v", resolved, diagnostics)
|
||||
}
|
||||
|
||||
noIdentity := extractSequenceDeclarationsYAML(t, `configkit:
|
||||
noIdentity := extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
`, rootDirectiveContextSource).sequences
|
||||
@@ -129,7 +129,7 @@ func TestResolveCollectionPolicyInfersWithoutPersistingMode(t *testing.T) {
|
||||
t.Fatalf("unkeyed mode inference = %#v, %#v", resolved, diagnostics)
|
||||
}
|
||||
|
||||
explicitDefault := extractSequenceDeclarationsYAML(t, `configkit:
|
||||
explicitDefault := extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
mode: replace
|
||||
@@ -141,7 +141,7 @@ func TestResolveCollectionPolicyInfersWithoutPersistingMode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveCollectionPolicyChecksKeyOnlyAgainstApplicationConstraint(t *testing.T) {
|
||||
declarations := extractSequenceDeclarationsYAML(t, `configkit:
|
||||
declarations := extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: [namespace, id]
|
||||
@@ -169,7 +169,7 @@ func TestResolveCollectionPolicyChecksRetainedIdentityAgainstApplicationConstrai
|
||||
}
|
||||
|
||||
func TestResolveCollectionPolicyRequiresCurrentIdentityToAgreeWithRetainedIdentity(t *testing.T) {
|
||||
declarations := extractSequenceDeclarationsYAML(t, `configkit:
|
||||
declarations := extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: name
|
||||
|
||||
@@ -12,8 +12,8 @@ func TestSourceReplacementRetiresIdentityForEveryValueKind(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, value := range []string{"null", "42", "{}", "[{name: new}]"} {
|
||||
t.Run(value, func(t *testing.T) {
|
||||
lower := "configkit:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n"
|
||||
main := "configkit:\n sequences: [{destination: [items], key: name, mode: replace}]\nitems: " + value + "\n"
|
||||
lower := "config:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n"
|
||||
main := "config:\n sequences: [{destination: [items], key: name, mode: replace}]\nitems: " + value + "\n"
|
||||
loaded, err := testGroupedLoad(loadRequest[map[string]any]{
|
||||
main: loadInput{source: Bytes("main", []byte(main))},
|
||||
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte(lower))}}}},
|
||||
@@ -36,7 +36,7 @@ func TestAncestorRemovalCanEstablishPendingReplacementIdentity(t *testing.T) {
|
||||
var loaded *testResult[map[string]any]
|
||||
var err error
|
||||
if inheritance {
|
||||
loaded, err = testLoad[map[string]any](Bytes("source", []byte(`configkit:
|
||||
loaded, err = testLoad[map[string]any](Bytes("source", []byte(`config:
|
||||
sequences:
|
||||
- {destination: [base, group, items], key: id}
|
||||
- {destination: [child, group, items], key: name}
|
||||
@@ -52,8 +52,8 @@ grandchild:
|
||||
loaded, err = testGroupedLoad(loadRequest[map[string]any]{
|
||||
main: loadInput{source: Bytes("main", []byte("group: {items: [{name: new}]}\n"))},
|
||||
underlays: []loadGroup{
|
||||
{inputs: []loadInput{{source: Bytes("base", []byte("configkit:\n sequences: [{destination: [group, items], key: id}]\ngroup: {items: [{id: old}]}\n"))}}},
|
||||
{inputs: []loadInput{{source: Bytes("remove", []byte("configkit:\n sequences: [{destination: [group, items], key: name}]\ngroup: null\n"))}}},
|
||||
{inputs: []loadInput{{source: Bytes("base", []byte("config:\n sequences: [{destination: [group, items], key: id}]\ngroup: {items: [{id: old}]}\n"))}}},
|
||||
{inputs: []loadInput{{source: Bytes("remove", []byte("config:\n sequences: [{destination: [group, items], key: name}]\ngroup: null\n"))}}},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -74,7 +74,7 @@ grandchild:
|
||||
|
||||
func TestInheritanceAncestorRetirementDoesNotResurrectOldIdentity(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := testLoad[map[string]any](Bytes("source", []byte(`configkit:
|
||||
_, err := testLoad[map[string]any](Bytes("source", []byte(`config:
|
||||
sequences: [{destination: [base, group, items], key: id}]
|
||||
base: {group: {items: [{id: old}]}}
|
||||
child:
|
||||
@@ -92,7 +92,7 @@ grandchild:
|
||||
func TestReplacementRetirementPreservesApplicationConstraintsAndRetainedKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, withValue := range []bool{false, true} {
|
||||
main := "configkit:\n sequences: [{destination: [items], key: name, mode: replace}]\n"
|
||||
main := "config:\n sequences: [{destination: [items], key: name, mode: replace}]\n"
|
||||
if withValue {
|
||||
main += "items: null\n"
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func TestReplacementRetirementPreservesApplicationConstraintsAndRetainedKeys(t *
|
||||
}
|
||||
_, err := testGroupedLoad(loadRequest[map[string]any]{
|
||||
main: loadInput{source: Bytes("main", []byte(main))}, definition: definition,
|
||||
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("configkit:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n"))}}}},
|
||||
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("config:\n sequences: [{destination: [items], key: id}]\nitems: [{id: old}]\n"))}}}},
|
||||
})
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) || len(loadErr.Diagnostics()) == 0 || loadErr.Diagnostics()[0].Code != CodeSequenceConflict {
|
||||
|
||||
@@ -24,7 +24,7 @@ type explanationConcurrency struct {
|
||||
|
||||
func TestExplainShowsDefaultBaseAndEffectiveOverlayValue(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "config.yml", "configkit:\n imports: [{path: transcription.yml}]\n")
|
||||
mainPath := writeImportFile(t, directory, "config.yml", "config:\n imports: [{path: transcription.yml}]\n")
|
||||
writeImportFile(t, directory, "transcription.yml", "transcription:\n concurrency: 8\n")
|
||||
writeImportFile(t, directory, filepath.Join("overlays", "testing.yml"), "transcription:\n concurrency: 1\n")
|
||||
result, err := testLoad[explanationConcurrency](File(mainPath), Options[explanationConcurrency]{
|
||||
|
||||
@@ -235,7 +235,7 @@ func TestLayeredUpsertExplanationFollowsStructuralOrder(t *testing.T) {
|
||||
Records []map[string]any `yaml:"records"`
|
||||
}
|
||||
loaded, err := testGroupedLoad(loadRequest[config]{
|
||||
main: loadInput{source: Bytes("main", []byte("configkit:\n sequences: [{destination: [records], key: id}]\nrecords: [{id: b, value: new-b}, {id: c, value: new-c}, {id: a, value: new-a}]\n"))},
|
||||
main: loadInput{source: Bytes("main", []byte("config:\n sequences: [{destination: [records], key: id}]\nrecords: [{id: b, value: new-b}, {id: c, value: new-c}, {id: a, value: new-a}]\n"))},
|
||||
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("records: [{id: a, value: old-a}, {id: b, value: old-b, removed: old}]\n"))}}}},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -8,6 +8,27 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestControlEvidenceAndApplicationValuesSharePathText(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
main := writeImportFile(t, directory, "main.yml", "config: {imports: [{path: missing.yml}, {path: data.yml, destination: [config]}]}\n")
|
||||
writeImportFile(t, directory, "data.yml", "imports: [ordinary]\n")
|
||||
loaded, err := testLoad[map[string]any](File(main), Options[map[string]any]{Imports: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sources := loaded.Sources()
|
||||
if len(sources) != 2 || len(sources[0].importSelections) != 1 || sources[0].importSelections[0].path.String() != "/config/imports/0" || sources[0].importSelections[0].location.SourceID != sources[0].ID {
|
||||
t.Fatalf("source control evidence = %#v", sources)
|
||||
}
|
||||
info, ok := loaded.Metadata().Lookup(Keys("config", "imports").Index(0))
|
||||
if !ok || len(info.Origin.Locations) == 0 || info.Origin.Locations[0].SourceID != sources[1].ID {
|
||||
t.Fatalf("application value attribution = %#v, %t", info, ok)
|
||||
}
|
||||
if got := loaded.Config["config"].(map[string]any)["imports"].([]any)[0]; got != "ordinary" {
|
||||
t.Fatalf("application value = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBroadEmptyImportSelectionsKeepDeclaringSourceOwnership(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestInheritanceReplacementResetsOnlyReplacedYamlIdentity(t *testing.T) {
|
||||
|
||||
func TestLoadInheritanceReplacementUsesNewYamlKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
loaded, err := testLoad[map[string]any](Bytes("replacement", []byte(`configkit:
|
||||
loaded, err := testLoad[map[string]any](Bytes("replacement", []byte(`config:
|
||||
sequences:
|
||||
- {destination: [base, items], key: id}
|
||||
- {destination: [child, items], key: name, mode: replace}
|
||||
@@ -150,7 +150,7 @@ child:
|
||||
}
|
||||
|
||||
func TestLoadAppliesYamlCollectionIdentityDuringSiblingInheritance(t *testing.T) {
|
||||
input := `configkit:
|
||||
input := `config:
|
||||
sequences:
|
||||
- destination: [base, items]
|
||||
key: id
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestRecordInheritanceHistoryPreservesChildIdentity(t *testing.T) {
|
||||
key, reference = "[id, tenant]", "{id: base, tenant: parent-team}"
|
||||
identities["tenant"] = "child-team"
|
||||
}
|
||||
body := "configkit:\n sequences: [{destination: [items], key: " + key + "}]\nitems:\n - id: base\n tenant: parent-team\n value: inherited\n - id: child\n tenant: child-team\n extends: " + reference + "\n"
|
||||
body := "config:\n sequences: [{destination: [items], key: " + key + "}]\nitems:\n - id: base\n tenant: parent-team\n value: inherited\n - id: child\n tenant: child-team\n extends: " + reference + "\n"
|
||||
loaded, err := testLoad(Bytes("records", []byte(body)), Options[map[string]any]{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestInheritancePolicyIndexReplacesOnlyTheChangedScope(t *testing.T) {
|
||||
|
||||
func TestInheritanceWideIndependentYamlCollectionScopes(t *testing.T) {
|
||||
var body strings.Builder
|
||||
body.WriteString("configkit:\n sequences:\n")
|
||||
body.WriteString("config:\n sequences:\n")
|
||||
for index := range 64 {
|
||||
fmt.Fprintf(&body, " - {destination: [groups, group-%03d, base, items], key: id, mode: append}\n", index)
|
||||
}
|
||||
|
||||
@@ -71,6 +71,21 @@ func TestSiblingInheritanceResolvesReferencesAndKeepsInputs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSiblingInheritancePreservesNestedConfigData(t *testing.T) {
|
||||
input := parseCompositionDocument(t, "source-1", "parent: {config: {enabled: false, count: 0, empty: '', optional: null}}\nchild: {extends: parent}\n")
|
||||
result, diagnostics := expandSiblingDefinitions(input, Path{})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatal(diagnostics)
|
||||
}
|
||||
for _, key := range []string{"enabled", "count", "empty", "optional"} {
|
||||
parent, ok := documentAtPath(result.root, Keys("parent", "config", key))
|
||||
child, found := documentAtPath(result.root, Keys("child", "config", key))
|
||||
if !ok || !found || parent.Kind != child.Kind || parent.Text != child.Text {
|
||||
t.Fatalf("inherited %s = %#v, parent = %#v", key, child, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSiblingInheritanceTracksTransferAndIndependentChildren(t *testing.T) {
|
||||
input := parseCompositionDocument(t, "source-1", "first: {value: parent}\nsecond: {extends: first}\nthird: {extends: first}\n")
|
||||
result, diagnostics := expandSiblingDefinitions(input, Keys("catalog"))
|
||||
|
||||
@@ -584,7 +584,7 @@ func TestLoadRejectsUnavailableOptionsAndDisabledDirectives(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
result, err := testLoad[config](Bytes("directive.yml", []byte("nested:\n configkit: {}\n")))
|
||||
result, err := testLoad[config](Bytes("directive.yml", []byte("config: {imports: []}\n")))
|
||||
if result != nil || err == nil {
|
||||
t.Fatalf("result, err = %#v, %v", result, err)
|
||||
}
|
||||
@@ -593,7 +593,7 @@ func TestLoadRejectsUnavailableOptionsAndDisabledDirectives(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
diagnostic := loadError.Diagnostics()[0]
|
||||
if diagnostic.Code != CodeImportDisabled || diagnostic.Path.String() != "/nested/configkit" || diagnostic.Primary == nil || diagnostic.Primary.Line != 2 {
|
||||
if diagnostic.Code != CodeImportDisabled || diagnostic.Path.String() != "/config" || diagnostic.Primary == nil || diagnostic.Primary.Line != 1 {
|
||||
t.Fatalf("diagnostic = %#v", diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ func TestHistoricalPathsExposeHiddenBaseOwnershipWithoutResolvingBaseSecrets(t *
|
||||
for _, credential := range []string{"CONFIGKIT_HISTORY_TEST_KEY", "null"} {
|
||||
t.Run(credential, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
root := writeImportFile(t, dir, "main.yml", "configkit:\n imports: [{path: first.yml}, {path: second.yml}]\ncredential: CONFIGKIT_HISTORY_UNUSED_PROD_KEY\n")
|
||||
root := writeImportFile(t, dir, "main.yml", "config:\n imports: [{path: first.yml}, {path: second.yml}]\ncredential: CONFIGKIT_HISTORY_UNUSED_PROD_KEY\n")
|
||||
writeImportFile(t, dir, "first.yml", "definitions: {recap: {prompt: recap}}\n")
|
||||
writeImportFile(t, dir, "second.yml", "definitions: {recap: {output: recap.md}}\n")
|
||||
writeImportFile(t, dir, "overlays/testing.yml", "definitions: null\ncredential: "+credential+"\n")
|
||||
|
||||
@@ -25,7 +25,7 @@ type ordinarySequenceConfig struct {
|
||||
}
|
||||
|
||||
func TestOrdinaryLoadAcceptsDeclarationsWithoutImports(t *testing.T) {
|
||||
result, err := testLoad[ordinarySequenceConfig](Bytes("catalog.yml", []byte(`configkit:
|
||||
result, err := testLoad[ordinarySequenceConfig](Bytes("catalog.yml", []byte(`config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -48,7 +48,7 @@ items:
|
||||
}
|
||||
|
||||
func TestOrdinaryLoadKeepsDisabledImportRejection(t *testing.T) {
|
||||
_, err := testLoad[ordinarySequenceConfig](Bytes("disabled.yml", []byte(`configkit:
|
||||
_, err := testLoad[ordinarySequenceConfig](Bytes("disabled.yml", []byte(`config:
|
||||
imports: [{path: fragment.yml}]
|
||||
sequences: [{destination: [items], key: id}]
|
||||
`)))
|
||||
@@ -62,7 +62,7 @@ func TestOrdinaryLoadKeepsDisabledImportRejection(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOrdinaryLoadValidatesSingletonCollectionIdentity(t *testing.T) {
|
||||
_, err := testLoad[ordinarySequenceConfig](Bytes("missing-identity.yml", []byte(`configkit:
|
||||
_, err := testLoad[ordinarySequenceConfig](Bytes("missing-identity.yml", []byte(`config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -90,7 +90,7 @@ func TestOrdinaryLoadCombinesImportedCollectionPeers(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `config:
|
||||
imports:
|
||||
- path: first.yml
|
||||
- path: second.yml
|
||||
@@ -123,7 +123,7 @@ func TestOrdinaryLoadCombinesImportedCollectionPeers(t *testing.T) {
|
||||
if !reflect.DeepEqual(result.Config.Items, test.want) {
|
||||
t.Fatalf("items = %#v, want %#v", result.Config.Items, test.want)
|
||||
}
|
||||
inline, err := testLoad[ordinarySequenceConfig](Bytes("inline.yml", []byte(`configkit:
|
||||
inline, err := testLoad[ordinarySequenceConfig](Bytes("inline.yml", []byte(`config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -142,14 +142,14 @@ items: [{id: first}, {id: second}]
|
||||
|
||||
func TestImportedCollectionDeclarationsUseRelativePlacement(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `config:
|
||||
imports:
|
||||
- path: first.yml
|
||||
destination: [catalog]
|
||||
- path: second.yml
|
||||
destination: [catalog]
|
||||
`)
|
||||
fragment := `configkit:
|
||||
fragment := `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -171,13 +171,13 @@ items: [{id: ITEM}]
|
||||
|
||||
func TestSequenceItemSourceAppliesItsCollectionDeclarations(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `config:
|
||||
imports:
|
||||
- path: item.yml
|
||||
destination: [records]
|
||||
placement: sequence-item
|
||||
`)
|
||||
writeImportFile(t, directory, "item.yml", `configkit:
|
||||
writeImportFile(t, directory, "item.yml", `config:
|
||||
sequences:
|
||||
- destination: [children]
|
||||
key: id
|
||||
@@ -196,7 +196,7 @@ children: [{id: child}]
|
||||
}
|
||||
|
||||
func TestOrdinaryLoadAllowsDeclarationsWithoutValues(t *testing.T) {
|
||||
result, err := testLoad[ordinarySequenceConfig](Bytes("policy.yml", []byte(`configkit:
|
||||
result, err := testLoad[ordinarySequenceConfig](Bytes("policy.yml", []byte(`config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestCLIHistoryFollowsInheritanceAndPrecedesResolution(t *testing.T) {
|
||||
}
|
||||
override := Override{Path: query, Value: cliValue, Flag: "--value"}
|
||||
if test.records {
|
||||
body = "configkit:\n sequences: [{destination: [items], key: id}]\nitems: [{id: base, value: parent}, {id: child, extends: {id: base}}]\n"
|
||||
body = "config:\n sequences: [{destination: [items], key: id}]\nitems: [{id: base, value: parent}, {id: child, extends: {id: base}}]\n"
|
||||
query, parent = Keys("items").Index(1).Key("value"), Keys("items").Index(0).Key("value")
|
||||
if test.equal {
|
||||
cliValue = "parent"
|
||||
|
||||
@@ -178,7 +178,7 @@ func TestCLISequenceAssignmentReplacesAppendAndUpsertResults(t *testing.T) {
|
||||
Items []item `yaml:"items"`
|
||||
}
|
||||
declaration := func(mode string, id string) Source {
|
||||
return Bytes(id, []byte("configkit:\n sequences:\n - destination: [items]\n key: id\n mode: "+mode+"\nitems: [{id: "+id+"}]\n"))
|
||||
return Bytes(id, []byte("config:\n sequences:\n - destination: [items]\n key: id\n mode: "+mode+"\nitems: [{id: "+id+"}]\n"))
|
||||
}
|
||||
result, err := testGroupedLoad(loadRequest[config]{
|
||||
main: loadInput{source: declaration("upsert", "main")},
|
||||
|
||||
@@ -30,7 +30,7 @@ type overrideLifecycleConfig struct {
|
||||
|
||||
func TestOverridesRunAtHighestInputPrecedenceBeforeResolutionAndValidation(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports: [{path: base.yml}]\nversion: v1\n")
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "config:\n imports: [{path: base.yml}]\nversion: v1\n")
|
||||
writeImportFile(t, directory, "base.yml", `settings:
|
||||
target: base
|
||||
count: 4
|
||||
|
||||
@@ -101,7 +101,7 @@ func TestNoSourceSelectedOverlayUsesCompleteNativeContext(t *testing.T) {
|
||||
|
||||
func TestLoadAppliesSelectedOverlayAfterBaseAndBeforeDefaults(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `config:
|
||||
imports: [{path: base.yml}]
|
||||
settings:
|
||||
kept: main
|
||||
@@ -191,7 +191,7 @@ func TestLoadSelectedOverlayAppliesCollectionOperationsAndEvidence(t *testing.T)
|
||||
{
|
||||
name: "append",
|
||||
mode: "append",
|
||||
overlay: `configkit:
|
||||
overlay: `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -203,7 +203,7 @@ items: [{id: c, value: added}]
|
||||
{
|
||||
name: "upsert replaces complete records at retained positions",
|
||||
mode: "upsert",
|
||||
overlay: `configkit:
|
||||
overlay: `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -215,7 +215,7 @@ items: [{id: a, value: new}]
|
||||
{
|
||||
name: "replace",
|
||||
mode: "replace",
|
||||
overlay: `configkit:
|
||||
overlay: `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -283,7 +283,7 @@ func TestLoadSelectedOverlayReconcilesPendingAndRetainedCollectionPolicy(t *test
|
||||
}
|
||||
}
|
||||
t.Run("key only validates retained records", func(t *testing.T) {
|
||||
requireCode(t, "items: [{id: duplicate}, {id: duplicate}]\n", `configkit:
|
||||
requireCode(t, "items: [{id: duplicate}, {id: duplicate}]\n", `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -292,14 +292,14 @@ func TestLoadSelectedOverlayReconcilesPendingAndRetainedCollectionPolicy(t *test
|
||||
|
||||
t.Run("mode only retains lower identity", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
mode: append
|
||||
items: [{id: base}]
|
||||
`)
|
||||
writeImportFile(t, directory, "overlays/testing.yml", `configkit:
|
||||
writeImportFile(t, directory, "overlays/testing.yml", `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
mode: append
|
||||
@@ -316,7 +316,7 @@ items: [{id: overlay}]
|
||||
|
||||
t.Run("lower explicit mode remains active when overlay omits it", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -331,12 +331,12 @@ items: [{id: base}]
|
||||
})
|
||||
|
||||
t.Run("overlay identity must agree with retained identity", func(t *testing.T) {
|
||||
requireCode(t, `configkit:
|
||||
requireCode(t, `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
items: [{id: base}]
|
||||
`, `configkit:
|
||||
`, `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: slug
|
||||
@@ -352,7 +352,7 @@ items: [{id: overlay}]
|
||||
{mode: "replace"},
|
||||
{mode: "append", want: CodeSequenceInvalid},
|
||||
} {
|
||||
overlay := "configkit:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: null\n"
|
||||
overlay := "config:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: null\n"
|
||||
if test.want != "" {
|
||||
requireCode(t, "items: [{id: base}]\n", overlay, test.want)
|
||||
continue
|
||||
@@ -379,7 +379,7 @@ items: [{id: overlay}]
|
||||
{name: "append mapping", mode: "append", value: "{id: overlay}", wantCode: CodeSequenceInvalid},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
overlay := "configkit:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: " + test.value + "\n"
|
||||
overlay := "config:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: " + test.value + "\n"
|
||||
if test.wantCode != "" {
|
||||
requireCode(t, "items: [{id: base}]\n", overlay, test.wantCode)
|
||||
return
|
||||
@@ -399,7 +399,7 @@ items: [{id: overlay}]
|
||||
func TestLoadSelectedOverlayKeepsCollectionPoliciesIndependent(t *testing.T) {
|
||||
t.Parallel()
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -410,7 +410,7 @@ func TestLoadSelectedOverlayKeepsCollectionPoliciesIndependent(t *testing.T) {
|
||||
items: [{id: base}]
|
||||
catalog: {items: [{id: catalog-base}]}
|
||||
`)
|
||||
writeImportFile(t, directory, "overlays/testing.yml", `configkit:
|
||||
writeImportFile(t, directory, "overlays/testing.yml", `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
mode: upsert
|
||||
@@ -477,8 +477,7 @@ func TestLoadOverlayRejectsDirectivesVersionsAndBaseAliases(t *testing.T) {
|
||||
overlay string
|
||||
code Code
|
||||
}{
|
||||
{name: "root directive", overlay: "configkit: {}\n", code: CodeOverlayDirective},
|
||||
{name: "nested directive", overlay: "settings: {configkit: {}}\n", code: CodeOverlayDirective},
|
||||
{name: "root directive", overlay: "config: {}\n", code: CodeOverlayDirective},
|
||||
{name: "equal integer version", overlay: "schema: {version: 4}\n", code: CodeOverlayVersion},
|
||||
{name: "ancestor scalar", overlay: "schema: null\n", code: CodeOverlayVersion},
|
||||
} {
|
||||
@@ -502,7 +501,7 @@ func TestLoadOverlayRejectsDirectivesVersionsAndBaseAliases(t *testing.T) {
|
||||
|
||||
t.Run("overlay aliases imported base", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: overlays/testing.yml}]}\n")
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "config: {imports: [{path: overlays/testing.yml}]}\n")
|
||||
writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: value}\n")
|
||||
requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Imports: true, Overlay: "testing"}, CodeOverlayRepeated)
|
||||
})
|
||||
|
||||
@@ -177,7 +177,7 @@ func TestCombineSequenceReplacementResetsLowerIdentity(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCombineSequenceDifferentIdentityDeclarationsConflict(t *testing.T) {
|
||||
declarations := extractSequenceDeclarationsYAML(t, `configkit:
|
||||
declarations := extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: id
|
||||
@@ -256,7 +256,7 @@ func TestCombineSequenceClonesInputsAndDoesNotMutatePolicy(t *testing.T) {
|
||||
func TestCombineSequenceEquivalentIdentityFieldSets(t *testing.T) {
|
||||
lower := sequenceFromYAML(t, "items: [{id: a, namespace: one}]\n")
|
||||
higher := sequenceFromYAML(t, "items: [{namespace: one, id: b}]\n")
|
||||
policy, diagnostics := resolveCollectionPolicy(Keys("items"), extractSequenceDeclarationsYAML(t, `configkit:
|
||||
policy, diagnostics := resolveCollectionPolicy(Keys("items"), extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [items]
|
||||
key: [namespace, id, id]
|
||||
|
||||
@@ -23,12 +23,12 @@ func TestParseSequenceDeclarationPresenceAndRelativeDestination(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
extracted := extractSequenceDeclarationsYAML(t, "configkit:\n sequences:\n - "+test.declaration+"\n", rootDirectiveContextSource)
|
||||
extracted := extractSequenceDeclarationsYAML(t, "config:\n sequences:\n - "+test.declaration+"\n", rootDirectiveContextSource)
|
||||
if len(extracted.sequences) != 1 {
|
||||
t.Fatalf("extraction = %#v", extracted)
|
||||
}
|
||||
declaration := extracted.sequences[0]
|
||||
if declaration.destination.String() != "/prompts" || declaration.path.String() != "/configkit/sequences/0" {
|
||||
if declaration.destination.String() != "/prompts" || declaration.path.String() != "/config/sequences/0" {
|
||||
t.Fatalf("relative destination or declaration path changed: %#v", declaration)
|
||||
}
|
||||
if declaration.location.SourceID != "sequence-declarations" || declaration.location.Line == 0 {
|
||||
@@ -51,7 +51,7 @@ func TestParseSequenceDeclarationPresenceAndRelativeDestination(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseSequenceDeclarationNormalizesEquivalentFieldSets(t *testing.T) {
|
||||
extracted := extractSequenceDeclarationsYAML(t, `configkit:
|
||||
extracted := extractSequenceDeclarationsYAML(t, `config:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: [namespace, id, id]
|
||||
@@ -100,17 +100,17 @@ func TestParseSequenceDeclarationRejectsMalformedForms(t *testing.T) {
|
||||
body string
|
||||
path string
|
||||
}{
|
||||
{name: "sequences wrong type", body: "configkit: {sequences: nope}\n", path: "/configkit/sequences"},
|
||||
{name: "declaration wrong type", body: "configkit: {sequences: [nope]}\n", path: "/configkit/sequences/0"},
|
||||
{name: "missing destination", body: "configkit: {sequences: [{key: id}]}\n", path: "/configkit/sequences/0/destination"},
|
||||
{name: "destination wrong type", body: "configkit: {sequences: [{destination: nope}]}\n", path: "/configkit/sequences/0/destination"},
|
||||
{name: "reserved destination", body: "configkit: {sequences: [{destination: [configkit]}]}\n", path: "/configkit/sequences/0/destination/0"},
|
||||
{name: "key wrong type", body: "configkit: {sequences: [{destination: [prompts], key: true}]}\n", path: "/configkit/sequences/0/key"},
|
||||
{name: "empty key set", body: "configkit: {sequences: [{destination: [prompts], key: []}]}\n", path: "/configkit/sequences/0/key"},
|
||||
{name: "key member wrong type", body: "configkit: {sequences: [{destination: [prompts], key: [id, null]}]}\n", path: "/configkit/sequences/0/key/1"},
|
||||
{name: "mode wrong type", body: "configkit: {sequences: [{destination: [prompts], mode: true}]}\n", path: "/configkit/sequences/0/mode"},
|
||||
{name: "unknown mode", body: "configkit: {sequences: [{destination: [prompts], mode: merge}]}\n", path: "/configkit/sequences/0/mode"},
|
||||
{name: "unknown declaration field", body: "configkit: {sequences: [{destination: [prompts], append: true}]}\n", path: "/configkit/sequences/0/append"},
|
||||
{name: "sequences wrong type", body: "config: {sequences: nope}\n", path: "/config/sequences"},
|
||||
{name: "declaration wrong type", body: "config: {sequences: [nope]}\n", path: "/config/sequences/0"},
|
||||
{name: "missing destination", body: "config: {sequences: [{key: id}]}\n", path: "/config/sequences/0/destination"},
|
||||
{name: "destination wrong type", body: "config: {sequences: [{destination: nope}]}\n", path: "/config/sequences/0/destination"},
|
||||
{name: "reserved destination", body: "config: {sequences: [{destination: [extends]}]}\n", path: "/config/sequences/0/destination/0"},
|
||||
{name: "key wrong type", body: "config: {sequences: [{destination: [prompts], key: true}]}\n", path: "/config/sequences/0/key"},
|
||||
{name: "empty key set", body: "config: {sequences: [{destination: [prompts], key: []}]}\n", path: "/config/sequences/0/key"},
|
||||
{name: "key member wrong type", body: "config: {sequences: [{destination: [prompts], key: [id, null]}]}\n", path: "/config/sequences/0/key/1"},
|
||||
{name: "mode wrong type", body: "config: {sequences: [{destination: [prompts], mode: true}]}\n", path: "/config/sequences/0/mode"},
|
||||
{name: "unknown mode", body: "config: {sequences: [{destination: [prompts], mode: merge}]}\n", path: "/config/sequences/0/mode"},
|
||||
{name: "unknown declaration field", body: "config: {sequences: [{destination: [prompts], append: true}]}\n", path: "/config/sequences/0/append"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -17,10 +17,10 @@ func TestLayeredSequenceProjectsNestedSourceDestinations(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%s/underlay=%t", mode, underlay), func(t *testing.T) {
|
||||
files := fstest.MapFS{}
|
||||
for _, id := range []string{"a", "b", "c"} {
|
||||
files[id+"/record.yml"] = &fstest.MapFile{Data: []byte("configkit: {imports: [{path: helper.yml}]}\nid: " + id + "\n")}
|
||||
files[id+"/record.yml"] = &fstest.MapFile{Data: []byte("config: {imports: [{path: helper.yml}]}\nid: " + id + "\n")}
|
||||
files[id+"/helper.yml"] = &fstest.MapFile{Data: []byte("value: " + id + "\n")}
|
||||
}
|
||||
files["main.yml"] = &fstest.MapFile{Data: []byte("configkit:\n sequences: [{destination: [items], key: id, mode: " + mode + "}]\n imports: [{glob: '*/record.yml', destination: [items], placement: sequence-item}]\n")}
|
||||
files["main.yml"] = &fstest.MapFile{Data: []byte("config:\n sequences: [{destination: [items], key: id, mode: " + mode + "}]\n imports: [{glob: '*/record.yml', destination: [items], placement: sequence-item}]\n")}
|
||||
lower := "items: [{id: b}, {id: a}, {id: keep}]\n"
|
||||
want := map[string]int{"a": 1, "b": 0, "c": 3}
|
||||
if mode == "append" {
|
||||
|
||||
@@ -34,11 +34,11 @@ func TestSequenceUpsertProjectsIncomingOrigins(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
files := fstest.MapFS{}
|
||||
for _, id := range []string{"a", "b", "c"} {
|
||||
files[id+"/record.yml"] = &fstest.MapFile{Data: []byte("configkit: {imports: [{path: helper.yml}]}\nid: " + id + "\ndetails: {label: " + id + "}\n")}
|
||||
files[id+"/record.yml"] = &fstest.MapFile{Data: []byte("config: {imports: [{path: helper.yml}]}\nid: " + id + "\ndetails: {label: " + id + "}\n")}
|
||||
files[id+"/helper.yml"] = &fstest.MapFile{Data: []byte("details: {resource: asset.txt}\n")}
|
||||
files[id+"/asset.txt"] = &fstest.MapFile{Data: []byte(id + " asset")}
|
||||
}
|
||||
main := "configkit:\n sequences:\n - destination: " + destination + "\n key: id\n imports:\n - glob: '*/record.yml'\n destination: " + destination + "\n placement: sequence-item\n"
|
||||
main := "config:\n sequences:\n - destination: " + destination + "\n key: id\n imports:\n - glob: '*/record.yml'\n destination: " + destination + "\n placement: sequence-item\n"
|
||||
if inheritance {
|
||||
main += "base:\n items: [{id: b}, {id: a}, {id: keep}]\nchild:\n extends: base\n"
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
|
||||
|
||||
t.Run("ordinary imports retain registered composition", func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: fragment.yml}]}\nitems: [{id: authored}]\n")
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "config: {imports: [{path: fragment.yml}]}\nitems: [{id: authored}]\n")
|
||||
writeImportFile(t, directory, "fragment.yml", "items: [{id: imported}]\n")
|
||||
result, err := testLoad[config](File(mainPath), Options[config]{Imports: true, Definition: definition})
|
||||
if err != nil || len(result.Config.Items) != 2 || result.Config.Items[0].ID != "imported" || result.Config.Items[1].ID != "authored" {
|
||||
|
||||
Reference in New Issue
Block a user