Files
larksuite-cli/internal/output/csv_test.go
梁硕 83dfb068ad feat: open-source lark-cli — the official CLI for Lark/Feishu
Change-Id: I113d9cdb5403cec347efe4595415e34a18b7decf
2026-03-28 10:36:25 +08:00

153 lines
3.6 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package output
import (
"bytes"
"strings"
"testing"
)
func TestFormatAsCSV_BasicArray(t *testing.T) {
data := []interface{}{
map[string]interface{}{"name": "Alice", "age": float64(30)},
map[string]interface{}{"name": "Bob", "age": float64(25)},
}
var buf bytes.Buffer
FormatAsCSV(&buf, data)
out := buf.String()
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
if len(lines) != 3 {
t.Fatalf("expected 3 lines (header + 2 rows), got %d:\n%s", len(lines), out)
}
// Header should contain both column names
header := lines[0]
if !strings.Contains(header, "name") || !strings.Contains(header, "age") {
t.Errorf("header should contain 'name' and 'age', got: %s", header)
}
}
func TestFormatAsCSV_RFC4180Escaping(t *testing.T) {
data := []interface{}{
map[string]interface{}{
"text": `hello, "world"`,
},
}
var buf bytes.Buffer
FormatAsCSV(&buf, data)
out := buf.String()
// RFC 4180: fields with commas/quotes are quoted, internal quotes are doubled
if !strings.Contains(out, `"hello, ""world"""`) {
t.Errorf("CSV should properly escape commas and quotes, got:\n%s", out)
}
}
func TestFormatAsCSV_NewlineInValue(t *testing.T) {
data := []interface{}{
map[string]interface{}{
"text": "line1\nline2",
},
}
var buf bytes.Buffer
FormatAsCSV(&buf, data)
out := buf.String()
// RFC 4180: fields with newlines should be quoted
if !strings.Contains(out, `"line1`) {
t.Errorf("CSV should quote fields containing newlines, got:\n%s", out)
}
}
func TestFormatAsCSV_NestedObject(t *testing.T) {
data := []interface{}{
map[string]interface{}{
"user": map[string]interface{}{
"name": "Alice",
},
"id": float64(1),
},
}
var buf bytes.Buffer
FormatAsCSV(&buf, data)
out := buf.String()
if !strings.Contains(out, "user.name") {
t.Errorf("CSV should contain flattened 'user.name' column, got:\n%s", out)
}
}
func TestFormatAsCSV_EmptyArray(t *testing.T) {
data := []interface{}{}
var buf bytes.Buffer
FormatAsCSV(&buf, data)
out := strings.TrimSpace(buf.String())
if out != "(empty)" {
t.Errorf("empty array should output '(empty)', got:\n%s", out)
}
}
func TestFormatAsCSVPaginated_FirstPage(t *testing.T) {
data := []interface{}{
map[string]interface{}{"name": "Alice"},
}
var buf bytes.Buffer
FormatAsCSVPaginated(&buf, data, true)
out := buf.String()
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
if len(lines) != 2 {
t.Errorf("first page should have header + 1 data row, got %d lines:\n%s", len(lines), out)
}
if lines[0] != "name" {
t.Errorf("first line should be header 'name', got: %s", lines[0])
}
}
func TestFormatAsCSVPaginated_ContinuationPage(t *testing.T) {
data := []interface{}{
map[string]interface{}{"name": "Bob"},
}
var buf bytes.Buffer
FormatAsCSVPaginated(&buf, data, false)
out := buf.String()
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
if len(lines) != 1 {
t.Errorf("continuation page should have 1 data row, got %d lines:\n%s", len(lines), out)
}
if lines[0] != "Bob" {
t.Errorf("continuation page data should be 'Bob', got: %s", lines[0])
}
}
func TestFormatAsCSV_SingleObject(t *testing.T) {
data := map[string]interface{}{
"name": "Alice",
"age": float64(30),
}
var buf bytes.Buffer
FormatAsCSV(&buf, data)
out := buf.String()
// Single object should render as key,value format
if !strings.Contains(out, "key,value") {
t.Errorf("single object should have key,value header, got:\n%s", out)
}
if !strings.Contains(out, "Alice") {
t.Errorf("output should contain 'Alice', got:\n%s", out)
}
}