Files
dingtalk-workspace-cli/internal/helpers/small_remaining_coverage_test.go
huwenjiao.hwj 0bb2b6ce98 feat(sheet): CSV export, style extensions and create-with-data
Adds four capabilities and, for the style surface, moves to the interface that
can actually express them.

Added:
- sheet create --values / --sheets / --styles: create a workbook and populate it
  in one command. --values takes a 2D array into the default sheet, --sheets
  takes typed tables across several sheets, --styles carries cell_styles /
  row_sizes / col_sizes / cell_merges. Every structure and enum is validated
  before the document is created, so an invalid config never leaves an orphan
  empty document behind.
- sheet export --export-format csv: synchronous single-sheet RFC4180 export with
  --sheet-id, --range and --value-render-option. --output writes to a file (a
  directory gets sheet-export.csv), otherwise the CSV goes to stdout while the
  truncation warning goes to stderr, keeping stdout pipeable.
- sheet update-dimension --size-type: pixel / standard (restore the default row
  height or column width) / auto (fit row height to content, ROWS only).
- sheet replace --match-formula: search and replace inside formula text.
- sheet range set-style --font-style / --font-line / --font-family /
  --border-styles-json.
- sheet range batch-set-style --ranges: stamp one style across several
  sheet-qualified ranges.

Changed (breaking Schema change, no CLI break):
- sheet range set-style moves from update_range to set_cell_range. The
  update_range style channel exposes exactly eight properties
  (backgroundColors, fontSizes, horizontalAlignments, verticalAlignments,
  fontColors, fontWeights, wordWrap, numberFormat) and has no slot for italic,
  underline/line-through, font family or borders, so the four new dimensions are
  not expressible there. interface_ref becomes set_cell_range and the twelve
  style flags stop publishing a flat property, because the value now lands in
  cells[i][j].cellStyles.* with no single top-level field to name.
- sheet range batch-set-style submits one atomic batch_update instead of looping
  update_range, so a partial failure no longer leaves half the ranges stamped.
  --continue-on-error becomes a server passthrough. Caps the fan-out at 100
  ranges and 200000 cells in aggregate.
- sheet export and sheet create declare interface_mode=composite: both route
  across several tools depending on the flags, so a single mcp ref was wrong.

Schema hygiene:
- Nineteen parameters that previously resolved through flag_name_inference into
  property names present in no request (bgColor, exportFormat, values, ...) are
  now reviewed mapping exclusions with a stated reason, so Schema omits the
  property instead of inventing one.

schema-compatibility reports 17 changed fields: 13 on sheet.range_set_style
(interface_ref plus twelve property mappings) and 2 each on
sheet.submit_export_job and sheet.create_workspace_sheet (interface_mode plus
interface_ref). No CLI flag is removed and no command path changes; the same
invocation runs on both the old and the new binary. Landing this needs a
decision on the Schema contract break.

Tests: targeted sheet suites in internal/helpers and the interface disposition
contract tests in internal/app pass; make build and gofmt clean;
check-schema-catalog, check-generated-drift, check-command-surface,
check-skill-commands and check-runtime-confirmation-truth all pass.
2026-08-07 20:25:21 +08:00

50 lines
1.9 KiB
Go

package helpers
import (
"context"
"errors"
"net/http"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
)
func TestCrossPlatformCoverageSheetAndMinutesSmallRemainingBranches(t *testing.T) {
if _, err := buildStyleCells(&styleSpec{FontColorsJSON: "{"}, 1, 1); err == nil {
t.Fatal("invalid font colors JSON returned nil")
}
root := &cobra.Command{Use: "sheet"}
group := &cobra.Command{Use: "range", Run: func(*cobra.Command, []string) {}}
group.AddCommand(&cobra.Command{Use: "read", Run: func(*cobra.Command, []string) {}})
root.AddCommand(group)
attachUnknownSubcommandGuard(root)
if err := root.RunE(root, []string{"read"}); err == nil || !strings.Contains(err.Error(), "range read") {
t.Fatalf("deep suggestion err=%v", err)
}
installScriptedCaller(t, &scriptedToolCaller{dry: true})
if err := executeFilterCoverage(t, newMinutesCommand(), "permission", "add", "--ids", "id", "--member-uids", "uid", "--policy", "3", "--cover=false"); err != nil {
t.Fatalf("explicit false cover: %v", err)
}
if err := executeFilterCoverage(t, newMinutesCommand(), "list", "all", "--start", "2030-01-01T10:00:00+08:00", "--end", "2030-01-01T09:00:00+08:00"); err == nil {
t.Fatal("reversed minutes range returned nil")
}
}
func TestCrossPlatformCoverageGeminiForwardRemainingFailures(t *testing.T) {
invalid := &geminiAPIForwarder{baseURL: "%", model: "model", timeout: time.Second}
if _, err := invalid.forward(context.Background(), "", "text"); err == nil {
t.Fatal("invalid Gemini endpoint returned nil")
}
client := &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
return nil, errors.New("transport")
})}
f := &geminiAPIForwarder{baseURL: "https://example.test", model: "model", apiKey: "key", timeout: time.Second, httpClient: client}
if _, err := f.forward(context.Background(), "", "text"); err == nil || !strings.Contains(err.Error(), "transport") {
t.Fatalf("Gemini transport err=%v", err)
}
}