bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
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 / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
187 lines
7.3 KiB
Go
187 lines
7.3 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
package apps
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"os"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/larksuite/cli/errs"
|
||
"github.com/larksuite/cli/internal/httpmock"
|
||
)
|
||
|
||
const dbDataImportURL = "/open-apis/spark/v1/apps/app_x/db/data_import"
|
||
|
||
// chdirTemp 切到临时工作目录(--file 走 cwd 内相对路径),返回该目录。
|
||
func chdirTemp(t *testing.T) string {
|
||
t.Helper()
|
||
dir := t.TempDir()
|
||
old, _ := os.Getwd()
|
||
if err := os.Chdir(dir); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
t.Cleanup(func() { _ = os.Chdir(old) })
|
||
return dir
|
||
}
|
||
|
||
// TestAppsDBDataImport_RequiresAppID 验证空白 --app-id 报 --app-id 的 ValidationError。
|
||
func TestAppsDBDataImport_RequiresAppID(t *testing.T) {
|
||
chdirTemp(t)
|
||
_ = os.WriteFile("orders.csv", []byte("id\n1\n"), 0o600)
|
||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||
err := runAppsShortcut(t, AppsDBDataImport,
|
||
[]string{"+db-data-import", "--app-id", " ", "--file", "orders.csv", "--yes", "--as", "user"}, factory, stdout)
|
||
var ve *errs.ValidationError
|
||
if !errors.As(err, &ve) {
|
||
t.Fatalf("err = %T %v, want *errs.ValidationError", err, err)
|
||
}
|
||
if ve.Param != "--app-id" {
|
||
t.Fatalf("Param = %q, want --app-id", ve.Param)
|
||
}
|
||
}
|
||
|
||
// TestAppsDBDataImport_RejectsUnsupportedFormat 验证非 csv/json 文件(.txt)报不支持格式的校验错误。
|
||
func TestAppsDBDataImport_RejectsUnsupportedFormat(t *testing.T) {
|
||
chdirTemp(t)
|
||
_ = os.WriteFile("data.txt", []byte("x\n"), 0o600)
|
||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||
err := runAppsShortcut(t, AppsDBDataImport,
|
||
[]string{"+db-data-import", "--app-id", "app_x", "--file", "data.txt", "--yes", "--as", "user"}, factory, stdout)
|
||
p, ok := errs.ProblemOf(err)
|
||
if !ok || p.Category != errs.CategoryValidation || p.Subtype != errs.SubtypeInvalidArgument {
|
||
t.Fatalf("expected unsupported-format validation, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestAppsDBDataImport_RequiresConfirmation 验证缺 --yes 时报 requires confirmation 错误。
|
||
func TestAppsDBDataImport_RequiresConfirmation(t *testing.T) {
|
||
chdirTemp(t)
|
||
_ = os.WriteFile("orders.csv", []byte("id\n1\n"), 0o600)
|
||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||
err := runAppsShortcut(t, AppsDBDataImport,
|
||
[]string{"+db-data-import", "--app-id", "app_x", "--file", "orders.csv", "--as", "user"}, factory, stdout)
|
||
if err == nil || !strings.Contains(err.Error(), "requires confirmation") {
|
||
t.Fatalf("expected confirmation_required, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestAppsDBDataImport_RejectsOversizeFile 验证超过 1MB 上限的文件报 --file 的 ValidationError。
|
||
func TestAppsDBDataImport_RejectsOversizeFile(t *testing.T) {
|
||
chdirTemp(t)
|
||
// >1MB → size 校验
|
||
big := append([]byte("id\n"), make([]byte, dbDataImportMaxBytes+1)...)
|
||
_ = os.WriteFile("big.csv", big, 0o600)
|
||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||
err := runAppsShortcut(t, AppsDBDataImport,
|
||
[]string{"+db-data-import", "--app-id", "app_x", "--file", "big.csv", "--yes", "--as", "user"}, factory, stdout)
|
||
var ve *errs.ValidationError
|
||
if !errors.As(err, &ve) {
|
||
t.Fatalf("expected 1MB limit error, got %T %v", err, err)
|
||
}
|
||
if ve.Param != "--file" {
|
||
t.Fatalf("Param = %q, want --file", ve.Param)
|
||
}
|
||
}
|
||
|
||
// dry-run:multipart 上传——file_name + file 走 body,env + table 走 query(table 缺省取文件名)。
|
||
// TestAppsDBDataImport_DryRunMultipartShape 验证 dry-run 的 multipart 形态:file_name+file 走 body、env+table 走 query 且不再发 format。
|
||
func TestAppsDBDataImport_DryRunMultipartShape(t *testing.T) {
|
||
chdirTemp(t)
|
||
_ = os.WriteFile("orders.csv", []byte("id\n1\n"), 0o600)
|
||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||
if err := runAppsShortcut(t, AppsDBDataImport,
|
||
[]string{"+db-data-import", "--app-id", "app_x", "--file", "orders.csv", "--environment", "dev", "--dry-run", "--yes", "--as", "user"}, factory, stdout); err != nil {
|
||
t.Fatalf("dry-run err=%v", err)
|
||
}
|
||
var env struct {
|
||
API []struct {
|
||
Method string `json:"method"`
|
||
URL string `json:"url"`
|
||
Params map[string]interface{} `json:"params"`
|
||
Body map[string]interface{} `json:"body"`
|
||
} `json:"api"`
|
||
}
|
||
_ = json.Unmarshal([]byte(stdout.String()), &env)
|
||
a := env.API[0]
|
||
if a.Method != "POST" || a.URL != dbDataImportURL {
|
||
t.Fatalf("dry-run = %s %s", a.Method, a.URL)
|
||
}
|
||
if a.Body["file_name"] != "orders.csv" || a.Body["file"] == nil {
|
||
t.Fatalf("dry-run body should carry file_name + file: %v", a.Body)
|
||
}
|
||
if _, ok := a.Body["format"]; ok {
|
||
t.Fatalf("format must no longer be sent: %v", a.Body)
|
||
}
|
||
if a.Params["env"] != "dev" || a.Params["table"] != "orders" {
|
||
t.Fatalf("dry-run params (env+table) = %v", a.Params)
|
||
}
|
||
}
|
||
|
||
// TestAppsDBDataImport_DryRunOmitsEnvWhenUnset 验证不传 --environment 时 dry-run 的 query
|
||
// 不带 env 键(交服务端按应用形态自动选分支),但仍携带 table。
|
||
func TestAppsDBDataImport_DryRunOmitsEnvWhenUnset(t *testing.T) {
|
||
chdirTemp(t)
|
||
_ = os.WriteFile("orders.csv", []byte("id\n1\n"), 0o600)
|
||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||
if err := runAppsShortcut(t, AppsDBDataImport,
|
||
[]string{"+db-data-import", "--app-id", "app_x", "--file", "orders.csv", "--dry-run", "--yes", "--as", "user"}, factory, stdout); err != nil {
|
||
t.Fatalf("dry-run err=%v", err)
|
||
}
|
||
var env struct {
|
||
API []struct {
|
||
Params map[string]interface{} `json:"params"`
|
||
} `json:"api"`
|
||
}
|
||
_ = json.Unmarshal([]byte(stdout.String()), &env)
|
||
p := env.API[0].Params
|
||
if _, ok := p["env"]; ok {
|
||
t.Fatalf("no --environment → env key must be omitted, got params=%v", p)
|
||
}
|
||
if p["table"] != "orders" {
|
||
t.Fatalf("table should still default to file basename, got params=%v", p)
|
||
}
|
||
}
|
||
|
||
// TestAppsDBDataImport_Success 验证成功导入后输出含 table、rows 与回显的 file 名。
|
||
func TestAppsDBDataImport_Success(t *testing.T) {
|
||
chdirTemp(t)
|
||
_ = os.WriteFile("orders.csv", []byte("id,name\n1,a\n2,b\n"), 0o600)
|
||
factory, stdout, reg := newAppsExecuteFactory(t)
|
||
reg.Register(&httpmock.Stub{
|
||
Method: "POST", URL: dbDataImportURL,
|
||
Body: map[string]interface{}{"code": 0, "data": map[string]interface{}{"table": "orders", "rows": 2}},
|
||
})
|
||
if err := runAppsShortcut(t, AppsDBDataImport,
|
||
[]string{"+db-data-import", "--app-id", "app_x", "--file", "orders.csv", "--table", "orders", "--yes", "--as", "user"}, factory, stdout); err != nil {
|
||
t.Fatalf("execute err=%v", err)
|
||
}
|
||
got := stdout.String()
|
||
if !strings.Contains(got, `"table": "orders"`) || !strings.Contains(got, `"rows": 2`) || !strings.Contains(got, `"file": "orders.csv"`) {
|
||
t.Fatalf("output missing fields:\n%s", got)
|
||
}
|
||
}
|
||
|
||
// TestAppsDBDataImport_TableDefaultsToFileBasename 验证未传 --table 时表名缺省取文件名去扩展名(customers.json→customers)。
|
||
func TestAppsDBDataImport_TableDefaultsToFileBasename(t *testing.T) {
|
||
chdirTemp(t)
|
||
_ = os.WriteFile("customers.json", []byte(`[{"id":1}]`), 0o600)
|
||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||
if err := runAppsShortcut(t, AppsDBDataImport,
|
||
[]string{"+db-data-import", "--app-id", "app_x", "--file", "customers.json", "--dry-run", "--yes", "--as", "user"}, factory, stdout); err != nil {
|
||
t.Fatalf("dry-run err=%v", err)
|
||
}
|
||
var env struct {
|
||
API []struct {
|
||
Params map[string]interface{} `json:"params"`
|
||
} `json:"api"`
|
||
}
|
||
_ = json.Unmarshal([]byte(stdout.String()), &env)
|
||
if env.API[0].Params["table"] != "customers" {
|
||
t.Fatalf("expected table=customers (from file basename) in params, got %v", env.API[0].Params)
|
||
}
|
||
}
|