Add shared report rendering
This commit is contained in:
188
report_test.go
Normal file
188
report_test.go
Normal file
@@ -0,0 +1,188 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user