Files
larksuite-cli/shortcuts/drive/drive_import_test.go

765 lines
23 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package drive
import (
"context"
"encoding/json"
"os"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/core"
"github.com/larksuite/cli/internal/httpmock"
_ "github.com/larksuite/cli/internal/vfs/localfileio"
"github.com/larksuite/cli/shortcuts/common"
)
func TestImportDefaultFileName(t *testing.T) {
t.Parallel()
tests := []struct {
name string
filePath string
want string
}{
{
name: "strip xlsx extension",
filePath: "/tmp/base-import.xlsx",
want: "base-import",
},
{
name: "strip last extension only",
filePath: "/tmp/report.final.csv",
want: "report.final",
},
{
name: "keep name without extension",
filePath: "/tmp/README",
want: "README",
},
{
name: "keep hidden file name when trim would be empty",
filePath: "/tmp/.env",
want: ".env",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := importDefaultFileName(tt.filePath); got != tt.want {
t.Fatalf("importDefaultFileName(%q) = %q, want %q", tt.filePath, got, tt.want)
}
})
}
}
func TestImportTargetFileName(t *testing.T) {
t.Parallel()
if got := importTargetFileName("/tmp/base-import.xlsx", "custom-name.xlsx"); got != "custom-name.xlsx" {
t.Fatalf("explicit name should win, got %q", got)
}
if got := importTargetFileName("/tmp/base-import.xlsx", ""); got != "base-import" {
t.Fatalf("default import name = %q, want %q", got, "base-import")
}
}
func TestDriveImportDryRunUsesExtensionlessDefaultName(t *testing.T) {
tmpDir := t.TempDir()
withDriveWorkingDir(t, tmpDir)
if err := os.WriteFile("base-import.xlsx", []byte("fake-xlsx"), 0644); err != nil {
t.Fatalf("WriteFile() error: %v", err)
}
cmd := &cobra.Command{Use: "drive +import"}
cmd.Flags().String("file", "", "")
cmd.Flags().String("type", "", "")
cmd.Flags().String("folder-token", "", "")
cmd.Flags().String("name", "", "")
cmd.Flags().String("target-token", "", "")
if err := cmd.Flags().Set("file", "./base-import.xlsx"); err != nil {
t.Fatalf("set --file: %v", err)
}
if err := cmd.Flags().Set("type", "bitable"); err != nil {
t.Fatalf("set --type: %v", err)
}
if err := cmd.Flags().Set("folder-token", "fld_test"); err != nil {
t.Fatalf("set --folder-token: %v", err)
}
runtime := common.TestNewRuntimeContextWithCtx(context.Background(), cmd, nil)
dry := DriveImport.DryRun(context.Background(), runtime)
if dry == nil {
t.Fatal("DryRun returned nil")
}
data, err := json.Marshal(dry)
if err != nil {
t.Fatalf("marshal dry run: %v", err)
}
var got struct {
API []struct {
Body map[string]interface{} `json:"body"`
} `json:"api"`
}
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("unmarshal dry run json: %v", err)
}
if len(got.API) != 3 {
t.Fatalf("expected 3 API calls, got %d", len(got.API))
}
uploadName, _ := got.API[0].Body["file_name"].(string)
if uploadName != "base-import.xlsx" {
t.Fatalf("upload file_name = %q, want %q", uploadName, "base-import.xlsx")
}
importName, _ := got.API[1].Body["file_name"].(string)
if importName != "base-import" {
t.Fatalf("import task file_name = %q, want %q", importName, "base-import")
}
}
func TestDriveImportDryRunShowsMultipartUploadForLargeFile(t *testing.T) {
tmpDir := t.TempDir()
withDriveWorkingDir(t, tmpDir)
fh, err := os.Create("large.xlsx")
if err != nil {
t.Fatalf("Create() error: %v", err)
}
if err := fh.Truncate(common.MaxDriveMediaUploadSinglePartSize + 1); err != nil {
t.Fatalf("Truncate() error: %v", err)
}
if err := fh.Close(); err != nil {
t.Fatalf("Close() error: %v", err)
}
cmd := &cobra.Command{Use: "drive +import"}
cmd.Flags().String("file", "", "")
cmd.Flags().String("type", "", "")
cmd.Flags().String("folder-token", "", "")
cmd.Flags().String("name", "", "")
cmd.Flags().String("target-token", "", "")
if err := cmd.Flags().Set("file", "./large.xlsx"); err != nil {
t.Fatalf("set --file: %v", err)
}
if err := cmd.Flags().Set("type", "sheet"); err != nil {
t.Fatalf("set --type: %v", err)
}
runtime := common.TestNewRuntimeContextWithCtx(context.Background(), cmd, nil)
dry := DriveImport.DryRun(context.Background(), runtime)
if dry == nil {
t.Fatal("DryRun returned nil")
}
data, err := json.Marshal(dry)
if err != nil {
t.Fatalf("marshal dry run: %v", err)
}
var got struct {
API []struct {
Method string `json:"method"`
URL string `json:"url"`
} `json:"api"`
}
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("unmarshal dry run json: %v", err)
}
if len(got.API) != 5 {
t.Fatalf("expected 5 API calls, got %d", len(got.API))
}
if got.API[0].URL != "/open-apis/drive/v1/medias/upload_prepare" {
t.Fatalf("dry-run first URL = %q, want upload_prepare", got.API[0].URL)
}
if got.API[1].URL != "/open-apis/drive/v1/medias/upload_part" {
t.Fatalf("dry-run second URL = %q, want upload_part", got.API[1].URL)
}
if got.API[2].URL != "/open-apis/drive/v1/medias/upload_finish" {
t.Fatalf("dry-run third URL = %q, want upload_finish", got.API[2].URL)
}
}
func TestDriveImportDryRunReturnsErrorForUnsafePath(t *testing.T) {
t.Parallel()
cmd := &cobra.Command{Use: "drive +import"}
cmd.Flags().String("file", "", "")
cmd.Flags().String("type", "", "")
cmd.Flags().String("folder-token", "", "")
cmd.Flags().String("name", "", "")
cmd.Flags().String("target-token", "", "")
if err := cmd.Flags().Set("file", "../outside.md"); err != nil {
t.Fatalf("set --file: %v", err)
}
if err := cmd.Flags().Set("type", "docx"); err != nil {
t.Fatalf("set --type: %v", err)
}
runtime := common.TestNewRuntimeContext(cmd, nil)
dry := DriveImport.DryRun(context.Background(), runtime)
if dry == nil {
t.Fatal("DryRun returned nil")
}
data, err := json.Marshal(dry)
if err != nil {
t.Fatalf("marshal dry run: %v", err)
}
var got struct {
API []struct{} `json:"api"`
Error string `json:"error"`
}
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("unmarshal dry run json: %v", err)
}
if got.Error == "" || !strings.Contains(got.Error, "unsafe file path") {
t.Fatalf("dry-run error = %q, want unsafe file path error", got.Error)
}
if len(got.API) != 0 {
t.Fatalf("expected no API calls when preflight fails, got %d", len(got.API))
}
}
func TestDriveImportDryRunReturnsErrorForOversizedMarkdown(t *testing.T) {
tmpDir := t.TempDir()
withDriveWorkingDir(t, tmpDir)
fh, err := os.Create("large.md")
if err != nil {
t.Fatalf("Create() error: %v", err)
}
if err := fh.Truncate(driveImport20MBFileSizeLimit + 5*1024*1024); err != nil {
t.Fatalf("Truncate() error: %v", err)
}
if err := fh.Close(); err != nil {
t.Fatalf("Close() error: %v", err)
}
cmd := &cobra.Command{Use: "drive +import"}
cmd.Flags().String("file", "", "")
cmd.Flags().String("type", "", "")
cmd.Flags().String("folder-token", "", "")
cmd.Flags().String("name", "", "")
cmd.Flags().String("target-token", "", "")
if err := cmd.Flags().Set("file", "./large.md"); err != nil {
t.Fatalf("set --file: %v", err)
}
if err := cmd.Flags().Set("type", "docx"); err != nil {
t.Fatalf("set --type: %v", err)
}
runtime := common.TestNewRuntimeContextWithCtx(context.Background(), cmd, nil)
dry := DriveImport.DryRun(context.Background(), runtime)
if dry == nil {
t.Fatal("DryRun returned nil")
}
data, err := json.Marshal(dry)
if err != nil {
t.Fatalf("marshal dry run: %v", err)
}
var got struct {
API []struct{} `json:"api"`
Error string `json:"error"`
}
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("unmarshal dry run json: %v", err)
}
if got.Error == "" || !strings.Contains(got.Error, "exceeds 20.0 MB import limit for .md") {
t.Fatalf("dry-run error = %q, want oversized markdown error", got.Error)
}
if len(got.API) != 0 {
t.Fatalf("expected no API calls when size preflight fails, got %d", len(got.API))
}
}
func TestDriveImportDryRunReturnsErrorForDirectoryInput(t *testing.T) {
tmpDir := t.TempDir()
withDriveWorkingDir(t, tmpDir)
if err := os.Mkdir("folder-input", 0755); err != nil {
t.Fatalf("Mkdir() error: %v", err)
}
cmd := &cobra.Command{Use: "drive +import"}
cmd.Flags().String("file", "", "")
cmd.Flags().String("type", "", "")
cmd.Flags().String("folder-token", "", "")
cmd.Flags().String("name", "", "")
cmd.Flags().String("target-token", "", "")
if err := cmd.Flags().Set("file", "./folder-input"); err != nil {
t.Fatalf("set --file: %v", err)
}
if err := cmd.Flags().Set("type", "docx"); err != nil {
t.Fatalf("set --type: %v", err)
}
runtime := common.TestNewRuntimeContextWithCtx(context.Background(), cmd, nil)
dry := DriveImport.DryRun(context.Background(), runtime)
if dry == nil {
t.Fatal("DryRun returned nil")
}
data, err := json.Marshal(dry)
if err != nil {
t.Fatalf("marshal dry run: %v", err)
}
var got struct {
API []struct{} `json:"api"`
Error string `json:"error"`
}
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("unmarshal dry run json: %v", err)
}
if got.Error == "" || !strings.Contains(got.Error, "file must be a regular file") {
t.Fatalf("dry-run error = %q, want regular file error", got.Error)
}
if len(got.API) != 0 {
t.Fatalf("expected no API calls when file type preflight fails, got %d", len(got.API))
}
}
func TestDriveImportCreateTaskBodyKeepsEmptyMountKeyForRoot(t *testing.T) {
t.Parallel()
spec := driveImportSpec{
FilePath: "/tmp/README.md",
DocType: "docx",
}
body := spec.CreateTaskBody("file_token_test")
point, ok := body["point"].(map[string]interface{})
if !ok {
t.Fatalf("point = %#v, want map", body["point"])
}
raw, exists := point["mount_key"]
if !exists {
t.Fatal("mount_key missing; want empty string for root import")
}
got, ok := raw.(string)
if !ok {
t.Fatalf("mount_key type = %T, want string", raw)
}
if got != "" {
t.Fatalf("mount_key = %q, want empty string for root import", got)
}
spec.FolderToken = "fld_test"
body = spec.CreateTaskBody("file_token_test")
point, ok = body["point"].(map[string]interface{})
if !ok {
t.Fatalf("point = %#v, want map", body["point"])
}
if got, _ := point["mount_key"].(string); got != "fld_test" {
t.Fatalf("mount_key = %q, want %q", got, "fld_test")
}
}
func TestDriveImportCreateTaskBodyWithTargetToken(t *testing.T) {
t.Parallel()
spec := driveImportSpec{
FilePath: "/tmp/data.xlsx",
DocType: "bitable",
TargetToken: "bascnxxxxx",
}
body := spec.CreateTaskBody("file_token_test")
// point stays the same as default (mount_type=1)
point, ok := body["point"].(map[string]interface{})
if !ok {
t.Fatalf("point = %#v, want map", body["point"])
}
if mt := point["mount_type"]; mt != float64(1) && mt != 1 {
t.Fatalf("mount_type = %v (%T), want 1", mt, mt)
}
// token is injected at body top-level
if tt, _ := body["token"].(string); tt != "bascnxxxxx" {
t.Fatalf("token = %q, want %q", tt, "bascnxxxxx")
}
}
func TestDriveImportCreateTaskBodyTargetTokenIgnoredForNonBitable(t *testing.T) {
t.Parallel()
spec := driveImportSpec{
FilePath: "/tmp/data.xlsx",
DocType: "sheet",
TargetToken: "bascnxxxxx",
FolderToken: "fld_test",
}
body := spec.CreateTaskBody("file_token_test")
point, ok := body["point"].(map[string]interface{})
if !ok {
t.Fatalf("point = %#v, want map", body["point"])
}
// Non-bitable should use default folder mount (type=1), ignoring TargetToken
if mt := point["mount_type"]; mt != float64(1) && mt != 1 {
t.Fatalf("mount_type = %v (%T), want 1 (folder mount)", mt, mt)
}
if _, exists := point["target_token"]; exists {
t.Fatal("target_token should not be present for non-bitable type")
}
}
func TestDriveImportDryRunWithTargetToken(t *testing.T) {
tmpDir := t.TempDir()
withDriveWorkingDir(t, tmpDir)
if err := os.WriteFile("data.xlsx", []byte("fake-xlsx"), 0644); err != nil {
t.Fatalf("WriteFile() error: %v", err)
}
cmd := &cobra.Command{Use: "drive +import"}
cmd.Flags().String("file", "", "")
cmd.Flags().String("type", "", "")
cmd.Flags().String("folder-token", "", "")
cmd.Flags().String("name", "", "")
cmd.Flags().String("target-token", "", "")
if err := cmd.Flags().Set("file", "./data.xlsx"); err != nil {
t.Fatalf("set --file: %v", err)
}
if err := cmd.Flags().Set("type", "bitable"); err != nil {
t.Fatalf("set --type: %v", err)
}
if err := cmd.Flags().Set("target-token", "bascntarget123"); err != nil {
t.Fatalf("set --target-token: %v", err)
}
runtime := common.TestNewRuntimeContextWithCtx(context.Background(), cmd, nil)
dry := DriveImport.DryRun(context.Background(), runtime)
if dry == nil {
t.Fatal("DryRun returned nil")
}
data, err := json.Marshal(dry)
if err != nil {
t.Fatalf("marshal dry run: %v", err)
}
var got struct {
API []struct {
URL string `json:"url"`
Body map[string]interface{} `json:"body"`
} `json:"api"`
}
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("unmarshal dry run json: %v", err)
}
if len(got.API) != 3 {
t.Fatalf("expected 3 API calls, got %d", len(got.API))
}
// The import task body (API[1]) should contain target_token in point
importTaskBody := got.API[1].Body
point, ok := importTaskBody["point"].(map[string]interface{})
if !ok {
t.Fatalf("point = %#v, want map", importTaskBody["point"])
}
if mt := point["mount_type"]; mt != float64(1) && mt != 1 {
t.Fatalf("dry-run mount_type = %v (%T), want 1 (unchanged)", mt, mt)
}
if tt, _ := importTaskBody["token"].(string); tt != "bascntarget123" {
t.Fatalf("dry-run token = %q, want %q", tt, "bascntarget123")
}
}
func TestDriveImportDryRunTargetTokenRejectedForSheet(t *testing.T) {
tmpDir := t.TempDir()
withDriveWorkingDir(t, tmpDir)
if err := os.WriteFile("data.xlsx", []byte("fake-xlsx"), 0644); err != nil {
t.Fatalf("WriteFile() error: %v", err)
}
cmd := &cobra.Command{Use: "drive +import"}
cmd.Flags().String("file", "", "")
cmd.Flags().String("type", "", "")
cmd.Flags().String("folder-token", "", "")
cmd.Flags().String("name", "", "")
cmd.Flags().String("target-token", "", "")
if err := cmd.Flags().Set("file", "./data.xlsx"); err != nil {
t.Fatalf("set --file: %v", err)
}
if err := cmd.Flags().Set("type", "sheet"); err != nil {
t.Fatalf("set --type: %v", err)
}
if err := cmd.Flags().Set("target-token", "bascnxxx"); err != nil {
t.Fatalf("set --target-token: %v", err)
}
runtime := common.TestNewRuntimeContextWithCtx(context.Background(), cmd, nil)
dry := DriveImport.DryRun(context.Background(), runtime)
if dry == nil {
t.Fatal("DryRun returned nil")
}
data, err := json.Marshal(dry)
if err != nil {
t.Fatalf("marshal dry run: %v", err)
}
var got struct {
Error string `json:"error"`
}
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if got.Error == "" || !strings.Contains(got.Error, "--target-token is only supported when --type is bitable") {
t.Fatalf("dry-run error = %q, want target-token validation error", got.Error)
}
}
// driveImportMockEnv mounts the three stubs needed for a full +import run:
// media upload_all -> import_tasks (create) -> import_tasks/<ticket> (poll).
// Returns nothing; caller asserts on stdout via decodeDriveEnvelope.
func driveImportMockEnv(t *testing.T, reg *httpmock.Registry, ticket string, pollData map[string]interface{}) {
t.Helper()
reg.Register(&httpmock.Stub{
Method: "POST",
URL: "/open-apis/drive/v1/medias/upload_all",
Body: map[string]interface{}{
"code": 0, "msg": "ok",
"data": map[string]interface{}{"file_token": "file_import_media"},
},
})
reg.Register(&httpmock.Stub{
Method: "POST",
URL: "/open-apis/drive/v1/import_tasks",
Body: map[string]interface{}{
"code": 0, "msg": "ok",
"data": map[string]interface{}{"ticket": ticket},
},
})
reg.Register(&httpmock.Stub{
Method: "GET",
URL: "/open-apis/drive/v1/import_tasks/" + ticket,
Body: map[string]interface{}{
"code": 0, "msg": "ok",
"data": map[string]interface{}{"result": pollData},
},
})
}
// driveImportTestConfig builds a CliConfig for the import fallback tests.
// The brand defaults to BrandFeishu when omitted; pass core.BrandLark to
// exercise the larksuite.com branch of BuildResourceURL.
func driveImportTestConfig(suffix string, brands ...core.LarkBrand) *core.CliConfig {
brand := core.BrandFeishu
if len(brands) > 0 {
brand = brands[0]
}
return &core.CliConfig{
AppID: "drive-import-fallback-" + suffix,
AppSecret: "test-secret",
Brand: brand,
}
}
func TestDriveImportFallbackURLWhenBackendOmitsIt(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, driveImportTestConfig("missing-url"))
driveImportMockEnv(t, reg, "ticket_fallback", map[string]interface{}{
"token": "doxcn_imported",
"type": "docx",
"job_status": float64(0),
// "url" deliberately omitted: import API frequently returns the doc
// without an absolute URL, leaving the CLI to backfill from token.
})
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Chdir: %v", err)
}
defer os.Chdir(origDir)
if err := os.WriteFile("notes.md", []byte("# Hi"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := mountAndRunDrive(t, DriveImport, []string{
"+import", "--file", "notes.md", "--type", "docx", "--as", "user",
}, f, stdout); err != nil {
t.Fatalf("import should succeed, got: %v", err)
}
data := decodeDriveEnvelope(t, stdout)
if got, want := data["url"], "https://www.feishu.cn/docx/doxcn_imported"; got != want {
t.Fatalf("data.url = %#v, want %q (brand-standard fallback)", got, want)
}
}
func TestDriveImportPreservesBackendURL(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, driveImportTestConfig("preserve-url"))
driveImportMockEnv(t, reg, "ticket_preserve", map[string]interface{}{
"token": "doxcn_imported",
"type": "docx",
"job_status": float64(0),
"url": "https://tenant.larkoffice.com/docx/doxcn_imported",
})
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Chdir: %v", err)
}
defer os.Chdir(origDir)
if err := os.WriteFile("notes.md", []byte("# Hi"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := mountAndRunDrive(t, DriveImport, []string{
"+import", "--file", "notes.md", "--type", "docx", "--as", "user",
}, f, stdout); err != nil {
t.Fatalf("import should succeed, got: %v", err)
}
data := decodeDriveEnvelope(t, stdout)
if got, want := data["url"], "https://tenant.larkoffice.com/docx/doxcn_imported"; got != want {
t.Fatalf("data.url = %#v, want backend tenant URL %q (fallback must not overwrite)", got, want)
}
}
func TestDriveImportFallbackURLWhenServerURLIsWhitespace(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, driveImportTestConfig("whitespace-url"))
driveImportMockEnv(t, reg, "ticket_whitespace", map[string]interface{}{
"token": "doxcn_imported",
"type": "docx",
"job_status": float64(0),
"url": " ", // whitespace-only must trigger fallback, not pass through.
})
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Chdir: %v", err)
}
defer os.Chdir(origDir)
if err := os.WriteFile("notes.md", []byte("# Hi"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := mountAndRunDrive(t, DriveImport, []string{
"+import", "--file", "notes.md", "--type", "docx", "--as", "user",
}, f, stdout); err != nil {
t.Fatalf("import should succeed, got: %v", err)
}
data := decodeDriveEnvelope(t, stdout)
if got, want := data["url"], "https://www.feishu.cn/docx/doxcn_imported"; got != want {
t.Fatalf("data.url = %#v, want %q (whitespace-only backend URL must yield fallback)", got, want)
}
}
func TestDriveImportFallbackURLForLarkBrand(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, driveImportTestConfig("lark-brand", core.BrandLark))
driveImportMockEnv(t, reg, "ticket_lark", map[string]interface{}{
"token": "doxcn_imported",
"type": "docx",
"job_status": float64(0),
// "url" omitted to force the fallback through the lark host branch.
})
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Chdir: %v", err)
}
defer os.Chdir(origDir)
if err := os.WriteFile("notes.md", []byte("# Hi"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := mountAndRunDrive(t, DriveImport, []string{
"+import", "--file", "notes.md", "--type", "docx", "--as", "user",
}, f, stdout); err != nil {
t.Fatalf("import should succeed, got: %v", err)
}
data := decodeDriveEnvelope(t, stdout)
if got, want := data["url"], "https://www.larksuite.com/docx/doxcn_imported"; got != want {
t.Fatalf("data.url = %#v, want %q (lark brand fallback)", got, want)
}
}
func TestDriveImportFallbackURLWhenServerTypeIsAlias(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, driveImportTestConfig("alias-type"))
driveImportMockEnv(t, reg, "ticket_alias", map[string]interface{}{
"token": "shtcn_imported",
"type": "sheets", // non-canonical alias the server may return
"job_status": float64(0),
})
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Chdir: %v", err)
}
defer os.Chdir(origDir)
if err := os.WriteFile("data.csv", []byte("a,b\n1,2\n"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := mountAndRunDrive(t, DriveImport, []string{
"+import", "--file", "data.csv", "--type", "sheet", "--as", "user",
}, f, stdout); err != nil {
t.Fatalf("import should succeed, got: %v", err)
}
data := decodeDriveEnvelope(t, stdout)
// Server returned "sheets" (alias) — normalize falls back to the user
// --type "sheet", so BuildResourceURL picks the canonical /sheets/ path.
if got, want := data["url"], "https://www.feishu.cn/sheets/shtcn_imported"; got != want {
t.Fatalf("data.url = %#v, want %q (alias normalized via spec.DocType fallback)", got, want)
}
}
func TestDriveImportFallbackURLForSlides(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, driveImportTestConfig("slides"))
driveImportMockEnv(t, reg, "ticket_slides", map[string]interface{}{
"token": "sldcn_imported",
"type": "slides",
"job_status": float64(0),
})
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Chdir: %v", err)
}
defer os.Chdir(origDir)
if err := os.WriteFile("deck.pptx", []byte("fake-pptx"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := mountAndRunDrive(t, DriveImport, []string{
"+import", "--file", "deck.pptx", "--type", "slides", "--as", "user",
}, f, stdout); err != nil {
t.Fatalf("import should succeed, got: %v", err)
}
data := decodeDriveEnvelope(t, stdout)
if got, want := data["url"], "https://www.feishu.cn/slides/sldcn_imported"; got != want {
t.Fatalf("data.url = %#v, want %q (slides fallback)", got, want)
}
}