-
Notifications
You must be signed in to change notification settings - Fork 413
test: add mail and wiki shortcut e2e coverage #296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // Copyright (c) 2026 Lark Technologies Pte. Ltd. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package base | ||
|
|
||
| import ( | ||
| "context" | ||
| "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 TestBase_CoreWorkflow(t *testing.T) { | ||
| ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) | ||
| t.Cleanup(cancel) | ||
|
|
||
| baseToken := createBase(t, ctx, uniqueName("lark-cli-e2e-base")) | ||
|
|
||
| t.Run("get base", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+base-get", "--base-token", baseToken}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot base get capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.NotEmpty(t, gjson.Get(result.Stdout, "data.base.name").String(), "stdout:\n%s", result.Stdout) | ||
| }) | ||
|
|
||
| t.Run("copy base", func(t *testing.T) { | ||
| copiedToken := copyBase(t, ctx, baseToken, uniqueName("lark-cli-e2e-base-copy")) | ||
| assert.NotEqual(t, baseToken, copiedToken) | ||
| }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,265 @@ | ||
| // Copyright (c) 2026 Lark Technologies Pte. Ltd. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package base | ||
|
|
||
| import ( | ||
| "context" | ||
| "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 TestBase_DashboardWorkflow(t *testing.T) { | ||
| parentT := t | ||
| ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute) | ||
| t.Cleanup(cancel) | ||
|
Comment on lines
+17
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isolate the CLI config in both E2E entrypoints. Both tests shell out to the real CLI without sandboxing Suggested fix func TestBase_DashboardWorkflow(t *testing.T) {
+ t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
t.Cleanup(cancel)
@@
func TestBase_FormWorkflow(t *testing.T) {
+ t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
t.Cleanup(cancel)Also applies to: 142-145 🤖 Prompt for AI Agents |
||
|
|
||
| baseToken := createBase(t, ctx, uniqueName("lark-cli-e2e-base-dashboard")) | ||
| tableID, _, _ := createTable(t, parentT, ctx, baseToken, uniqueName("DashboardTable"), `[{"name":"Amount","type":"number"}]`, "") | ||
| dashboardID := createDashboard(t, parentT, ctx, baseToken, uniqueName("Sales Dashboard")) | ||
| blockID := createBlock(t, parentT, ctx, baseToken, dashboardID, "Amount Stats", "statistics", `{"table_name":"DashboardTable","series":[{"field_name":"Amount","rollup":"sum"}],"count_all":true}`) | ||
|
Comment on lines
+22
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the actual generated table name in dashboard block config. The table is created with Suggested fix- tableID, _, _ := createTable(t, parentT, ctx, baseToken, uniqueName("DashboardTable"), `[{"name":"Amount","type":"number"}]`, "")
- blockID := createBlock(t, parentT, ctx, baseToken, dashboardID, "Amount Stats", "statistics", `{"table_name":"DashboardTable","series":[{"field_name":"Amount","rollup":"sum"}],"count_all":true}`)
+ tableName := uniqueName("DashboardTable")
+ _, _, _ := createTable(t, parentT, ctx, baseToken, tableName, `[{"name":"Amount","type":"number"}]`, "")
+ blockID := createBlock(t, parentT, ctx, baseToken, dashboardID, "Amount Stats", "statistics", `{"table_name":"`+tableName+`","series":[{"field_name":"Amount","rollup":"sum"}],"count_all":true}`)
@@
- Args: []string{"base", "+dashboard-block-update", "--base-token", baseToken, "--dashboard-id", dashboardID, "--block-id", blockID, "--name", "Amount Stats Updated", "--data-config", `{"table_name":"DashboardTable","series":[{"field_name":"Amount","rollup":"SUM"}],"count_all":true}`},
+ Args: []string{"base", "+dashboard-block-update", "--base-token", baseToken, "--dashboard-id", dashboardID, "--block-id", blockID, "--name", "Amount Stats Updated", "--data-config", `{"table_name":"`+tableName+`","series":[{"field_name":"Amount","rollup":"SUM"}],"count_all":true}`},
@@
- _ = tableIDAlso applies to: 97-99, 139-139 🤖 Prompt for AI Agents |
||
|
|
||
| t.Run("dashboard list", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+dashboard-list", "--base-token", baseToken}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot dashboard list capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.True(t, gjson.Get(result.Stdout, "data.items.#(dashboard_id==\""+dashboardID+"\")").Exists(), "stdout:\n%s", result.Stdout) | ||
| }) | ||
|
|
||
| t.Run("dashboard get", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+dashboard-get", "--base-token", baseToken, "--dashboard-id", dashboardID}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot dashboard get capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, dashboardID, gjson.Get(result.Stdout, "data.dashboard.dashboard_id").String()) | ||
| }) | ||
|
|
||
| t.Run("dashboard update", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+dashboard-update", "--base-token", baseToken, "--dashboard-id", dashboardID, "--name", "Sales Dashboard Updated", "--theme-style", "SimpleBlue"}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot dashboard update capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, "Sales Dashboard Updated", gjson.Get(result.Stdout, "data.dashboard.name").String()) | ||
| }) | ||
|
|
||
| t.Run("dashboard block list", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+dashboard-block-list", "--base-token", baseToken, "--dashboard-id", dashboardID}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot dashboard block list capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.True(t, gjson.Get(result.Stdout, "data.items.#(block_id==\""+blockID+"\")").Exists(), "stdout:\n%s", result.Stdout) | ||
| }) | ||
|
|
||
| t.Run("dashboard block get", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+dashboard-block-get", "--base-token", baseToken, "--dashboard-id", dashboardID, "--block-id", blockID}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot dashboard block get capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, blockID, gjson.Get(result.Stdout, "data.block.block_id").String()) | ||
| }) | ||
|
|
||
| t.Run("dashboard block update", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+dashboard-block-update", "--base-token", baseToken, "--dashboard-id", dashboardID, "--block-id", blockID, "--name", "Amount Stats Updated", "--data-config", `{"table_name":"DashboardTable","series":[{"field_name":"Amount","rollup":"SUM"}],"count_all":true}`}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot dashboard block update capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, "Amount Stats Updated", gjson.Get(result.Stdout, "data.block.name").String()) | ||
| }) | ||
|
|
||
| t.Run("dashboard block delete", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+dashboard-block-delete", "--base-token", baseToken, "--dashboard-id", dashboardID, "--block-id", blockID, "--yes"}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot dashboard block delete capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, blockID, gjson.Get(result.Stdout, "data.block_id").String()) | ||
| }) | ||
|
|
||
| t.Run("dashboard delete", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+dashboard-delete", "--base-token", baseToken, "--dashboard-id", dashboardID, "--yes"}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot dashboard delete capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, dashboardID, gjson.Get(result.Stdout, "data.dashboard_id").String()) | ||
| }) | ||
|
|
||
| _ = tableID | ||
| } | ||
|
|
||
| func TestBase_FormWorkflow(t *testing.T) { | ||
| parentT := t | ||
| ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute) | ||
| t.Cleanup(cancel) | ||
|
|
||
| baseToken := createBase(t, ctx, uniqueName("lark-cli-e2e-base-form")) | ||
| tableID, _, _ := createTable(t, parentT, ctx, baseToken, uniqueName("FormTable"), `[{"name":"Name","type":"text"}]`, "") | ||
| formID := createForm(t, parentT, ctx, baseToken, tableID, uniqueName("Survey")) | ||
|
|
||
| var questionID string | ||
|
|
||
| t.Run("form get", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+form-get", "--base-token", baseToken, "--table-id", tableID, "--form-id", formID}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot form get capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, formID, gjson.Get(result.Stdout, "data.id").String()) | ||
| }) | ||
|
|
||
| t.Run("form list", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+form-list", "--base-token", baseToken, "--table-id", tableID}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot form list capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.True(t, gjson.Get(result.Stdout, "data.forms.#(id==\""+formID+"\")").Exists(), "stdout:\n%s", result.Stdout) | ||
| }) | ||
|
|
||
| t.Run("form update", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+form-update", "--base-token", baseToken, "--table-id", tableID, "--form-id", formID, "--name", "Survey Updated", "--description", "updated description"}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot form update capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, "Survey Updated", gjson.Get(result.Stdout, "data.name").String()) | ||
| }) | ||
|
|
||
| t.Run("form questions create", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+form-questions-create", "--base-token", baseToken, "--table-id", tableID, "--form-id", formID, "--questions", `[{"type":"text","title":"Your Name","required":true}]`}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot form question create capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| questionID = gjson.Get(result.Stdout, "data.questions.0.id").String() | ||
| require.NotEmpty(t, questionID, "stdout:\n%s", result.Stdout) | ||
| }) | ||
|
|
||
| t.Run("form questions list", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+form-questions-list", "--base-token", baseToken, "--table-id", tableID, "--form-id", formID}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot form question list capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.True(t, gjson.Get(result.Stdout, "data.questions.#(id==\""+questionID+"\")").Exists(), "stdout:\n%s", result.Stdout) | ||
| }) | ||
|
|
||
| t.Run("form questions update", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+form-questions-update", "--base-token", baseToken, "--table-id", tableID, "--form-id", formID, "--questions", `[{"id":"` + questionID + `","title":"Your Name Updated","required":true}]`}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot form question update capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, "Your Name Updated", gjson.Get(result.Stdout, "data.questions.0.title").String()) | ||
| }) | ||
|
|
||
| t.Run("form questions delete", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+form-questions-delete", "--base-token", baseToken, "--table-id", tableID, "--form-id", formID, "--question-ids", `["` + questionID + `"]`}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot form question delete capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, true, gjson.Get(result.Stdout, "data.deleted").Bool(), "stdout:\n%s", result.Stdout) | ||
| }) | ||
|
|
||
| t.Run("form delete", func(t *testing.T) { | ||
| result, err := clie2e.RunCmd(ctx, clie2e.Request{ | ||
| Args: []string{"base", "+form-delete", "--base-token", baseToken, "--table-id", tableID, "--form-id", formID, "--yes"}, | ||
| DefaultAs: "bot", | ||
| }) | ||
| require.NoError(t, err) | ||
| if result.ExitCode != 0 { | ||
| skipIfBaseUnavailable(t, result, "requires bot form delete capability") | ||
| } | ||
| result.AssertExitCode(t, 0) | ||
| result.AssertStdoutStatus(t, true) | ||
| assert.Equal(t, true, gjson.Get(result.Stdout, "data.deleted").Bool(), "stdout:\n%s", result.Stdout) | ||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isolate the CLI config for this E2E test.
This shells out to the real CLI without sandboxing
LARKSUITE_CLI_CONFIG_DIR, so it can read/write shared local state and cross-contaminate other runs.Suggested fix
func TestBase_CoreWorkflow(t *testing.T) { + t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) t.Cleanup(cancel)📝 Committable suggestion
🤖 Prompt for AI Agents