189 lines
5.4 KiB
Go
189 lines
5.4 KiB
Go
|
|
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)
|
||
|
|
}
|
||
|
|
}
|