forked from Gitlink/gitlink-cli
263 lines
7.8 KiB
Go
263 lines
7.8 KiB
Go
package output
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPrintTable_NestedSliceRendersAsTable(t *testing.T) {
|
|
data := map[string]interface{}{
|
|
"tags": []interface{}{
|
|
map[string]interface{}{"name": "v1", "id": "a"},
|
|
map[string]interface{}{"name": "v2", "id": "b"},
|
|
},
|
|
"total_count": 2,
|
|
}
|
|
env := SuccessEnvelope(data, nil)
|
|
var buf bytes.Buffer
|
|
if err := PrintTo(&buf, env, "table"); err != nil {
|
|
t.Fatalf("PrintTo returned error: %v", err)
|
|
}
|
|
out := buf.String()
|
|
for _, want := range []string{"name", "v1", "v2", "id"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("expected output to contain %q, got:\n%s", want, out)
|
|
}
|
|
}
|
|
// Must NOT start with JSON envelope.
|
|
if strings.HasPrefix(strings.TrimSpace(out), "{") {
|
|
t.Errorf("expected table output, not JSON; got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestPrintTable_NestedMapFallsBackToJSON(t *testing.T) {
|
|
data := map[string]interface{}{
|
|
"outer": map[string]interface{}{"inner": "value"},
|
|
}
|
|
env := SuccessEnvelope(data, nil)
|
|
var buf bytes.Buffer
|
|
if err := PrintTo(&buf, env, "table"); err != nil {
|
|
t.Fatalf("PrintTo returned error: %v", err)
|
|
}
|
|
out := buf.String()
|
|
if !strings.Contains(out, `"ok"`) {
|
|
t.Errorf("expected JSON fallback containing \"ok\"; got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestPrintTable_SimpleMapRendersAsMapTable(t *testing.T) {
|
|
data := map[string]interface{}{
|
|
"name": "x",
|
|
"count": 5,
|
|
}
|
|
env := SuccessEnvelope(data, nil)
|
|
var buf bytes.Buffer
|
|
if err := PrintTo(&buf, env, "table"); err != nil {
|
|
t.Fatalf("PrintTo returned error: %v", err)
|
|
}
|
|
out := buf.String()
|
|
if !strings.Contains(out, "KEY") || !strings.Contains(out, "VALUE") {
|
|
t.Errorf("expected KEY/VALUE map table; got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestPrintTable_ErrorEnvelope(t *testing.T) {
|
|
env := ErrorEnvelope("E_FAIL", "boom", "")
|
|
var buf bytes.Buffer
|
|
if err := PrintTo(&buf, env, "table"); err != nil {
|
|
t.Fatalf("PrintTo returned error: %v", err)
|
|
}
|
|
out := buf.String()
|
|
if !strings.Contains(out, "Error:") {
|
|
t.Errorf("expected output to contain \"Error:\"; got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "boom") {
|
|
t.Errorf("expected output to contain error message \"boom\"; got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestPrintTable_PicksLargestSlice(t *testing.T) {
|
|
data := map[string]interface{}{
|
|
"small": []interface{}{
|
|
map[string]interface{}{"a": 1},
|
|
},
|
|
"big": []interface{}{
|
|
map[string]interface{}{"x": 1},
|
|
map[string]interface{}{"x": 2},
|
|
map[string]interface{}{"x": 3},
|
|
},
|
|
}
|
|
env := SuccessEnvelope(data, nil)
|
|
var buf bytes.Buffer
|
|
if err := PrintTo(&buf, env, "table"); err != nil {
|
|
t.Fatalf("PrintTo returned error: %v", err)
|
|
}
|
|
out := buf.String()
|
|
// Must not be a JSON envelope fallback.
|
|
if strings.HasPrefix(strings.TrimSpace(out), "{") {
|
|
t.Errorf("expected table output, not JSON; got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "x") {
|
|
t.Errorf("expected output to render \"big\" list with x column; got:\n%s", out)
|
|
}
|
|
for _, want := range []string{"1", "2", "3"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("expected output to contain row value %q; got:\n%s", want, out)
|
|
}
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(out), "\n")
|
|
// header + dash line + 3 rows = 5 lines minimum
|
|
if len(lines) < 5 {
|
|
t.Errorf("expected at least 5 lines (header/dash/3 rows); got %d:\n%s", len(lines), out)
|
|
}
|
|
// The "a" column from the smaller slice must not appear as a header.
|
|
for _, line := range lines {
|
|
// header line is tab-separated; ensure "a" is not one of the columns.
|
|
cols := strings.Split(line, "\t")
|
|
for _, c := range cols {
|
|
if strings.TrimSpace(c) == "a" {
|
|
t.Errorf("did not expect \"a\" column from smaller slice; got line: %q", line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Readability improvements (truncation, column limits, nested-map summaries) ---
|
|
|
|
func TestFormatValue_TruncatesLongString(t *testing.T) {
|
|
long := strings.Repeat("a", 100)
|
|
got := formatValue(long)
|
|
if len(got) > 40 {
|
|
t.Errorf("expected output <= 40 chars, got %d: %q", len(got), got)
|
|
}
|
|
if !strings.HasSuffix(got, "...") {
|
|
t.Errorf("expected output to end with \"...\", got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatValue_ShortStringUntouched(t *testing.T) {
|
|
got := formatValue("hello")
|
|
if got != "hello" {
|
|
t.Errorf("expected short string unchanged, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatValue_NestedMapShowsSummary(t *testing.T) {
|
|
m := map[string]interface{}{
|
|
"login": "alice",
|
|
"id": 123,
|
|
"image_url": "http://example.com/avatar.png",
|
|
}
|
|
got := formatValue(m)
|
|
if !strings.Contains(got, "alice") {
|
|
t.Errorf("expected summary to contain \"alice\", got %q", got)
|
|
}
|
|
if strings.Contains(got, "image_url") {
|
|
t.Errorf("expected summary to omit noisy field \"image_url\", got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatValue_NestedMapWithoutKnownFields(t *testing.T) {
|
|
m := map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": 42,
|
|
}
|
|
got := formatValue(m)
|
|
// No recognized key -> should be a short placeholder, not a JSON blob.
|
|
if strings.HasPrefix(got, "{") {
|
|
t.Errorf("expected placeholder for object without known fields, got JSON: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatValue_FloatNoScientificNotation(t *testing.T) {
|
|
got := formatValue(1.77920097e+09)
|
|
if strings.Contains(got, "e+") || strings.Contains(got, "E+") {
|
|
t.Errorf("expected float without scientific notation, got %q", got)
|
|
}
|
|
if !strings.Contains(got, "1779200970") {
|
|
t.Errorf("expected full decimal expansion, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestPrintSliceTable_LimitsColumns(t *testing.T) {
|
|
// Build a single row with 12 columns.
|
|
row := map[string]interface{}{}
|
|
cols := []string{"id", "name", "login", "title", "subject", "number",
|
|
"status", "state", "extra1", "extra2", "extra3", "extra4"}
|
|
for _, c := range cols {
|
|
row[c] = "v_" + c
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := printSliceTable(&buf, []interface{}{row}); err != nil {
|
|
t.Fatalf("printSliceTable returned error: %v", err)
|
|
}
|
|
out := buf.String()
|
|
|
|
// The header line is the first non-empty line.
|
|
lines := strings.Split(out, "\n")
|
|
var header string
|
|
for _, l := range lines {
|
|
if strings.TrimSpace(l) != "" {
|
|
header = l
|
|
break
|
|
}
|
|
}
|
|
colCount := len(strings.Split(header, "\t"))
|
|
if colCount > 8 {
|
|
t.Errorf("expected <= 8 columns in header, got %d:\n%s", colCount, header)
|
|
}
|
|
// And the output should note that columns were omitted.
|
|
if !strings.Contains(out, "省略") && !strings.Contains(strings.ToLower(out), "omitted") {
|
|
t.Errorf("expected output to mention omitted columns (省略/omitted), got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestPrintSliceTable_PreservesPriorityHeaders(t *testing.T) {
|
|
// Even with >8 columns, priority fields like id/name should survive.
|
|
row := map[string]interface{}{}
|
|
cols := []string{"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "id", "name"}
|
|
for _, c := range cols {
|
|
row[c] = "v"
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := printSliceTable(&buf, []interface{}{row}); err != nil {
|
|
t.Fatalf("printSliceTable returned error: %v", err)
|
|
}
|
|
out := buf.String()
|
|
if !strings.Contains(out, "id") || !strings.Contains(out, "name") {
|
|
t.Errorf("expected priority headers id/name to survive; got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestTruncateString(t *testing.T) {
|
|
if got := truncateString("abc", 10); got != "abc" {
|
|
t.Errorf("expected short string unchanged, got %q", got)
|
|
}
|
|
got := truncateString("abcdefghij", 5)
|
|
if len(got) > 5 {
|
|
t.Errorf("expected len <= 5, got %d: %q", len(got), got)
|
|
}
|
|
if !strings.HasSuffix(got, "...") {
|
|
t.Errorf("expected ... suffix, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatNestedMap_LoginAndSha(t *testing.T) {
|
|
m := map[string]interface{}{
|
|
"login": "bob",
|
|
"sha": "0123456789abcdef0123456789abcdef",
|
|
}
|
|
got := formatNestedMap(m)
|
|
if !strings.Contains(got, "bob") {
|
|
t.Errorf("expected login in summary, got %q", got)
|
|
}
|
|
if !strings.Contains(got, "0123456") {
|
|
t.Errorf("expected truncated sha prefix in summary, got %q", got)
|
|
}
|
|
// Should not contain the full sha.
|
|
if strings.Contains(got, "0123456789abcdef0123456789abcdef") {
|
|
t.Errorf("expected sha to be truncated, got %q", got)
|
|
}
|
|
}
|