Files
unkin-agent a76480d454
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
unwrap the catalog response and classify failures correctly
- unwrap the {"catalog": ...} envelope, still accepting a bare catalog
- report an environment-less response as a protocol mismatch, not an ENC problem
- make Resource.Line a pointer and Resource.Params() map absent onto empty
- render a missing manifest location as "(source unknown)"
- retry compile failures caused by the compiler's PuppetDB query
- stop retrying manifest errors the compiler returns as HTTP 500
- add a trimmed real-server response fixture and tests for each shape
2026-09-12 16:58:59 +10:00

175 lines
5.4 KiB
Go

package catalog
import (
"bytes"
"flag"
"os"
"path/filepath"
"strings"
"testing"
)
var update = flag.Bool("update", false, "rewrite the golden files")
func golden(t *testing.T, name string, got []byte) {
t.Helper()
path := filepath.Join("testdata", name)
if *update {
if err := os.WriteFile(path, got, 0o644); err != nil {
t.Fatalf("writing golden %s: %v", path, err)
}
return
}
want, err := os.ReadFile(path)
if err != nil {
t.Fatalf("reading golden %s: %v (run go test -update)", path, err)
}
if !bytes.Equal(want, got) {
t.Errorf("output does not match %s\n--- want ---\n%s\n--- got ---\n%s", path, want, got)
}
}
func testReport(t *testing.T) Report {
t.Helper()
return NewReport("host.example.net", "develop", "benvin_test", changeSet(t))
}
func TestRenderTextGolden(t *testing.T) {
var buf bytes.Buffer
if err := RenderText(&buf, testReport(t), RenderOptions{SourceFileLine: true}); err != nil {
t.Fatalf("RenderText: %v", err)
}
golden(t, "report.txt", buf.Bytes())
}
func TestRenderJSONGolden(t *testing.T) {
var buf bytes.Buffer
if err := RenderJSON(&buf, testReport(t)); err != nil {
t.Fatalf("RenderJSON: %v", err)
}
golden(t, "report.json", buf.Bytes())
}
func TestRenderTextNoChanges(t *testing.T) {
var buf bytes.Buffer
if err := RenderText(&buf, NewReport("host.example.net", "develop", "benvin_test", nil), RenderOptions{}); err != nil {
t.Fatalf("RenderText: %v", err)
}
if !strings.Contains(buf.String(), "No differences between environments.") {
t.Errorf("unexpected empty-diff output:\n%s", buf.String())
}
}
func TestRenderTextColourOnlyWhenAsked(t *testing.T) {
var plain, coloured bytes.Buffer
report := testReport(t)
if err := RenderText(&plain, report, RenderOptions{}); err != nil {
t.Fatal(err)
}
if err := RenderText(&coloured, report, RenderOptions{Colour: true}); err != nil {
t.Fatal(err)
}
if strings.Contains(plain.String(), "\033[") {
t.Error("plain output contains ANSI escapes")
}
if !strings.Contains(coloured.String(), "\033[") {
t.Error("coloured output contains no ANSI escapes")
}
}
func TestRenderTextGroupsCriticalFirst(t *testing.T) {
var buf bytes.Buffer
if err := RenderText(&buf, testReport(t), RenderOptions{SourceFileLine: true}); err != nil {
t.Fatal(err)
}
out := buf.String()
crit, other := strings.Index(out, "CRITICAL"), strings.Index(out, "OTHER")
if crit < 0 || other < 0 || crit > other {
t.Errorf("critical changes not listed first:\n%s", out)
}
}
func TestRenderJSONEmptyChangesIsArray(t *testing.T) {
var buf bytes.Buffer
if err := RenderJSON(&buf, NewReport("host.example.net", "develop", "benvin_test", nil)); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), `"changes": []`) {
t.Errorf("empty change list must render as [], got:\n%s", buf.String())
}
}
func TestRenderTextCollapsesAddedResources(t *testing.T) {
report := testReport(t)
var collapsed bytes.Buffer
if err := RenderText(&collapsed, report, RenderOptions{}); err != nil {
t.Fatal(err)
}
if strings.Contains(collapsed.String(), "message: hi") {
t.Errorf("added resource expanded without --display-detail-add:\n%s", collapsed.String())
}
var detailed bytes.Buffer
if err := RenderText(&detailed, report, RenderOptions{DetailAdd: true}); err != nil {
t.Fatal(err)
}
if !strings.Contains(detailed.String(), "message: hi") {
t.Errorf("--display-detail-add did not expand the added resource:\n%s", detailed.String())
}
}
func TestRenderTextSourceFileLineIsOptional(t *testing.T) {
report := testReport(t)
var plain bytes.Buffer
if err := RenderText(&plain, report, RenderOptions{}); err != nil {
t.Fatal(err)
}
if strings.Contains(plain.String(), "site.pp") {
t.Errorf("file:line printed without --display-source-file-line:\n%s", plain.String())
}
var located bytes.Buffer
if err := RenderText(&located, report, RenderOptions{SourceFileLine: true}); err != nil {
t.Fatal(err)
}
if !strings.Contains(located.String(), "site.pp:19") {
t.Errorf("--display-source-file-line did not print the source:\n%s", located.String())
}
}
func TestRenderTextMarksMissingSourceUnknown(t *testing.T) {
changes := []Change{
{DiffType: DiffAdded, Type: "Class", Title: "Settings", Severity: SeverityNormal},
{DiffType: DiffRemoved, Type: "File", Title: "/etc/motd", OldFile: "site.pp", OldLine: intPtr(9), Severity: SeverityCritical},
}
var buf bytes.Buffer
if err := RenderText(&buf, NewReport("host.example.net", "develop", "benvin_test", changes), RenderOptions{SourceFileLine: true}); err != nil {
t.Fatal(err)
}
out := buf.String()
if !strings.Contains(out, "Class[Settings] added (source unknown)") {
t.Errorf("a resource with no manifest location must render as unknown:\n%s", out)
}
if strings.Contains(out, ":0)") {
t.Errorf("an absent line must never render as line 0:\n%s", out)
}
if !strings.Contains(out, "(site.pp:9)") {
t.Errorf("a real location must still render:\n%s", out)
}
}
func TestRenderJSONOmitsAbsentSource(t *testing.T) {
changes := []Change{{DiffType: DiffAdded, Type: "Class", Title: "Settings", Severity: SeverityNormal}}
var buf bytes.Buffer
if err := RenderJSON(&buf, NewReport("host.example.net", "develop", "benvin_test", changes)); err != nil {
t.Fatal(err)
}
for _, key := range []string{"new_file", "new_line", "old_file", "old_line"} {
if strings.Contains(buf.String(), key) {
t.Errorf("%s must be omitted when the compiler gave no location:\n%s", key, buf.String())
}
}
}