224 lines
8.6 KiB
Go
224 lines
8.6 KiB
Go
|
|
package configkit_test
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"go.maximumdirect.net/configkit"
|
||
|
|
)
|
||
|
|
|
||
|
|
type resolutionSequenceItem struct {
|
||
|
|
ID string `yaml:"id"`
|
||
|
|
Value string `yaml:"value"`
|
||
|
|
}
|
||
|
|
type resolutionSequenceConfig struct {
|
||
|
|
Groups map[string][]resolutionSequenceItem `yaml:"groups"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPublicResolutionOrdinaryListField(t *testing.T) {
|
||
|
|
type config struct {
|
||
|
|
Items []resolutionSequenceItem `yaml:"items"`
|
||
|
|
}
|
||
|
|
definition := configkit.Definition[config]{Resolve: func(value *config, r *configkit.Resolution) error {
|
||
|
|
configkit.Normalize(r, configkit.Keys("items").Index(0).Key("value"), &value.Items[0].Value, "changed", "normalize")
|
||
|
|
return nil
|
||
|
|
}}
|
||
|
|
result, err := configkit.Load(configkit.Bytes("items", []byte("items: [{id: a, value: old}]\n")), configkit.Options[config]{Definition: definition})
|
||
|
|
if err != nil || result.Config.Items[0].Value != "changed" {
|
||
|
|
t.Fatalf("result = %#v, %v", result, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPublicRegisteredResolutionHistoryAndOwnership(t *testing.T) {
|
||
|
|
selector := configkit.SequenceSelector{configkit.SequenceLiteralKey("groups"), configkit.SequenceAnyMappingKey()}
|
||
|
|
path := configkit.Keys("groups", "active")
|
||
|
|
entryFixed := false
|
||
|
|
definition := configkit.Definition[resolutionSequenceConfig]{
|
||
|
|
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityMatching(selector, "id")},
|
||
|
|
Resolve: func(value *resolutionSequenceConfig, r *configkit.Resolution) error {
|
||
|
|
entryFixed = r.IsExplicit(path.Index(0).Key("value")) && !r.IsExplicit(path.Index(1).Key("value"))
|
||
|
|
items := value.Groups["active"]
|
||
|
|
items[0], items[1] = items[1], items[0]
|
||
|
|
items[1].Value = "normalized"
|
||
|
|
value.Groups["active"] = items
|
||
|
|
r.RecordNormalization(path, "reorder")
|
||
|
|
r.RecordNormalization(path.Index(1).Key("value"), "normalize")
|
||
|
|
return nil
|
||
|
|
},
|
||
|
|
}
|
||
|
|
source := configkit.Bytes("records", []byte("groups:\n active:\n - id: a\n value: original\n - id: b\n"))
|
||
|
|
load := func() *configkit.Result[resolutionSequenceConfig] {
|
||
|
|
result, err := configkit.Load(source, configkit.Options[resolutionSequenceConfig]{Definition: definition})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
result := load()
|
||
|
|
if !entryFixed || result.Config.Groups["active"][1].Value != "normalized" {
|
||
|
|
t.Fatalf("result = %#v", result.Config)
|
||
|
|
}
|
||
|
|
info, ok := result.Metadata().Lookup(path.Index(1).Key("value"))
|
||
|
|
if !ok || !info.Explicit || info.Origin.Layer != configkit.LayerResolution {
|
||
|
|
t.Fatalf("metadata = %#v, %t", info, ok)
|
||
|
|
}
|
||
|
|
explanation, ok := result.Explain(path.Index(1).Key("value"))
|
||
|
|
if !ok {
|
||
|
|
t.Fatal("missing explanation")
|
||
|
|
}
|
||
|
|
found := false
|
||
|
|
for _, change := range explanation.History {
|
||
|
|
if change.Origin.Layer == configkit.LayerResolution && change.Before.YAML == "original\n" && change.After.YAML == "normalized\n" {
|
||
|
|
found = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !found {
|
||
|
|
t.Fatalf("history = %#v", explanation.History)
|
||
|
|
}
|
||
|
|
other := load()
|
||
|
|
result.Config.Groups["active"][1].Value = "mutated"
|
||
|
|
if len(explanation.History) != 0 {
|
||
|
|
explanation.History[0].After.YAML = "mutated"
|
||
|
|
}
|
||
|
|
again, _ := result.Explain(path.Index(1).Key("value"))
|
||
|
|
if strings.Contains(again.Effective.YAML, "mutated") || other.Config.Groups["active"][1].Value != "normalized" {
|
||
|
|
t.Fatal("inspection or independent load changed")
|
||
|
|
}
|
||
|
|
plain, err := configkit.Load[resolutionSequenceConfig](source)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
differences, err := configkit.Diff(plain, result)
|
||
|
|
if err != nil || len(differences) != 1 || differences[0].Path.String() != "/groups/active" {
|
||
|
|
t.Fatalf("diff = %#v, %v", differences, err)
|
||
|
|
}
|
||
|
|
layered, err := configkit.LoadLayered(configkit.Input{Source: source}, configkit.LayeredOptions[resolutionSequenceConfig]{Definition: definition})
|
||
|
|
if err != nil || layered.Config.Groups["active"][1].Value != other.Config.Groups["active"][1].Value {
|
||
|
|
t.Fatalf("layered = %#v, %v", layered, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPublicRegisteredMoveRequiresContainerRecord(t *testing.T) {
|
||
|
|
type config struct {
|
||
|
|
Items []resolutionSequenceItem `yaml:"items"`
|
||
|
|
}
|
||
|
|
_, err := configkit.Load(configkit.Bytes("items", []byte("items: [{id: a}, {id: b}]\n")), configkit.Options[config]{Definition: configkit.Definition[config]{
|
||
|
|
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("items"), "id")},
|
||
|
|
Resolve: func(value *config, r *configkit.Resolution) error {
|
||
|
|
value.Items[0], value.Items[1] = value.Items[1], value.Items[0]
|
||
|
|
r.RecordNormalization(configkit.Keys("items").Index(1).Key("id"), "record field")
|
||
|
|
return nil
|
||
|
|
},
|
||
|
|
}})
|
||
|
|
var failure *configkit.LoadError
|
||
|
|
if !errors.As(err, &failure) {
|
||
|
|
t.Fatalf("error = %v", err)
|
||
|
|
}
|
||
|
|
for _, diagnostic := range failure.Diagnostics() {
|
||
|
|
if diagnostic.Code == configkit.CodeResolveUnrecordedChange && diagnostic.Path.String() == "/items" {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
t.Fatalf("diagnostics = %#v", failure.Diagnostics())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPublicRegisteredRemovalKeepsSurvivorHistory(t *testing.T) {
|
||
|
|
type config struct {
|
||
|
|
Items []resolutionSequenceItem `yaml:"items"`
|
||
|
|
}
|
||
|
|
definition := configkit.Definition[config]{
|
||
|
|
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("items"), "id")},
|
||
|
|
Resolve: func(value *config, r *configkit.Resolution) error {
|
||
|
|
value.Items = value.Items[1:]
|
||
|
|
r.RecordNormalization(configkit.Keys("items"), "remove first")
|
||
|
|
return nil
|
||
|
|
},
|
||
|
|
}
|
||
|
|
result, err := configkit.Load(configkit.Bytes("items", []byte("items:\n - id: reports\n value: report\n - id: invoices\n value: invoice\n")), configkit.Options[config]{Definition: definition})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
explanation, ok := result.Explain(configkit.Keys("items").Index(0).Key("value"))
|
||
|
|
if !ok {
|
||
|
|
t.Fatal("missing survivor")
|
||
|
|
}
|
||
|
|
for _, change := range explanation.History {
|
||
|
|
if change.Origin.Layer == configkit.LayerMain && change.After.YAML == "report\n" {
|
||
|
|
t.Fatalf("wrong lineage = %#v", explanation.History)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
container, ok := result.Explain(configkit.Keys("items"))
|
||
|
|
if !ok {
|
||
|
|
t.Fatal("missing collection explanation")
|
||
|
|
}
|
||
|
|
found := false
|
||
|
|
for _, change := range container.History {
|
||
|
|
if change.Origin.Layer == configkit.LayerResolution && strings.Contains(change.Before.YAML, "reports") {
|
||
|
|
found = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !found {
|
||
|
|
t.Fatalf("missing removal evidence = %#v", container.History)
|
||
|
|
}
|
||
|
|
if _, ok := result.Explain(configkit.Keys("items").Index(1)); !ok {
|
||
|
|
t.Fatal("missing historical position")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPublicYAMLIdentityRemainsPositionalDuringResolution(t *testing.T) {
|
||
|
|
type config struct {
|
||
|
|
Items []resolutionSequenceItem `yaml:"items"`
|
||
|
|
}
|
||
|
|
result, err := configkit.Load(configkit.Bytes("items", []byte("config: {sequences: [{destination: [items], key: id}]}\nitems: [{id: a, value: first}, {id: b, value: second}]\n")), configkit.Options[config]{Definition: configkit.Definition[config]{Resolve: func(value *config, r *configkit.Resolution) error {
|
||
|
|
value.Items[0], value.Items[1] = value.Items[1], value.Items[0]
|
||
|
|
r.RecordNormalization(configkit.Keys("items"), "reorder")
|
||
|
|
return nil
|
||
|
|
}}})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
explanation, ok := result.Explain(configkit.Keys("items").Index(0).Key("value"))
|
||
|
|
if !ok {
|
||
|
|
t.Fatal("missing explanation")
|
||
|
|
}
|
||
|
|
for _, change := range explanation.History {
|
||
|
|
if change.Origin.Layer == configkit.LayerResolution && change.Before.YAML == "first\n" && change.After.YAML == "second\n" {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
t.Fatalf("positional history = %#v", explanation.History)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPublicMovedSensitiveRecordIsHidden(t *testing.T) {
|
||
|
|
type config struct {
|
||
|
|
Items []resolutionSequenceItem `yaml:"items"`
|
||
|
|
}
|
||
|
|
result, err := configkit.Load(configkit.Bytes("items", []byte("items: [{id: a, value: FACADE_PRIVATE_CANARY}, {id: b, value: public}]\n")), configkit.Options[config]{Definition: configkit.Definition[config]{
|
||
|
|
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("items"), "id")},
|
||
|
|
Sensitive: func(path configkit.Path) bool { return path.String() == "/items/0/value" },
|
||
|
|
Resolve: func(value *config, r *configkit.Resolution) error {
|
||
|
|
value.Items[0], value.Items[1] = value.Items[1], value.Items[0]
|
||
|
|
r.RecordNormalization(configkit.Keys("items"), "reorder")
|
||
|
|
return nil
|
||
|
|
},
|
||
|
|
}})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, path := range []configkit.Path{configkit.Keys("items"), configkit.Keys("items").Index(1).Key("value")} {
|
||
|
|
explanation, ok := result.Explain(path)
|
||
|
|
if !ok {
|
||
|
|
t.Fatalf("missing explanation %s", path.String())
|
||
|
|
}
|
||
|
|
if strings.Contains(explanation.Effective.YAML, "FACADE_PRIVATE_CANARY") {
|
||
|
|
t.Fatal("effective leak")
|
||
|
|
}
|
||
|
|
for _, change := range explanation.History {
|
||
|
|
if strings.Contains(change.Before.YAML, "FACADE_PRIVATE_CANARY") || strings.Contains(change.After.YAML, "FACADE_PRIVATE_CANARY") {
|
||
|
|
t.Fatal("history leak")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|