Merge pull request #654 from LastdianXuan/codex/fix-aitable-import-file-size
fix(aitable): require import upload file size
This commit is contained in:
@@ -4150,6 +4150,10 @@ role-get 自行 merge)。
|
||||
if err := validateRequiredFlags(cmd, "file-name"); err != nil {
|
||||
return err
|
||||
}
|
||||
fileSize, _ := cmd.Flags().GetInt64("file-size")
|
||||
if fileSize <= 0 {
|
||||
return fmt.Errorf("flag --file-size is required and must be a positive integer")
|
||||
}
|
||||
baseID, err := mustFlagOrFallback(cmd, "base-id", "base")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -4157,9 +4161,7 @@ role-get 自行 merge)。
|
||||
toolArgs := map[string]any{
|
||||
"baseId": baseID,
|
||||
"fileName": mustGetFlag(cmd, "file-name"),
|
||||
}
|
||||
if v, _ := cmd.Flags().GetInt64("file-size"); v > 0 {
|
||||
toolArgs["fileSize"] = v
|
||||
"fileSize": fileSize,
|
||||
}
|
||||
return callAitableTool("prepare_import_upload", toolArgs)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2026 Alibaba Group
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func runAitableImportUploadCommand(t *testing.T, args ...string) (*aitableTestCaller, error) {
|
||||
t.Helper()
|
||||
caller := &aitableTestCaller{}
|
||||
installAitableDeps(t, caller)
|
||||
deps.Out.w = io.Discard
|
||||
deps.Out.errW = io.Discard
|
||||
|
||||
cmd := newAitableCommand()
|
||||
cmd.SilenceErrors = true
|
||||
cmd.SilenceUsage = true
|
||||
cmd.SetOut(io.Discard)
|
||||
cmd.SetErr(io.Discard)
|
||||
cmd.SetArgs(append([]string{"import", "upload"}, args...))
|
||||
return caller, cmd.ExecuteContext(context.Background())
|
||||
}
|
||||
|
||||
func TestAitableImportUploadRequiresPositiveFileSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{name: "missing", args: nil},
|
||||
{name: "zero", args: []string{"--file-size", "0"}},
|
||||
{name: "negative", args: []string{"--file-size", "-1"}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
args := []string{"--base-id", "base-smoke", "--file-name", "data.xlsx"}
|
||||
caller, err := runAitableImportUploadCommand(t, append(args, test.args...)...)
|
||||
if err == nil {
|
||||
t.Fatal("expected --file-size validation error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "--file-size") {
|
||||
t.Fatalf("error = %q, want --file-size validation", err)
|
||||
}
|
||||
if len(caller.calls) != 0 {
|
||||
t.Fatalf("invalid file size dispatched %d MCP call(s): %#v", len(caller.calls), caller.calls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAitableImportUploadPassesFileSizeToMCP(t *testing.T) {
|
||||
caller, err := runAitableImportUploadCommand(t,
|
||||
"--base-id", "base-smoke",
|
||||
"--file-name", "data.xlsx",
|
||||
"--file-size", "204800",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("aitable import upload returned error: %v", err)
|
||||
}
|
||||
if len(caller.calls) != 1 {
|
||||
t.Fatalf("tool call count = %d, want 1", len(caller.calls))
|
||||
}
|
||||
call := caller.calls[0]
|
||||
if call.server != "aitable" || call.tool != "prepare_import_upload" {
|
||||
t.Fatalf("tool call = %s/%s, want aitable/prepare_import_upload", call.server, call.tool)
|
||||
}
|
||||
want := map[string]any{
|
||||
"baseId": "base-smoke",
|
||||
"fileName": "data.xlsx",
|
||||
"fileSize": int64(204800),
|
||||
}
|
||||
if !reflect.DeepEqual(call.args, want) {
|
||||
t.Fatalf("tool args = %#v, want %#v", call.args, want)
|
||||
}
|
||||
}
|
||||
@@ -2107,16 +2107,27 @@ var ImportUpload = shortcut.Shortcut{
|
||||
Flags: []shortcut.Flag{
|
||||
{Name: "base-id", Type: shortcut.FlagString, Desc: "Base ID", Required: true},
|
||||
{Name: "file-name", Type: shortcut.FlagString, Desc: "文件名(含扩展名)", Required: true},
|
||||
{Name: "file-size", Type: shortcut.FlagInt, Desc: "文件大小(字节,可选)"},
|
||||
{Name: "file-size", Type: shortcut.FlagInt, Desc: "文件大小(字节,必须大于 0)", Required: true},
|
||||
},
|
||||
Constraints: []shortcut.Constraint{
|
||||
{
|
||||
Kind: shortcut.ConstraintCustom,
|
||||
Flags: []string{"file-size"},
|
||||
Description: "--file-size 必须是大于 0 的整数(实际文件大小,单位字节)",
|
||||
},
|
||||
},
|
||||
Tips: []string{`dws aitable +import-upload --base-id B --file-name data.xlsx --file-size 204800`},
|
||||
Validate: func(rt *shortcut.RuntimeContext) error {
|
||||
if rt.Int("file-size") <= 0 {
|
||||
return fmt.Errorf("flag --file-size is required and must be a positive integer")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Execute: func(rt *shortcut.RuntimeContext) error {
|
||||
params := map[string]any{
|
||||
"baseId": rt.Str("base-id"),
|
||||
"fileName": rt.Str("file-name"),
|
||||
}
|
||||
if rt.Changed("file-size") {
|
||||
params["fileSize"] = rt.Int("file-size")
|
||||
"fileSize": rt.Int("file-size"),
|
||||
}
|
||||
return rt.CallMCP("prepare_import_upload", params)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2026 Alibaba Group
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package aitable
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type platformCoverageCaller struct {
|
||||
called bool
|
||||
product string
|
||||
tool string
|
||||
args map[string]any
|
||||
}
|
||||
|
||||
func (f *platformCoverageCaller) reset() {
|
||||
f.called, f.product, f.tool, f.args = false, "", "", nil
|
||||
}
|
||||
|
||||
func (f *platformCoverageCaller) CallTool(_ context.Context, product, tool string, args map[string]any) (*edition.ToolResult, error) {
|
||||
f.called, f.product, f.tool, f.args = true, product, tool, args
|
||||
return &edition.ToolResult{
|
||||
Content: []edition.ContentBlock{{Type: "text", Text: `{"result":[]}`}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *platformCoverageCaller) Format() string { return "json" }
|
||||
func (f *platformCoverageCaller) DryRun() bool { return false }
|
||||
func (f *platformCoverageCaller) Fields() string { return "" }
|
||||
func (f *platformCoverageCaller) JQ() string { return "" }
|
||||
|
||||
func newPlatformCoverageRoot() *cobra.Command {
|
||||
root := &cobra.Command{Use: "dws", SilenceUsage: true, SilenceErrors: true}
|
||||
root.SetOut(io.Discard)
|
||||
root.SetErr(io.Discard)
|
||||
root.PersistentFlags().Bool("yes", false, "")
|
||||
root.PersistentFlags().Bool("dry-run", false, "")
|
||||
root.PersistentFlags().String("format", "json", "")
|
||||
root.AddCommand(shortcut.Commands()...)
|
||||
return root
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageImportUploadRequiresPositiveFileSize(t *testing.T) {
|
||||
fake := &platformCoverageCaller{}
|
||||
helpers.InitDeps(fake)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fileSize string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "missing", wantErr: true},
|
||||
{name: "zero", fileSize: "0", wantErr: true},
|
||||
{name: "negative", fileSize: "-1", wantErr: true},
|
||||
{name: "positive", fileSize: "204800"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
fake.reset()
|
||||
root := newPlatformCoverageRoot()
|
||||
args := []string{
|
||||
"aitable", "+import-upload",
|
||||
"--base-id", "base-smoke",
|
||||
"--file-name", "data.xlsx",
|
||||
"--yes",
|
||||
}
|
||||
if test.fileSize != "" {
|
||||
args = append(args, "--file-size", test.fileSize)
|
||||
}
|
||||
root.SetArgs(args)
|
||||
err := root.Execute()
|
||||
|
||||
if test.wantErr {
|
||||
if err == nil {
|
||||
t.Fatal("expected --file-size validation error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "--file-size") {
|
||||
t.Fatalf("error = %q, want --file-size validation", err)
|
||||
}
|
||||
if fake.called {
|
||||
t.Fatalf("invalid file size called %s/%s with %#v", fake.product, fake.tool, fake.args)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("positive file size returned error: %v", err)
|
||||
}
|
||||
if !fake.called || fake.product != "aitable" || fake.tool != "prepare_import_upload" {
|
||||
t.Fatalf("tool call = called:%v %s/%s, want aitable/prepare_import_upload", fake.called, fake.product, fake.tool)
|
||||
}
|
||||
if got := fake.args["fileSize"]; got != 204800 {
|
||||
t.Fatalf("fileSize = %#v, want 204800", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user