368 lines
12 KiB
Go
368 lines
12 KiB
Go
package output
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/csv"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var errOutputInjected = errors.New("injected output failure")
|
|
|
|
type failNthWriter struct {
|
|
nth int
|
|
calls int
|
|
}
|
|
|
|
func (w *failNthWriter) Write(p []byte) (int, error) {
|
|
w.calls++
|
|
if w.calls >= w.nth {
|
|
return 0, errOutputInjected
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func TestCrossPlatformCoverageOutputJSONFailureInjection(t *testing.T) {
|
|
origMarshal := marshalJSON
|
|
origUnmarshal := unmarshalJSON
|
|
origOutput := marshalJSONOutput
|
|
origIndent := marshalJSONIndent
|
|
t.Cleanup(func() {
|
|
marshalJSON = origMarshal
|
|
unmarshalJSON = origUnmarshal
|
|
marshalJSONOutput = origOutput
|
|
marshalJSONIndent = origIndent
|
|
})
|
|
|
|
marshalJSON = func(any) ([]byte, error) { return nil, errOutputInjected }
|
|
if _, err := normalizePayload(struct{}{}); err == nil {
|
|
t.Fatal("normalizePayload should report marshal failure")
|
|
}
|
|
for name, call := range map[string]func() error{
|
|
"csv": func() error { return writeCSV(io.Discard, struct{}{}) },
|
|
"ndjson": func() error { return writeNDJSON(io.Discard, struct{}{}) },
|
|
"pretty": func() error { return writePretty(io.Discard, struct{}{}) },
|
|
"table": func() error { return writeTableish(io.Discard, struct{}{}) },
|
|
} {
|
|
if err := call(); err == nil {
|
|
t.Fatalf("%s should report normalization failure", name)
|
|
}
|
|
}
|
|
if _, err := roundTripJSON(struct{}{}); err == nil {
|
|
t.Fatal("roundTripJSON should report marshal failure")
|
|
}
|
|
if got := toGeneric(make(chan int)); got == nil {
|
|
t.Fatal("toGeneric should preserve an unmarshalable value")
|
|
}
|
|
if got := toGeneric(nil); got != nil {
|
|
t.Fatalf("toGeneric(nil) = %v", got)
|
|
}
|
|
|
|
marshalJSON = func(any) ([]byte, error) { return []byte("{"), nil }
|
|
if _, err := normalizePayload(struct{}{}); err == nil {
|
|
t.Fatal("normalizePayload should report unmarshal failure")
|
|
}
|
|
if _, err := roundTripJSON(struct{}{}); err == nil {
|
|
t.Fatal("roundTripJSON should report unmarshal failure")
|
|
}
|
|
if got := toGeneric("original"); got != "original" {
|
|
t.Fatalf("toGeneric fallback = %v", got)
|
|
}
|
|
|
|
marshalJSON = origMarshal
|
|
marshalJSONIndent = func(any, string, string) ([]byte, error) { return nil, errOutputInjected }
|
|
if err := WriteJSON(io.Discard, map[string]any{}); err == nil {
|
|
t.Fatal("WriteJSON should report marshal failure")
|
|
}
|
|
if err := ApplyJQ(io.Discard, map[string]any{"x": 1}, "."); err == nil {
|
|
t.Fatal("ApplyJQ should report marshal failure")
|
|
}
|
|
|
|
marshalJSONIndent = origIndent
|
|
marshalJSONOutput = func(any) ([]byte, error) { return nil, errOutputInjected }
|
|
if err := writeRaw(io.Discard, map[string]any{}); err == nil {
|
|
t.Fatal("writeRaw should report marshal failure")
|
|
}
|
|
if got := formatValue(make(chan int)); got == "" {
|
|
t.Fatalf("formatValue fallback = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageOutputFlagEdges(t *testing.T) {
|
|
if _, ok := formatFromFlagSet(nil, FormatRaw); ok {
|
|
t.Fatal("nil flag set should not resolve")
|
|
}
|
|
cmd := &cobra.Command{Use: "cmd"}
|
|
if _, ok := formatFromFlagSet(cmd.Flags(), FormatRaw); ok {
|
|
t.Fatal("missing flag should not resolve")
|
|
}
|
|
cmd.Flags().Int("format", 1, "wrong type")
|
|
if _, ok := formatFromFlagSet(cmd.Flags(), FormatRaw); ok {
|
|
t.Fatal("non-string format should not resolve")
|
|
}
|
|
|
|
for _, raw := range []string{"", "json", "raw", "table", "pretty", "ndjson", "csv", "unknown"} {
|
|
_ = normalizeFormat(raw, FormatRaw)
|
|
}
|
|
|
|
if rootPersistentFlags(nil) != nil {
|
|
t.Fatal("nil command should not have root flags")
|
|
}
|
|
if ResolveFields(nil) != "" || ResolveJQ(nil) != "" {
|
|
t.Fatal("nil command filters should be empty")
|
|
}
|
|
|
|
root := &cobra.Command{Use: "root"}
|
|
child := &cobra.Command{Use: "child"}
|
|
root.AddCommand(child)
|
|
if ResolveFields(child) != "" || ResolveJQ(child) != "" {
|
|
t.Fatal("missing global filters should be empty")
|
|
}
|
|
|
|
root.PersistentFlags().String("fields", "", "global fields")
|
|
root.PersistentFlags().String("jq", "", "global jq")
|
|
if err := root.PersistentFlags().Set("fields", "id,name"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := root.PersistentFlags().Set("jq", ".id"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := ResolveFields(child); got != "id,name" {
|
|
t.Fatalf("ResolveFields = %q", got)
|
|
}
|
|
if got := ResolveJQ(child); got != ".id" {
|
|
t.Fatalf("ResolveJQ = %q", got)
|
|
}
|
|
|
|
wrong := &cobra.Command{Use: "wrong"}
|
|
wrong.PersistentFlags().Int("fields", 1, "global fields")
|
|
wrong.PersistentFlags().Int("jq", 1, "global jq")
|
|
_ = wrong.PersistentFlags().Set("fields", "2")
|
|
_ = wrong.PersistentFlags().Set("jq", "2")
|
|
if ResolveFields(wrong) != "" || ResolveJQ(wrong) != "" {
|
|
t.Fatal("wrongly typed filters should be ignored")
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageOutputWriterFailures(t *testing.T) {
|
|
filtered := filterSlice([]any{"scalar", map[string]any{"keep": 1}}, map[string]bool{"keep": true})
|
|
if len(filtered) != 2 || filtered[0] != "scalar" {
|
|
t.Fatalf("filterSlice = %#v", filtered)
|
|
}
|
|
payload := map[string]any{
|
|
"items": []any{
|
|
map[string]any{"a": "one", "b": strings.Repeat("x", 80)},
|
|
map[string]any{"a": "two", "b": "three"},
|
|
},
|
|
"total": 2,
|
|
}
|
|
for nth := 1; nth <= 80; nth++ {
|
|
_ = writeTableish(&failNthWriter{nth: nth}, payload)
|
|
_ = writeTable(&failNthWriter{nth: nth}, []string{"a", "b"}, [][]string{{"1", "2"}, {"3", "4"}})
|
|
_ = writeKeyValues(&failNthWriter{nth: nth}, map[string]any{strings.Repeat("k", 30): "v", "z": 2})
|
|
}
|
|
if err := writeTable(io.Discard, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := writeTable(io.Discard, []string{"a"}, [][]string{{"1", "ignored"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, call := range []func(io.Writer) error{
|
|
func(w io.Writer) error { return WriteJSON(w, map[string]any{"x": 1}) },
|
|
func(w io.Writer) error { return writeRaw(w, "text") },
|
|
func(w io.Writer) error { return writeRaw(w, map[string]any{"x": 1}) },
|
|
func(w io.Writer) error { return writeTableish(w, payload) },
|
|
func(w io.Writer) error { return ApplyJQ(w, []any{1, 2}, ".[]") },
|
|
} {
|
|
for nth := 1; nth <= 8; nth++ {
|
|
_ = call(&failNthWriter{nth: nth})
|
|
}
|
|
}
|
|
if err := ApplyJQ(io.Discard, 1, ".foo"); err == nil {
|
|
t.Fatal("jq evaluation error expected")
|
|
}
|
|
if err := ApplyJQ(io.Discard, nil, "["); err == nil {
|
|
t.Fatal("jq parse error expected")
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageCSVFailureAndShapeEdges(t *testing.T) {
|
|
origWrite := writeCSVRecord
|
|
t.Cleanup(func() { writeCSVRecord = origWrite })
|
|
|
|
for failAt := 1; failAt <= 3; failAt++ {
|
|
calls := 0
|
|
writeCSVRecord = func(cw *csv.Writer, row []string) error {
|
|
calls++
|
|
if calls == failAt {
|
|
return errOutputInjected
|
|
}
|
|
return origWrite(cw, row)
|
|
}
|
|
_ = writeTableCSV(csv.NewWriter(io.Discard), []string{"h"}, [][]string{{"a"}, {"b"}})
|
|
calls = 0
|
|
_ = writeKeyValueCSV(csv.NewWriter(io.Discard), map[string]any{"a": 1, "b": 2})
|
|
calls = 0
|
|
_ = writeCSV(io.Discard, "scalar")
|
|
}
|
|
writeCSVRecord = origWrite
|
|
|
|
_ = writeCSV(&failNthWriter{nth: 1}, nil)
|
|
if err := writeCSV(&failNthWriter{nth: 1}, "scalar"); err == nil {
|
|
t.Fatal("scalar CSV flush failure expected")
|
|
}
|
|
if err := writeTableCSV(csv.NewWriter(&failNthWriter{nth: 1}), []string{"h"}, [][]string{{"v"}}); err == nil {
|
|
t.Fatal("table CSV flush failure expected")
|
|
}
|
|
if err := writeKeyValueCSV(csv.NewWriter(&failNthWriter{nth: 1}), map[string]any{"a": 1}); err == nil {
|
|
t.Fatal("key/value CSV flush failure expected")
|
|
}
|
|
|
|
headers, rows := broadcastMeta([]string{"id"}, [][]string{{"1"}}, nil)
|
|
if len(headers) != 1 || len(rows) != 1 {
|
|
t.Fatal("empty metadata changed rows")
|
|
}
|
|
headers, rows = broadcastMeta([]string{"id"}, [][]string{{"1"}}, map[string]any{"id": "collision"})
|
|
if len(headers) != 1 || len(rows) != 1 {
|
|
t.Fatal("colliding metadata should be ignored")
|
|
}
|
|
_, rows = broadcastMeta([]string{"id"}, nil, map[string]any{"total": 0})
|
|
if len(rows) != 1 {
|
|
t.Fatal("empty rows should retain metadata")
|
|
}
|
|
if err := writeCSV(io.Discard, map[string]any{"data": map[string]any{"x": 1}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := writeTableCSV(csv.NewWriter(io.Discard), []string{"a", "b"}, [][]string{{"one"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageNDJSONFailureEdges(t *testing.T) {
|
|
origEncode := encodeNDJSON
|
|
t.Cleanup(func() { encodeNDJSON = origEncode })
|
|
encodeNDJSON = func(*json.Encoder, any) error { return errOutputInjected }
|
|
for _, payload := range []any{
|
|
[]any{1},
|
|
map[string]any{"items": []any{map[string]any{"x": 1}}},
|
|
map[string]any{"x": 1},
|
|
"scalar",
|
|
} {
|
|
if err := writeNDJSON(io.Discard, payload); err == nil {
|
|
t.Fatalf("encode failure expected for %T", payload)
|
|
}
|
|
}
|
|
encodeNDJSON = origEncode
|
|
if err := writeNDJSON(&failNthWriter{nth: 1}, map[string]any{"x": 1}); err == nil {
|
|
t.Fatal("flush failure expected")
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoveragePrettyAndFormattingEdges(t *testing.T) {
|
|
forceNoColor(t)
|
|
for _, payload := range []map[string]any{
|
|
{"kind": "schema", "degraded": true, "reason": "offline", "hint": "retry"},
|
|
{"kind": "schema", "degraded": true, "reason": "offline"},
|
|
{"kind": "schema", "other": true},
|
|
{"kind": "schema", "products": []any{"invalid", map[string]any{
|
|
"id": "p", "name": "same", "description": "same", "tools": []any{
|
|
"invalid",
|
|
map[string]any{"name": "same", "cli_name": "same"},
|
|
map[string]any{"name": "rpc", "cli_name": "cli"},
|
|
map[string]any{"name": "3"}, map[string]any{"name": "4"}, map[string]any{"name": "5"},
|
|
map[string]any{"name": "6"}, map[string]any{"name": "7"}, map[string]any{"name": "8"},
|
|
}},
|
|
}},
|
|
} {
|
|
if err := writePretty(io.Discard, payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
toolPayload := map[string]any{
|
|
"kind": "schema",
|
|
"product": "invalid",
|
|
"tool": map[string]any{
|
|
"name": "rpc",
|
|
"cli_name": "rpc",
|
|
"title": "rpc",
|
|
"description": "rpc",
|
|
"parameters": map[string]any{
|
|
"any": "invalid",
|
|
"array": map[string]any{"type": "array", "items": map[string]any{}},
|
|
"enum": map[string]any{"enum": []any{"a", 2}, "description": "line1\nline2"},
|
|
"required": map[string]any{"type": "string"},
|
|
},
|
|
"required": []any{"required", 3},
|
|
"flag_overlay": map[string]any{
|
|
"any": "invalid",
|
|
"enum": map[string]any{
|
|
"alias": "enum-alias",
|
|
"transform": "map",
|
|
"transform_args": map[string]any{"z": 2},
|
|
"env_default": "ENUM_ENV",
|
|
"default": "a",
|
|
},
|
|
},
|
|
"output_schema": map[string]any{"type": "object"},
|
|
},
|
|
}
|
|
if err := writePretty(io.Discard, toolPayload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, value := range []any{nil, "text", map[string]any{"x": 1}} {
|
|
_ = formatValue(value)
|
|
}
|
|
for _, tc := range []struct {
|
|
s string
|
|
w int
|
|
}{{"abc", 0}, {"abc", 1}, {"abcdef", 3}, {"你好世界", 4}} {
|
|
_ = truncate(tc.s, tc.w)
|
|
}
|
|
for _, prop := range []map[string]any{
|
|
{"type": "array", "items": map[string]any{"type": "string"}},
|
|
{"type": "array"},
|
|
{},
|
|
{"type": "number"},
|
|
} {
|
|
_ = describeType(prop)
|
|
}
|
|
if got, err := normalizePayload("text"); err != nil || got != "text" {
|
|
t.Fatalf("normalize string = %v, %v", got, err)
|
|
}
|
|
if got, err := normalizePayload(nil); err != nil || got != nil {
|
|
t.Fatalf("normalize nil = %v, %v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageExtractRowsNestedMetadataCollision(t *testing.T) {
|
|
payload := map[string]any{
|
|
"result": map[string]any{
|
|
"items": []any{map[string]any{"id": 1}},
|
|
"same": "inner",
|
|
"inner": true,
|
|
},
|
|
"same": "outer",
|
|
"outer": true,
|
|
}
|
|
_, _, meta, ok := extractRowsFromMap(payload)
|
|
if !ok || meta["same"] != "outer" || meta["inner"] != true || meta["outer"] != true {
|
|
t.Fatalf("unexpected metadata: %#v", meta)
|
|
}
|
|
if _, _, _, ok := extractRowsFromMap(map[string]any{"x": 1}); ok {
|
|
t.Fatal("non-list map should not extract rows")
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := Write(&buf, FormatPretty, map[string]any{"x": 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|