Files
wehub-resource-sync bf9395e022
CI / results (push) Blocked by required conditions
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / e2e-live (push) Waiting to run
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / deadcode (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:22:54 +08:00

430 lines
12 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package cmdutil
import (
"bytes"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/internal/vfs/localfileio"
)
// failingReader always errors on Read, to exercise stdin read-failure paths.
type failingReader struct{ err error }
func (r *failingReader) Read([]byte) (int, error) { return 0, r.err }
// requireFileValidationError asserts err is a typed *errs.ValidationError with
// the expected subtype, exit code 2 (legacy ErrValidation parity), and a
// param diagnostic referencing --file (either Param or one of Params).
func requireFileValidationError(t *testing.T, err error, wantSubtype errs.Subtype) *errs.ValidationError {
t.Helper()
var valErr *errs.ValidationError
if !errors.As(err, &valErr) {
t.Fatalf("expected *errs.ValidationError, got %T: %v", err, err)
}
if valErr.Subtype != wantSubtype {
t.Errorf("subtype = %q, want %q", valErr.Subtype, wantSubtype)
}
if got := output.ExitCodeOf(err); got != output.ExitValidation {
t.Errorf("exit code = %d, want %d (ExitValidation, legacy parity)", got, output.ExitValidation)
}
mentionsFile := valErr.Param == "--file"
for _, p := range valErr.Params {
if p.Name == "--file" {
mentionsFile = true
}
}
if !mentionsFile {
t.Errorf("expected --file in Param/Params, got Param=%q Params=%v", valErr.Param, valErr.Params)
}
return valErr
}
func TestParseFileFlag(t *testing.T) {
tests := []struct {
name string
raw string
defaultField string
wantField string
wantPath string
wantStdin bool
}{
{
name: "simple filename uses default field",
raw: "photo.jpg",
defaultField: "file",
wantField: "file",
wantPath: "photo.jpg",
wantStdin: false,
},
{
name: "simple filename with custom default",
raw: "photo.jpg",
defaultField: "image",
wantField: "image",
wantPath: "photo.jpg",
wantStdin: false,
},
{
name: "explicit field prefix",
raw: "image=photo.jpg",
defaultField: "file",
wantField: "image",
wantPath: "photo.jpg",
wantStdin: false,
},
{
name: "stdin bare",
raw: "-",
defaultField: "file",
wantField: "file",
wantPath: "",
wantStdin: true,
},
{
name: "stdin with field prefix",
raw: "image=-",
defaultField: "file",
wantField: "image",
wantPath: "",
wantStdin: true,
},
{
name: "path with equals sign (only first equals splits)",
raw: "field=path/to/file=1.jpg",
defaultField: "file",
wantField: "field",
wantPath: "path/to/file=1.jpg",
wantStdin: false,
},
{
name: "absolute path no prefix",
raw: "/tmp/photo.jpg",
defaultField: "file",
wantField: "file",
wantPath: "/tmp/photo.jpg",
wantStdin: false,
},
{
name: "absolute path with field prefix",
raw: "image=/tmp/photo.jpg",
defaultField: "file",
wantField: "image",
wantPath: "/tmp/photo.jpg",
wantStdin: false,
},
{
name: "empty field prefix falls through to default",
raw: "=photo.jpg",
defaultField: "file",
wantField: "file",
wantPath: "=photo.jpg",
wantStdin: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
field, path, isStdin := ParseFileFlag(tt.raw, tt.defaultField)
if field != tt.wantField {
t.Errorf("field = %q, want %q", field, tt.wantField)
}
if path != tt.wantPath {
t.Errorf("path = %q, want %q", path, tt.wantPath)
}
if isStdin != tt.wantStdin {
t.Errorf("isStdin = %v, want %v", isStdin, tt.wantStdin)
}
})
}
}
func TestValidateFileFlag(t *testing.T) {
tests := []struct {
name string
file string
params string
data string
outputPath string
pageAll bool
httpMethod string
wantErr string // empty means no error
}{
{
name: "empty file is valid",
file: "",
httpMethod: "GET",
wantErr: "",
},
{
name: "empty file path",
file: "field=",
httpMethod: "POST",
wantErr: "--file: empty file path",
},
{
name: "file with output",
file: "photo.jpg",
outputPath: "out.json",
httpMethod: "POST",
wantErr: "--file and --output are mutually exclusive",
},
{
name: "file with page-all",
file: "photo.jpg",
pageAll: true,
httpMethod: "POST",
wantErr: "--file and --page-all are mutually exclusive",
},
{
name: "stdin file with stdin data",
file: "-",
data: "-",
httpMethod: "POST",
wantErr: "--file and --data cannot both read from stdin",
},
{
name: "stdin file with stdin params",
file: "-",
params: "-",
httpMethod: "POST",
wantErr: "--file and --params cannot both read from stdin",
},
{
name: "file with GET method",
file: "photo.jpg",
httpMethod: "GET",
wantErr: "--file requires POST, PUT, PATCH, or DELETE method",
},
{
name: "file with POST method",
file: "photo.jpg",
httpMethod: "POST",
wantErr: "",
},
{
name: "file with PUT method",
file: "photo.jpg",
httpMethod: "PUT",
wantErr: "",
},
{
name: "file with PATCH method",
file: "photo.jpg",
httpMethod: "PATCH",
wantErr: "",
},
{
name: "file with DELETE method",
file: "photo.jpg",
httpMethod: "DELETE",
wantErr: "",
},
{
name: "stdin with field prefix and data stdin",
file: "image=-",
data: "-",
httpMethod: "POST",
wantErr: "--file and --data cannot both read from stdin",
},
{
name: "stdin with field prefix and params stdin",
file: "image=-",
params: "-",
httpMethod: "POST",
wantErr: "--file and --params cannot both read from stdin",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateFileFlag(tt.file, tt.params, tt.data, tt.outputPath, tt.pageAll, tt.httpMethod)
if tt.wantErr == "" {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
return
}
if err == nil {
t.Fatalf("expected error containing %q, got nil", tt.wantErr)
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Errorf("error = %q, want containing %q", err.Error(), tt.wantErr)
}
requireFileValidationError(t, err, errs.SubtypeInvalidArgument)
})
}
}
func TestBuildFormdata(t *testing.T) {
fio := &localfileio.LocalFileIO{}
t.Run("stdin success", func(t *testing.T) {
stdin := bytes.NewReader([]byte("file-content-here"))
fd, err := BuildFormdata(fio, "file", "", true, stdin, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if fd == nil {
t.Fatal("expected non-nil Formdata")
}
})
t.Run("stdin nil reader", func(t *testing.T) {
_, err := BuildFormdata(fio, "file", "", true, nil, nil)
if err == nil {
t.Fatal("expected error for nil stdin")
}
if !strings.Contains(err.Error(), "stdin is not available") {
t.Errorf("error = %q, want containing %q", err.Error(), "stdin is not available")
}
requireFileValidationError(t, err, errs.SubtypeFailedPrecondition)
})
t.Run("stdin read failure", func(t *testing.T) {
readErr := errors.New("pipe closed")
_, err := BuildFormdata(fio, "file", "", true, &failingReader{err: readErr}, nil)
if err == nil {
t.Fatal("expected error for failing stdin reader")
}
requireFileValidationError(t, err, errs.SubtypeInvalidArgument)
if !errors.Is(err, readErr) {
t.Error("underlying read error not reachable via errors.Is; WithCause missing")
}
})
t.Run("stdin empty", func(t *testing.T) {
stdin := bytes.NewReader([]byte{})
_, err := BuildFormdata(fio, "file", "", true, stdin, nil)
if err == nil {
t.Fatal("expected error for empty stdin")
}
if !strings.Contains(err.Error(), "stdin is empty") {
t.Errorf("error = %q, want containing %q", err.Error(), "stdin is empty")
}
requireFileValidationError(t, err, errs.SubtypeInvalidArgument)
})
t.Run("file open success", func(t *testing.T) {
dir := t.TempDir()
TestChdir(t, dir)
if err := os.WriteFile(filepath.Join(dir, "test.txt"), []byte("hello"), 0600); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
fd, err := BuildFormdata(fio, "photo", "test.txt", false, nil, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if fd == nil {
t.Fatal("expected non-nil Formdata")
}
})
t.Run("file not found", func(t *testing.T) {
dir := t.TempDir()
TestChdir(t, dir)
_, err := BuildFormdata(fio, "file", "nonexistent.txt", false, nil, nil)
if err == nil {
t.Fatal("expected error for missing file")
}
if !strings.Contains(err.Error(), "cannot open file:") {
t.Errorf("error = %q, want containing %q", err.Error(), "cannot open file:")
}
valErr := requireFileValidationError(t, err, errs.SubtypeInvalidArgument)
if valErr.Cause == nil {
t.Error("expected the os open error attached as Cause")
}
})
t.Run("dataJSON fields added", func(t *testing.T) {
dir := t.TempDir()
TestChdir(t, dir)
if err := os.WriteFile(filepath.Join(dir, "upload.bin"), []byte("data"), 0600); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
dataJSON := map[string]any{
"file_name": "report.pdf",
"parent_type": "doc_image",
"size": 1024,
}
fd, err := BuildFormdata(fio, "file", "upload.bin", false, nil, dataJSON)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if fd == nil {
t.Fatal("expected non-nil Formdata")
}
})
t.Run("dataJSON nil is fine", func(t *testing.T) {
stdin := bytes.NewReader([]byte("content"))
fd, err := BuildFormdata(fio, "file", "", true, stdin, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if fd == nil {
t.Fatal("expected non-nil Formdata")
}
})
t.Run("dataJSON non-map is ignored", func(t *testing.T) {
stdin := bytes.NewReader([]byte("content"))
fd, err := BuildFormdata(fio, "file", "", true, stdin, "not-a-map")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if fd == nil {
t.Fatal("expected non-nil Formdata")
}
})
}
// TestFormatFormFieldValue locks in the fix for the float64 -> scientific
// notation bug. JSON numbers unmarshal to float64, and fmt's default %v for
// float64 delegates to %g which switches to scientific notation at ~1e6
// (e.g. 1185356 -> "1.185356e+06"). Backends that parse the form field as an
// integer reject that, surfacing as a generic "params error".
func TestFormatFormFieldValue(t *testing.T) {
t.Parallel()
tests := []struct {
name string
in any
want string
}{
{"float64 large integer avoids scientific", float64(1185356), "1185356"},
{"float64 below scientific threshold", float64(358934), "358934"},
{"float64 zero", float64(0), "0"},
{"float64 huge", float64(20 * 1024 * 1024), "20971520"},
{"float64 negative", float64(-42), "-42"},
{"float64 fractional preserved", float64(3.14), "3.14"},
{"string pass-through", "hello", "hello"},
{"bool true", true, "true"},
{"int via %v", 42, "42"},
{"int64 via %v", int64(9007199254740992), "9007199254740992"},
}
for _, temp := range tests {
tt := temp
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := formatFormFieldValue(tt.in)
if got != tt.want {
t.Fatalf("formatFormFieldValue(%v) = %q, want %q", tt.in, got, tt.want)
}
})
}
}