// Copyright (c) 2026 Lark Technologies Pte. Ltd. // SPDX-License-Identifier: MIT package markdown import ( "context" "os" "strings" "testing" "time" clie2e "github.com/larksuite/cli/tests/cli_e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" ) func TestMarkdownCreateDryRun_Content(t *testing.T) { setMarkdownDryRunConfigEnv(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+create", "--name", "README.md", "--content", "# hello", "--folder-token", "fldcnMarkdownDryRun", "--dry-run", }, DefaultAs: "bot", }) require.NoError(t, err) result.AssertExitCode(t, 0) output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") assert.Contains(t, output, `"with_url": true`) assert.Contains(t, output, `"file_name": "README.md"`) assert.Contains(t, output, `"parent_node": "fldcnMarkdownDryRun"`) assert.Contains(t, output, `"parent_type": "explorer"`) assert.Contains(t, output, `"size": 7`) } func TestMarkdownCreateDryRun_WikiTarget(t *testing.T) { setMarkdownDryRunConfigEnv(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+create", "--name", "README.md", "--content", "# hello", "--wiki-token", "wikcnMarkdownDryRun", "--dry-run", }, DefaultAs: "bot", }) require.NoError(t, err) result.AssertExitCode(t, 0) output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") assert.Contains(t, output, `"with_url": true`) assert.Contains(t, output, `"file_name": "README.md"`) assert.Contains(t, output, `"parent_node": "wikcnMarkdownDryRun"`) assert.Contains(t, output, `"parent_type": "wiki"`) assert.Contains(t, output, `"size": 7`) } func TestMarkdownCreateDryRun_FileShowsConcreteSize(t *testing.T) { setMarkdownDryRunConfigEnv(t) dir := t.TempDir() content := "# hi\n" require.NoError(t, os.WriteFile(dir+"/note.md", []byte(content), 0o644)) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+create", "--file", "note.md", "--dry-run", }, DefaultAs: "bot", WorkDir: dir, }) require.NoError(t, err) result.AssertExitCode(t, 0) output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") assert.Contains(t, output, `"with_url": true`) assert.Contains(t, output, `"file": "@note.md"`) assert.Contains(t, output, `"size": 5`) } func TestMarkdownCreateDryRun_RejectsEmptyContent(t *testing.T) { setMarkdownDryRunConfigEnv(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+create", "--name", "empty.md", "--content", "", "--dry-run", }, DefaultAs: "bot", }) require.NoError(t, err) result.AssertExitCode(t, 0) if api := gjson.Get(result.Stdout, "api"); api.IsArray() && len(api.Array()) > 0 { t.Fatalf("dry-run api list must be empty when validation fails\nstdout:\n%s", result.Stdout) } errMsg := gjson.Get(result.Stdout, "error").String() assert.Contains(t, errMsg, "empty markdown content is not supported") } func TestMarkdownDiffDryRun_RemoteVsRemote(t *testing.T) { setMarkdownDryRunConfigEnv(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+diff", "--file-token", "boxcnMarkdownDryRun", "--from-version", "7633658129540910621", "--to-version", "7633658129540910628", "--context-lines", "1", "--dry-run", }, DefaultAs: "bot", }) require.NoError(t, err) result.AssertExitCode(t, 0) output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/boxcnMarkdownDryRun/download") assert.Contains(t, output, `"mode": "remote_vs_remote"`) assert.Contains(t, output, `"version": "7633658129540910621"`) assert.Contains(t, output, `"version": "7633658129540910628"`) assert.Contains(t, output, `"context_lines": 1`) } func TestMarkdownDiffDryRun_RemoteVsLocal(t *testing.T) { setMarkdownDryRunConfigEnv(t) dir := t.TempDir() require.NoError(t, os.WriteFile(dir+"/draft.md", []byte("# draft\n"), 0o644)) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+diff", "--file-token", "boxcnMarkdownDryRun", "--file", "./draft.md", "--dry-run", }, DefaultAs: "bot", WorkDir: dir, }) require.NoError(t, err) result.AssertExitCode(t, 0) output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/boxcnMarkdownDryRun/download") assert.Contains(t, output, `"mode": "remote_vs_local"`) assert.Contains(t, output, `"local_file": "./draft.md"`) } func TestMarkdownCreateDryRun_RejectsEmptyWikiToken(t *testing.T) { setMarkdownDryRunConfigEnv(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+create", "--name", "README.md", "--content", "# hello", "--wiki-token", "", "--dry-run", }, DefaultAs: "bot", }) require.NoError(t, err) result.AssertExitCode(t, 2) assert.Contains(t, result.Stdout+result.Stderr, "--wiki-token cannot be empty") } func TestMarkdownFetchDryRun_OutputFile(t *testing.T) { setMarkdownDryRunConfigEnv(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+fetch", "--file-token", "boxcnMarkdownDryRun", "--output", "./copy.md", "--dry-run", }, DefaultAs: "bot", }) require.NoError(t, err) result.AssertExitCode(t, 0) output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/boxcnMarkdownDryRun/download") assert.Contains(t, output, `"output": "./copy.md"`) } func TestMarkdownOverwriteDryRun_ContentFile(t *testing.T) { setMarkdownDryRunConfigEnv(t) dir := t.TempDir() content := "# overwrite test\n" require.NoError(t, os.WriteFile(dir+"/input.md", []byte(content), 0o644)) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+overwrite", "--file-token", "boxcnMarkdownDryRun", "--content", "@input.md", "--dry-run", }, DefaultAs: "bot", WorkDir: dir, }) require.NoError(t, err) result.AssertExitCode(t, 0) output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") assert.Contains(t, output, `"file_token": "boxcnMarkdownDryRun"`) assert.Contains(t, output, `"size": 17`) } func TestMarkdownOverwriteDryRun_RejectsEmptyFile(t *testing.T) { setMarkdownDryRunConfigEnv(t) dir := t.TempDir() require.NoError(t, os.WriteFile(dir+"/empty.md", []byte{}, 0o644)) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+overwrite", "--file-token", "boxcnMarkdownDryRun", "--file", "empty.md", "--dry-run", }, DefaultAs: "bot", WorkDir: dir, }) require.NoError(t, err) result.AssertExitCode(t, 0) if api := gjson.Get(result.Stdout, "api"); api.IsArray() && len(api.Array()) > 0 { t.Fatalf("dry-run api list must be empty when validation fails\nstdout:\n%s", result.Stdout) } errMsg := gjson.Get(result.Stdout, "error").String() assert.Contains(t, errMsg, "empty markdown content is not supported") } func TestMarkdownPatchDryRun_Content(t *testing.T) { setMarkdownDryRunConfigEnv(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) result, err := clie2e.RunCmd(ctx, clie2e.Request{ Args: []string{ "markdown", "+patch", "--file-token", "boxcnMarkdownDryRun", "--pattern", "TODO", "--content", "DONE", "--dry-run", }, DefaultAs: "bot", }) require.NoError(t, err) result.AssertExitCode(t, 0) output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/boxcnMarkdownDryRun/download") assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") assert.Contains(t, output, "/open-apis/drive/v1/files/upload_prepare") assert.Contains(t, output, "/open-apis/drive/v1/files/upload_part") assert.Contains(t, output, "/open-apis/drive/v1/files/upload_finish") } func setMarkdownDryRunConfigEnv(t *testing.T) { t.Helper() t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) t.Setenv("LARKSUITE_CLI_APP_ID", "markdown_dryrun_test") t.Setenv("LARKSUITE_CLI_APP_SECRET", "markdown_dryrun_secret") t.Setenv("LARKSUITE_CLI_BRAND", "feishu") }