174 lines
8.1 KiB
Go
174 lines
8.1 KiB
Go
package ckconfig
|
|
|
|
import (
|
|
"bytes"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
func renderAndDecode(t *testing.T, differences []configkit.Difference) (*yaml.Node, []byte) {
|
|
t.Helper()
|
|
report, err := renderDiffReport(differences)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var document yaml.Node
|
|
if err := yaml.Unmarshal(report, &document); err != nil {
|
|
t.Fatalf("decode rendered report %q: %v", report, err)
|
|
}
|
|
return document.Content[0], report
|
|
}
|
|
|
|
func reportField(mapping *yaml.Node, key string) *yaml.Node {
|
|
for index := 0; index+1 < len(mapping.Content); index += 2 {
|
|
if mapping.Content[index].Value == key {
|
|
return mapping.Content[index+1]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestRenderDiffReportClassifiesAndOrdersNativeRecords(t *testing.T) {
|
|
differences := []configkit.Difference{
|
|
{Path: configkit.Keys("z-added"), Right: configkit.ValueSnapshot{Present: true, YAML: "false\n"}},
|
|
{Path: configkit.Keys("count"), Left: configkit.ValueSnapshot{Present: true, YAML: "1\n"}, Right: configkit.ValueSnapshot{Present: true, YAML: "9007199254740993\n"}},
|
|
{Path: configkit.Keys("removed"), Left: configkit.ValueSnapshot{Present: true, YAML: "unused"}},
|
|
{Path: configkit.Keys("a-added"), Right: configkit.ValueSnapshot{Present: true, YAML: "'123'\n"}},
|
|
}
|
|
root, _ := renderAndDecode(t, differences)
|
|
if root.Kind != yaml.MappingNode || len(root.Content) != 6 {
|
|
t.Fatalf("report root = %#v", root)
|
|
}
|
|
if got := []string{root.Content[0].Value, root.Content[2].Value, root.Content[4].Value}; !reflect.DeepEqual(got, []string{"additions", "changes", "deletions"}) {
|
|
t.Fatalf("report category order = %#v", got)
|
|
}
|
|
additions := reportField(root, "additions")
|
|
if additions.Kind != yaml.MappingNode || additions.Content[0].Value != "z-added" || additions.Content[2].Value != "a-added" {
|
|
t.Fatalf("addition order/value = %#v", additions)
|
|
}
|
|
if additions.Content[1].Kind != yaml.ScalarNode || additions.Content[1].Tag != "!!bool" || additions.Content[1].Value != "false" {
|
|
t.Fatalf("boolean addition lost its type: %#v", additions.Content[1])
|
|
}
|
|
if additions.Content[3].Tag != "!!str" || additions.Content[3].Value != "123" {
|
|
t.Fatalf("numeric-looking string lost its type: %#v", additions.Content[3])
|
|
}
|
|
changes := reportField(root, "changes")
|
|
if changes.Kind != yaml.MappingNode || changes.Content[1].Tag != "!!int" || changes.Content[1].Value != "9007199254740993" {
|
|
t.Fatalf("change value/type = %#v", changes)
|
|
}
|
|
deletions := reportField(root, "deletions")
|
|
if deletions.Kind != yaml.SequenceNode || len(deletions.Content) != 1 || deletions.Content[0].Tag != "!!str" || deletions.Content[0].Value != "/removed" {
|
|
t.Fatalf("deletion records = %#v", deletions)
|
|
}
|
|
}
|
|
|
|
func TestRenderDiffReportEmitsEmptyCategoriesAndRootValue(t *testing.T) {
|
|
root, _ := renderAndDecode(t, nil)
|
|
if got := []string{root.Content[0].Value, root.Content[2].Value, root.Content[4].Value}; !reflect.DeepEqual(got, []string{"additions", "changes", "deletions"}) {
|
|
t.Fatalf("empty report keys = %#v", got)
|
|
}
|
|
if reportField(root, "additions").Kind != yaml.MappingNode || len(reportField(root, "additions").Content) != 0 || reportField(root, "changes").Kind != yaml.MappingNode || len(reportField(root, "changes").Content) != 0 || reportField(root, "deletions").Kind != yaml.SequenceNode || len(reportField(root, "deletions").Content) != 0 {
|
|
t.Fatalf("empty report categories = %#v", root)
|
|
}
|
|
|
|
root, _ = renderAndDecode(t, []configkit.Difference{{Right: configkit.ValueSnapshot{Present: true, YAML: "null\n"}}})
|
|
additions := reportField(root, "additions")
|
|
if additions.Tag != "!!null" || additions.Value != "null" {
|
|
t.Fatalf("whole-root addition was wrapped: %#v", additions)
|
|
}
|
|
}
|
|
|
|
func TestRenderDiffReportDistinguishesDeletionFromNullAndEmptyMappings(t *testing.T) {
|
|
differences := []configkit.Difference{
|
|
{Path: configkit.Keys("nullable"), Right: configkit.ValueSnapshot{Present: true, YAML: "null\n"}},
|
|
{Path: configkit.Keys("settings", "first"), Left: configkit.ValueSnapshot{Present: true, YAML: "intentionally not parsed"}},
|
|
{Path: configkit.Keys("settings", "second"), Left: configkit.ValueSnapshot{Present: true, YAML: "also not parsed"}},
|
|
}
|
|
root, _ := renderAndDecode(t, differences)
|
|
if got := reportField(root, "additions").Content; len(got) != 2 || got[0].Value != "nullable" || got[1].Tag != "!!null" {
|
|
t.Fatalf("explicit null addition = %#v", got)
|
|
}
|
|
deletions := reportField(root, "deletions")
|
|
if len(deletions.Content) != 2 || deletions.Content[0].Value != "/settings/first" || deletions.Content[1].Value != "/settings/second" {
|
|
t.Fatalf("mapping-to-empty deletion records = %#v", deletions)
|
|
}
|
|
}
|
|
|
|
func TestRenderDiffReportKeepsAtomicSequencesAndStructuralValues(t *testing.T) {
|
|
differences := []configkit.Difference{
|
|
{Path: configkit.Keys("items"), Left: configkit.ValueSnapshot{Present: true}, Right: configkit.ValueSnapshot{Present: true, YAML: "- one\n- two\n"}},
|
|
{Path: configkit.Keys("settings"), Left: configkit.ValueSnapshot{Present: true}, Right: configkit.ValueSnapshot{Present: true, YAML: "nested:\n enabled: true\n"}},
|
|
}
|
|
root, _ := renderAndDecode(t, differences)
|
|
changes := reportField(root, "changes")
|
|
if changes.Content[1].Kind != yaml.SequenceNode || len(changes.Content[1].Content) != 2 {
|
|
t.Fatalf("sequence change was expanded or altered: %#v", changes.Content[1])
|
|
}
|
|
settings := reportField(changes, "settings")
|
|
if settings.Kind != yaml.MappingNode || reportField(settings, "nested").Kind != yaml.MappingNode || reportField(reportField(settings, "nested"), "enabled").Tag != "!!bool" {
|
|
t.Fatalf("structural replacement was altered: %#v", settings)
|
|
}
|
|
}
|
|
|
|
func TestRenderDiffReportPreservesNativeMasksAndMarkerLookingStrings(t *testing.T) {
|
|
type settings struct {
|
|
Public string `yaml:"public"`
|
|
Secret string `yaml:"secret"`
|
|
}
|
|
definition := configkit.Definition[settings]{Secret: func(path configkit.Path) bool { return path.String() == "/secret" }}
|
|
load := func(public, secret string) *configkit.Result[settings] {
|
|
result, err := configkit.Load(configkit.Mapping("configuration", map[string]any{"public": public, "secret": secret}), configkit.Options[settings]{Definition: definition})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return result
|
|
}
|
|
left := load("visible", "old-secret")
|
|
right := load("[REDACTED]", "new-secret")
|
|
differences, err := configkit.Diff(left, right)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(differences) != 2 || differences[0].Path.String() != "/public" || differences[1].Path.String() != "/secret" || !differences[1].HiddenChanged || !differences[1].Right.Redacted {
|
|
t.Fatalf("native changes/mask metadata = %#v", differences)
|
|
}
|
|
root, report := renderAndDecode(t, differences)
|
|
changes := reportField(root, "changes")
|
|
for _, key := range []string{"public", "secret"} {
|
|
value := reportField(changes, key)
|
|
if value == nil || value.Tag != "!!str" || value.Value != "[REDACTED]" {
|
|
t.Fatalf("safe value for %q = %#v", key, value)
|
|
}
|
|
}
|
|
if bytes.Contains(report, []byte("HiddenChanged")) || bytes.Contains(report, []byte("Redacted")) || strings.Contains(string(report), "old-secret") || strings.Contains(string(report), "new-secret") {
|
|
t.Fatalf("report exposed native metadata or secret contents: %q", report)
|
|
}
|
|
|
|
concealedOnly := load("[REDACTED]", "later-secret")
|
|
concealedDifferences, err := configkit.Diff(right, concealedOnly)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(concealedDifferences) != 1 || concealedDifferences[0].Path.String() != "/secret" || !concealedDifferences[0].HiddenChanged {
|
|
t.Fatalf("concealed-only native records = %#v", concealedDifferences)
|
|
}
|
|
concealedRoot, _ := renderAndDecode(t, concealedDifferences)
|
|
if value := reportField(reportField(concealedRoot, "changes"), "secret"); value == nil || value.Tag != "!!str" || value.Value != "[REDACTED]" {
|
|
t.Fatalf("concealed-only safe value = %#v", value)
|
|
}
|
|
}
|
|
|
|
func TestRenderDiffReportReportsMalformedPresentSnapshot(t *testing.T) {
|
|
report, err := renderDiffReport([]configkit.Difference{{
|
|
Path: configkit.Keys("setting"),
|
|
Right: configkit.ValueSnapshot{Present: true, YAML: "setting: [unterminated\n"},
|
|
}})
|
|
if err == nil || report != nil || !strings.Contains(err.Error(), "/setting") {
|
|
t.Fatalf("malformed snapshot report/error = %q / %v", report, err)
|
|
}
|
|
}
|