Files
larksuite-cli/shortcuts/sheets/validation_params_test.go
evandance b07be60068 feat(sheets): emit typed error envelopes across the sheets domain (#1348)
Emit structured validation, API, network, file, and internal error envelopes for Sheets shortcuts so users and agents can recover from failed spreadsheet workflows using stable type, subtype, param, and code fields.

Add Sheets domain errscontract and golangci guards to prevent legacy envelope and common helper regressions.
2026-06-10 11:51:42 +08:00

105 lines
3.4 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package sheets
import (
"errors"
"testing"
"github.com/larksuite/cli/errs"
)
// TestValidationParamMetadata locks the structured-param contract across the
// sheets domain: a validation failure must carry typed metadata
// (category/subtype via errs.ProblemOf) and tag the offending flag(s) via
// Param/Params, so consumers (agents) know which flag to fix without parsing
// the human message. Covers the four representative shapes — required,
// at-least-one, mutually-exclusive, and local-input-file errors.
func TestValidationParamMetadata(t *testing.T) {
t.Parallel()
// assertValidationProblem checks the typed metadata (category + subtype) via
// errs.ProblemOf and returns the ValidationError for param-level assertions.
assertValidationProblem := func(t *testing.T, err error) *errs.ValidationError {
t.Helper()
p, ok := errs.ProblemOf(err)
if !ok {
t.Fatalf("error = %T %v, want typed problem", err, err)
}
if p.Category != errs.CategoryValidation {
t.Errorf("category = %q, want %q", p.Category, errs.CategoryValidation)
}
if p.Subtype != errs.SubtypeInvalidArgument {
t.Errorf("subtype = %q, want %q", p.Subtype, errs.SubtypeInvalidArgument)
}
var ve *errs.ValidationError
if !errors.As(err, &ve) {
t.Fatalf("error = %T, want *errs.ValidationError", err)
}
return ve
}
assertParam := func(t *testing.T, err error, want string) {
t.Helper()
if ve := assertValidationProblem(t, err); ve.Param != want {
t.Fatalf("param = %q, want %q", ve.Param, want)
}
}
assertParams := func(t *testing.T, err error, want ...string) {
t.Helper()
ve := assertValidationProblem(t, err)
got := map[string]bool{}
for _, p := range ve.Params {
got[p.Name] = true
}
if len(ve.Params) != len(want) {
t.Fatalf("params = %#v, want %v", ve.Params, want)
}
for _, w := range want {
if !got[w] {
t.Fatalf("params = %#v, missing %q", ve.Params, w)
}
}
}
t.Run("required flag tags single param", func(t *testing.T) {
t.Parallel()
// --image-token satisfies the image source, so the missing --image-name
// trips the single-flag required check routed through sheetsValidationForFlag.
fv := newMapFlagViewForCommand("+float-image-create", map[string]interface{}{"image-token": "tok"})
_, err := floatImageProperties(fv, "", true)
assertParam(t, err, "--image-name")
})
t.Run("at-least-one tags every candidate flag", func(t *testing.T) {
t.Parallel()
fv := newMapFlagViewForCommand("+float-image-create", map[string]interface{}{})
_, err := floatImageProperties(fv, "", true)
assertParams(t, err, "--image", "--image-token", "--image-uri")
})
t.Run("mutually exclusive tags only the conflicting flags", func(t *testing.T) {
t.Parallel()
// Only --image and --image-token are set; the param list must not blame
// the untouched --image-uri.
fv := newMapFlagViewForCommand("+float-image-create", map[string]interface{}{
"image": "a.png",
"image-token": "tok",
})
_, err := floatImageProperties(fv, "", true)
assertParams(t, err, "--image", "--image-token")
})
t.Run("local input file error tags flag and preserves cause", func(t *testing.T) {
t.Parallel()
cause := errors.New("stat failed")
err := sheetsInputStatError("image", cause)
assertParam(t, err, "--image")
if !errors.Is(err, cause) {
t.Errorf("expected the original stat error preserved as the cause")
}
})
}