Files
commandkit/report_test.go

189 lines
5.4 KiB
Go
Raw Permalink Normal View History

2026-11-07 03:40:09 +00:00
package commandkit
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"strings"
"testing"
"text/template"
)
func TestRenderReportAutomaticShapes(t *testing.T) {
for _, tc := range []struct {
name string
value any
text string
yaml bool
}{
{"string", "hello", "hello\n", false},
{"number", json.Number("123456789012345678901234567890"), "123456789012345678901234567890\n", false},
{"boolean", false, "false\n", false},
{"strings", []string{"one", "two"}, "one\ntwo\n", false},
{"empty strings", []string{}, "", true},
{"empty map", map[string]any{}, "", true},
{"null", nil, "", true},
{"mixed", []any{"a", false}, "", true},
{"map", map[string]any{"n": json.Number("9007199254740993")}, "", true},
} {
t.Run(tc.name, func(t *testing.T) {
var out bytes.Buffer
if err := RenderReport(&out, Report{Value: tc.value}, ""); err != nil {
t.Fatal(err)
}
if !tc.yaml {
if out.String() != tc.text {
t.Fatalf("got %q want %q", out.String(), tc.text)
}
return
}
got, err := DecodeYAMLReport(&out)
if err != nil {
t.Fatal(err)
}
want, err := normalizeReport(tc.value)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got %#v want %#v", got, want)
}
})
}
}
func TestRenderReportTemplatesAndFormats(t *testing.T) {
tmpl, err := ParseTextTemplate("report", `{{upper .Secret}}/{{.Name}}`, template.FuncMap{"upper": strings.ToUpper})
if err != nil {
t.Fatal(err)
}
value := struct {
Name string `json:"name"`
Secret string `json:"-"`
}{"public", "private"}
var out bytes.Buffer
if err := RenderReport(&out, Report{value, tmpl}, ""); err != nil {
t.Fatal(err)
}
if out.String() != "PRIVATE/public" {
t.Fatal(out.String())
}
for _, format := range []Format{FormatYAML, FormatJSON} {
out.Reset()
if err := RenderReport(&out, Report{value, tmpl}, format); err != nil {
t.Fatal(err)
}
var got any
if format == FormatYAML {
got, err = DecodeYAMLReport(&out)
} else {
d := json.NewDecoder(&out)
d.UseNumber()
err = d.Decode(&got)
}
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, map[string]any{"name": "public"}) {
t.Fatalf("got %#v", got)
}
}
out.Reset()
if err := RenderReport(&out, Report{Value: map[string]any{"n": json.Number("123456789012345678901234567890")}}, FormatText); err != nil {
t.Fatal(err)
}
got, err := DecodeYAMLReport(&out)
if err != nil || !reflect.DeepEqual(got, map[string]any{"n": json.Number("123456789012345678901234567890")}) {
t.Fatalf("got %#v: %v", got, err)
}
}
type reportShortWriter struct{}
func (reportShortWriter) Write(p []byte) (int, error) { return len(p) / 2, nil }
type reportErrorWriter struct{ err error }
func (w reportErrorWriter) Write([]byte) (int, error) { return 0, w.err }
func TestRenderReportErrors(t *testing.T) {
if _, err := ParseTextTemplate("bad", `{{`, nil); err == nil {
t.Fatal("invalid template accepted")
}
tmpl, err := ParseTextTemplate("bad-execution", `{{.Missing}}`, nil)
if err != nil {
t.Fatal(err)
}
if err := RenderReport(io.Discard, Report{Value: struct{}{}, TextTemplate: tmpl}, ""); err == nil {
t.Fatal("template execution succeeded")
}
if err := RenderReport(io.Discard, Report{Value: make(chan int)}, FormatJSON); err == nil {
t.Fatal("unsupported value accepted")
}
if err := RenderReport(io.Discard, Report{}, Format("bad")); err == nil {
t.Fatal("unknown format accepted")
}
tmpl, err = ParseTextTemplate("skip-json", `{{if .}}ok{{end}}`, nil)
if err != nil {
t.Fatal(err)
}
var out bytes.Buffer
if err := RenderReport(&out, Report{Value: make(chan int), TextTemplate: tmpl}, FormatText); err != nil || out.String() != "ok" {
t.Fatalf("templated unsupported value: %q, %v", &out, err)
}
failure := errors.New("writer failure")
for _, format := range []Format{FormatText, FormatYAML, FormatJSON} {
t.Run(string(format), func(t *testing.T) {
for _, writer := range []struct {
w io.Writer
want error
}{{reportShortWriter{}, io.ErrShortWrite}, {reportErrorWriter{failure}, failure}} {
err := RenderReport(writer.w, Report{Value: "content"}, format)
if !errors.Is(err, writer.want) {
t.Fatalf("%s: got %v, want %v", fmt.Sprint(writer.w), err, writer.want)
}
}
})
}
}
func TestRenderReportExplicitFormatsSkipTemplateAndPreserveNumbers(t *testing.T) {
tmpl, err := ParseTextTemplate("invalid-at-execution", `{{.Missing}}`, nil)
if err != nil {
t.Fatal(err)
}
value := struct {
Number json.Number `json:"number"`
}{json.Number("123456789012345678901234567890")}
want := map[string]any{"number": json.Number("123456789012345678901234567890")}
for _, format := range []Format{FormatYAML, FormatJSON} {
t.Run(string(format), func(t *testing.T) {
var out bytes.Buffer
if err := RenderReport(&out, Report{Value: value, TextTemplate: tmpl}, format); err != nil {
t.Fatal(err)
}
var got any
if format == FormatYAML {
got, err = DecodeYAMLReport(&out)
} else {
dec := json.NewDecoder(&out)
dec.UseNumber()
err = dec.Decode(&got)
}
if err != nil || !reflect.DeepEqual(got, want) {
t.Fatalf("got %#v, %v", got, err)
}
})
}
plain, err := ParseTextTemplate("plain", "content", nil)
if err != nil {
t.Fatal(err)
}
if err := RenderReport(reportShortWriter{}, Report{TextTemplate: plain}, FormatText); !errors.Is(err, io.ErrShortWrite) {
t.Fatalf("got %v", err)
}
}