gogcli/internal/cmd/docs_sed_helpers_test.go

313 lines
8.4 KiB
Go

package cmd
import (
"bytes"
"context"
"errors"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/api/docs/v1"
gapi "google.golang.org/api/googleapi"
"github.com/steipete/gogcli/internal/ui"
)
func TestIsMergeOp(t *testing.T) {
tests := []struct {
input string
want bool
}{
{"merge", true},
{"MERGE", true},
{" Merge ", true},
{"unmerge", true},
{"split", true},
{"replace", false},
{"", false},
{"merged", false},
}
for _, tt := range tests {
assert.Equal(t, tt.want, isMergeOp(tt.input), "isMergeOp(%q)", tt.input)
}
}
func TestCanBatchCell(t *testing.T) {
// Simple whole-cell replacement — can batch
ie := indexedExpr{
index: 0,
expr: sedExpr{
replacement: "hello",
cellRef: &tableCellRef{tableIndex: 1, row: 1, col: 1},
},
}
assert.True(t, canBatchCell(ie))
// No cellRef — cannot batch
ie2 := indexedExpr{expr: sedExpr{replacement: "hello"}}
assert.False(t, canBatchCell(ie2))
// Has pattern — cannot batch
ie3 := indexedExpr{
expr: sedExpr{
pattern: "foo",
replacement: "bar",
cellRef: &tableCellRef{tableIndex: 1, row: 1, col: 1},
},
}
assert.False(t, canBatchCell(ie3))
// Merge op — cannot batch
ie4 := indexedExpr{
expr: sedExpr{
replacement: "merge",
cellRef: &tableCellRef{tableIndex: 1, row: 1, col: 1},
},
}
assert.False(t, canBatchCell(ie4))
// Row op — cannot batch
ie5 := indexedExpr{
expr: sedExpr{
replacement: "hello",
cellRef: &tableCellRef{tableIndex: 1, row: 1, col: 1, rowOp: "append"},
},
}
assert.False(t, canBatchCell(ie5))
// Row=0 — cannot batch
ie6 := indexedExpr{
expr: sedExpr{
replacement: "hello",
cellRef: &tableCellRef{tableIndex: 1, row: 0, col: 1},
},
}
assert.False(t, canBatchCell(ie6))
}
func TestTruncateSed(t *testing.T) {
assert.Equal(t, "hello", truncateSed("hello", 10))
assert.Equal(t, "hello", truncateSed("hello", 5))
assert.Equal(t, "he...", truncateSed("hello world", 5))
assert.Equal(t, "hel...", truncateSed("hello world", 6))
}
func TestFormatBraceFlags(t *testing.T) {
assert.Equal(t, "", formatBraceFlags(nil))
b := true
f := false
be := &braceExpr{Bold: &b}
result := formatBraceFlags(be)
assert.Contains(t, result, "b")
be2 := &braceExpr{Bold: &f}
result2 := formatBraceFlags(be2)
assert.Contains(t, result2, "!b")
be3 := &braceExpr{Reset: true, Italic: &b, Underline: &b}
result3 := formatBraceFlags(be3)
assert.Contains(t, result3, "0")
assert.Contains(t, result3, "i")
assert.Contains(t, result3, "_")
}
func TestIsRetryableError(t *testing.T) {
assert.False(t, isRetryableError(nil))
assert.False(t, isRetryableError(errors.New("some error")))
// 429 rate limit
apiErr429 := &gapi.Error{Code: 429}
assert.True(t, isRetryableError(apiErr429))
// 500 server error
apiErr500 := &gapi.Error{Code: 500}
assert.True(t, isRetryableError(apiErr500))
// 502 bad gateway
apiErr502 := &gapi.Error{Code: 502}
assert.True(t, isRetryableError(apiErr502))
// 503 service unavailable
apiErr503 := &gapi.Error{Code: 503}
assert.True(t, isRetryableError(apiErr503))
// 404 not found — not retryable
apiErr404 := &gapi.Error{Code: 404}
assert.False(t, isRetryableError(apiErr404))
// String-based match
assert.True(t, isRetryableError(errors.New("rateLimitExceeded")))
assert.True(t, isRetryableError(errors.New("error 429 too many requests")))
}
func TestCompilePattern(t *testing.T) {
e := sedExpr{pattern: `\d+`}
re, err := e.compilePattern()
assert.NoError(t, err)
assert.NotNil(t, re)
assert.True(t, re.MatchString("123"))
e2 := sedExpr{pattern: `[invalid`}
_, err2 := e2.compilePattern()
assert.Error(t, err2)
}
func TestRunDryRun(t *testing.T) {
var buf bytes.Buffer
u, err := ui.New(ui.Options{Stdout: &buf, Stderr: io.Discard, Color: "never"})
require.NoError(t, err)
cmd := &DocsSedCmd{}
exprs := []sedExpr{
{pattern: "hello", replacement: "world", global: true},
{pattern: "[invalid", replacement: "x"},
{command: 'd', pattern: "delete-me"},
}
err = cmd.runDryRun(context.Background(), u, exprs)
assert.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "native") // first expr is native (global, plain replace)
assert.Contains(t, output, "ERROR") // second expr has invalid regex
assert.Contains(t, output, "delete") // third is delete command
assert.Contains(t, output, "dry-run: 3 expressions parsed")
}
func TestRunDryRun_WithBrace(t *testing.T) {
var buf bytes.Buffer
u, err := ui.New(ui.Options{Stdout: &buf, Stderr: io.Discard, Color: "never"})
require.NoError(t, err)
b := true
cmd := &DocsSedCmd{}
exprs := []sedExpr{
{pattern: "hello", replacement: "world", brace: &braceExpr{Bold: &b}},
}
err = cmd.runDryRun(context.Background(), u, exprs)
assert.NoError(t, err)
assert.Contains(t, buf.String(), "brace")
assert.Contains(t, buf.String(), "b")
}
func TestRunDryRun_CellExpr(t *testing.T) {
var buf bytes.Buffer
u, err := ui.New(ui.Options{Stdout: &buf, Stderr: io.Discard, Color: "never"})
require.NoError(t, err)
cmd := &DocsSedCmd{}
exprs := []sedExpr{
{
pattern: "",
replacement: "new value",
cellRef: &tableCellRef{tableIndex: 1, row: 2, col: 3},
},
}
err = cmd.runDryRun(context.Background(), u, exprs)
assert.NoError(t, err)
assert.Contains(t, buf.String(), "cell")
}
func TestInferBulletPreset(t *testing.T) {
// nil doc.Lists
doc := &docs.Document{}
assert.Equal(t, bulletPresetDisc, inferBulletPreset(doc, "list1"))
// Unknown list ID
doc.Lists = map[string]docs.List{}
assert.Equal(t, bulletPresetDisc, inferBulletPreset(doc, "unknown"))
// Unordered list (default)
doc.Lists["list1"] = docs.List{
ListProperties: &docs.ListProperties{
NestingLevels: []*docs.NestingLevel{
{GlyphType: "GLYPH_TYPE_UNSPECIFIED"},
},
},
}
assert.Equal(t, bulletPresetDisc, inferBulletPreset(doc, "list1"))
// Ordered list (DECIMAL)
doc.Lists["list2"] = docs.List{
ListProperties: &docs.ListProperties{
NestingLevels: []*docs.NestingLevel{
{GlyphType: "DECIMAL"},
},
},
}
assert.Equal(t, "NUMBERED_DECIMAL_NESTED", inferBulletPreset(doc, "list2"))
// Ordered list (UPPER_ALPHA)
doc.Lists["list3"] = docs.List{
ListProperties: &docs.ListProperties{
NestingLevels: []*docs.NestingLevel{
{GlyphType: "UPPER_ALPHA"},
},
},
}
assert.Equal(t, "NUMBERED_DECIMAL_NESTED", inferBulletPreset(doc, "list3"))
}
func TestBuildTextStyleRequests_Superscript(t *testing.T) {
reqs := buildTextStyleRequests([]string{"superscript"}, 1, 5)
require.Len(t, reqs, 1)
assert.Equal(t, "SUPERSCRIPT", reqs[0].UpdateTextStyle.TextStyle.BaselineOffset)
}
func TestBuildTextStyleRequests_Subscript(t *testing.T) {
reqs := buildTextStyleRequests([]string{"subscript"}, 1, 5)
require.Len(t, reqs, 1)
assert.Equal(t, "SUBSCRIPT", reqs[0].UpdateTextStyle.TextStyle.BaselineOffset)
}
func TestBuildTextStyleRequests_SmallCaps(t *testing.T) {
reqs := buildTextStyleRequests([]string{"smallcaps"}, 1, 5)
require.Len(t, reqs, 1)
assert.True(t, reqs[0].UpdateTextStyle.TextStyle.SmallCaps)
}
func TestBuildTextStyleRequests_Font(t *testing.T) {
reqs := buildTextStyleRequests([]string{"font:Arial"}, 1, 5)
require.Len(t, reqs, 1)
assert.Equal(t, "Arial", reqs[0].UpdateTextStyle.TextStyle.WeightedFontFamily.FontFamily)
}
func TestBuildTextStyleRequests_Size(t *testing.T) {
reqs := buildTextStyleRequests([]string{"size:14"}, 1, 5)
require.Len(t, reqs, 1)
assert.Equal(t, float64(14), reqs[0].UpdateTextStyle.TextStyle.FontSize.Magnitude)
}
func TestBuildTextStyleRequests_Color(t *testing.T) {
reqs := buildTextStyleRequests([]string{"color:#ff0000"}, 1, 5)
require.Len(t, reqs, 1)
assert.InDelta(t, 1.0, reqs[0].UpdateTextStyle.TextStyle.ForegroundColor.Color.RgbColor.Red, 0.01)
}
func TestBuildTextStyleRequests_BgColor(t *testing.T) {
reqs := buildTextStyleRequests([]string{"bg:#00ff00"}, 1, 5)
require.Len(t, reqs, 1)
assert.InDelta(t, 1.0, reqs[0].UpdateTextStyle.TextStyle.BackgroundColor.Color.RgbColor.Green, 0.01)
}
func TestBuildTextStyleRequests_BookmarkLink(t *testing.T) {
reqs := buildTextStyleRequests([]string{"link:#bookmark1"}, 1, 5)
require.Len(t, reqs, 1)
assert.Equal(t, "bookmark1", reqs[0].UpdateTextStyle.TextStyle.Link.BookmarkId)
}
func TestBuildTextStyleRequests_Empty(t *testing.T) {
reqs := buildTextStyleRequests([]string{}, 1, 5)
assert.Nil(t, reqs)
}
func TestBuildTextStyleRequests_UnknownFormat(t *testing.T) {
reqs := buildTextStyleRequests([]string{"unknownformat"}, 1, 5)
assert.Nil(t, reqs)
}