Files
commandkit/integrations/ckconfig/sources_test.go

157 lines
7.2 KiB
Go

package ckconfig
import (
"bytes"
"context"
"io"
"reflect"
"strings"
"testing"
commandkit "go.maximumdirect.net/commandkit"
configkit "go.maximumdirect.net/configkit"
"go.yaml.in/yaml/v4"
)
func TestSourcesProjectNativeEvidenceInTextAndYAML(t *testing.T) {
servicePath, err := configkit.ParsePath("/services")
if err != nil {
t.Fatal(err)
}
otherPath, err := configkit.ParsePath("/other")
if err != nil {
t.Fatal(err)
}
versionPath, err := configkit.ParsePath("/version")
if err != nil {
t.Fatal(err)
}
nullVersionPath, err := configkit.ParsePath("/metadata/version")
if err != nil {
t.Fatal(err)
}
reader := &sourceInventory{values: []configkit.SourceInfo{
{
ID: "root", Label: "root source", Path: "root.yml",
PlacementMode: configkit.PlacementModeMapping, Layer: configkit.LayerMain,
EmptyImportSelections: []configkit.EmptyImportSelection{
{Path: configkit.Keys("imports", "first"), Location: configkit.Location{SourceID: "root.yml", Line: 3, Column: 4}, Selector: configkit.ImportSelectorGlob},
{Path: configkit.Keys("imports", "second"), Location: configkit.Location{}, Selector: configkit.ImportSelectorPath},
},
},
{
ID: "item", Label: "sequence item", Path: "item.yml",
Destination: servicePath, PlacementMode: configkit.PlacementModeSequenceItem,
DestinationIsFallback: true,
VersionEvidence: &configkit.SourceVersionEvidence{
Path: versionPath,
Version: configkit.Version{Present: true, Text: "v2"},
Location: &configkit.Location{SourceID: "item.yml", Line: 4, Column: 2},
},
},
{
ID: "null", Label: "null version", Path: "null.yml",
Destination: otherPath, PlacementMode: configkit.PlacementModeMapping,
VersionEvidence: &configkit.SourceVersionEvidence{
Path: nullVersionPath,
Version: configkit.Version{Present: true, Null: true},
Redacted: true,
},
},
}}
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{
{Name: "app", Operations: []Operation{Sources}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
return commandkit.Acquired[View]{Value: View{Sources: reader}}, nil
}},
}}))
var yamlOutput bytes.Buffer
outcome := app.Execute(context.Background(), []string{"config", "sources", "--format", "yaml"}, commandkit.Streams{Out: &yamlOutput, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || reader.calls != 1 {
t.Fatalf("YAML source report outcome=%#v calls=%d", outcome, reader.calls)
}
var report struct {
Scope string `yaml:"scope"`
Sources []struct {
ID string `yaml:"id"`
Destination string `yaml:"destination"`
Placement string `yaml:"placement"`
Layer string `yaml:"layer"`
EmptyImportSelections []emptyImportSelectionDTO `yaml:"empty_import_selections"`
DestinationIsFallback bool `yaml:"destination_is_fallback"`
VersionEvidence *struct {
Path string `yaml:"path"`
Version struct {
Present bool `yaml:"present"`
Null bool `yaml:"null"`
Text string `yaml:"text"`
} `yaml:"version"`
Location *struct {
SourceID string `yaml:"source_id"`
Line int `yaml:"line"`
Column int `yaml:"column"`
} `yaml:"location"`
Redacted bool `yaml:"redacted"`
} `yaml:"version_evidence"`
} `yaml:"sources"`
}
if err := yaml.Unmarshal(yamlOutput.Bytes(), &report); err != nil {
t.Fatalf("decode source report: %v\n%s", err, yamlOutput.String())
}
if report.Scope != "app" || len(report.Sources) != 3 {
t.Fatalf("source report scope/order = %q, %#v", report.Scope, report.Sources)
}
if got := []string{report.Sources[0].ID, report.Sources[1].ID, report.Sources[2].ID}; !reflect.DeepEqual(got, []string{"root", "item", "null"}) {
t.Fatalf("source order = %v", got)
}
if root := report.Sources[0]; root.Destination != "" || root.Placement != "mapping" || root.DestinationIsFallback || root.VersionEvidence != nil || root.Layer != "main" {
t.Errorf("root source projection = %#v", root)
}
wantSelections := []emptyImportSelectionDTO{
{Path: "/imports/first", Location: locationDTO{SourceID: "root.yml", Line: 3, Column: 4}, Selector: "glob"},
{Path: "/imports/second", Location: locationDTO{}, Selector: "path"},
}
if !reflect.DeepEqual(report.Sources[0].EmptyImportSelections, wantSelections) || report.Sources[1].EmptyImportSelections == nil || len(report.Sources[1].EmptyImportSelections) != 0 {
t.Fatalf("import selections: %#v", report.Sources)
}
if strings.Contains(yamlOutput.String(), "destination_unresolved:") || strings.Contains(yamlOutput.String(), "version_check:") {
t.Fatal("legacy YAML keys present")
}
item := report.Sources[1]
if item.Destination != "/services" || item.Placement != "sequence_item" || !item.DestinationIsFallback {
t.Errorf("sequence item projection = %#v", item)
}
if check := item.VersionEvidence; check == nil || check.Path != "/version" || !check.Version.Present || check.Version.Null || check.Version.Text != "v2" || check.Location == nil || check.Location.SourceID != "item.yml" || check.Location.Line != 4 || check.Location.Column != 2 || check.Redacted {
t.Errorf("text version evidence projection = %#v", check)
}
nullSource := report.Sources[2]
if nullSource.Destination != "/other" || nullSource.Placement != "mapping" || nullSource.DestinationIsFallback {
t.Errorf("mapping projection = %#v", nullSource)
}
if check := nullSource.VersionEvidence; check == nil || check.Path != "/metadata/version" || !check.Version.Present || !check.Version.Null || check.Version.Text != "" || check.Location != nil || !check.Redacted {
t.Errorf("null/redacted version evidence projection = %#v", check)
}
var textOutput bytes.Buffer
outcome = app.Execute(context.Background(), []string{"config", "sources", "--format", "text"}, commandkit.Streams{Out: &textOutput, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || reader.calls != 2 {
t.Fatalf("text source report outcome=%#v calls=%d", outcome, reader.calls)
}
if strings.Contains(textOutput.String(), "destination_unresolved:") || strings.Contains(textOutput.String(), "version_check:") {
t.Fatal("legacy text keys present")
}
if strings.Index(textOutput.String(), `path: "/imports/first"`) > strings.Index(textOutput.String(), `path: "/imports/second"`) {
t.Fatal("import selection order changed")
}
for _, fragment := range []string{
`id: "root"`, `destination: ""`, `placement: "mapping"`, `layer: "main"`, "destination_is_fallback: false", "version_evidence: null", `path: "/imports/first"`, `location: {source_id: "root.yml", line: 3, column: 4}`, `selector: "glob"`, `path: "/imports/second"`, `location: {source_id: "", line: 0, column: 0}`, `selector: "path"`, "empty_import_selections: []",
`destination: "/services"`, `placement: "sequence_item"`, "destination_is_fallback: true", `path: "/version"`, `text: "v2"`,
`destination: "/other"`, `path: "/metadata/version"`, "null: true", `text: ""`, "location: null", "redacted: true",
} {
if !strings.Contains(textOutput.String(), fragment) {
t.Errorf("text report missing %q:\n%s", fragment, textOutput.String())
}
}
}