Skip to content

test: add mail and wiki shortcut e2e coverage#296

Open
yxzhaao wants to merge 3 commits intolarksuite:mainfrom
yxzhaao:feat/cli-shortcut-e2e-tests
Open

test: add mail and wiki shortcut e2e coverage#296
yxzhaao wants to merge 3 commits intolarksuite:mainfrom
yxzhaao:feat/cli-shortcut-e2e-tests

Conversation

@yxzhaao
Copy link
Copy Markdown

@yxzhaao yxzhaao commented Apr 7, 2026

Change-Id: I43922a6cce5a671e842e48e57e8fb77aae738647

Summary

Changes

  • Change 1
  • Change 2

Test Plan

  • Unit tests pass
  • Manual local verification confirms the lark xxx command works as expected

Related Issues

  • None

Summary by CodeRabbit

  • Tests
    • Added comprehensive end-to-end test coverage for Wiki and Base workflows, including node creation, space management, dashboard operations, form management, role configurations, and table/field/record/view operations.

Change-Id: I43922a6cce5a671e842e48e57e8fb77aae738647
@github-actions github-actions bot added the size/S Low-risk docs, CI, test, or chore only changes label Apr 7, 2026
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


zhao.yuxuan seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@yxzhaao yxzhaao force-pushed the feat/cli-shortcut-e2e-tests branch from 54933f4 to dccdb71 Compare April 7, 2026 12:22
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

Comprehensive end-to-end test suite additions for CLI wiki and base functionality, including helper utilities and workflow test cases covering node creation, table/record management, dashboard/form operations, and role/permission scenarios across multiple feature domains.

Changes

Cohort / File(s) Summary
Wiki E2E Tests
tests/cli_e2e/wiki/helpers_test.go, tests/cli_e2e/wiki/wiki_workflow_test.go
Added helper function createWikiNode to create wiki nodes via CLI and new comprehensive workflow test TestWiki_Workflow_Bot covering node creation, retrieval, space listing, node listing/copying, and validation of JSON responses.
Base E2E Test Helpers
tests/cli_e2e/base/helpers_test.go
Added reusable helper functions for base E2E testing including resource creation utilities (createBase, createTable, createField, createRecord, createView, createDashboard, createBlock, createForm, createRole, createWorkflow) with automatic cleanup registration, JSON payload extraction, and capability-based test skipping.
Base Core Workflow Test
tests/cli_e2e/base/base_core_workflow_test.go
New test TestBase_CoreWorkflow exercising base retrieval and copying operations via CLI with JSON validation.
Base Advanced Feature Workflows
tests/cli_e2e/base/base_dashboard_form_workflow_test.go, tests/cli_e2e/base/base_role_workflow_workflow_test.go, tests/cli_e2e/base/base_table_record_view_workflow_test.go
Added three comprehensive workflow tests covering dashboard/block operations, form management and questions, role/advanced permission/workflow operations, and extensive table/field/record/view management with create, update, delete, and configuration operations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hopping through workflows, tests multiply,
Wiki nodes dance, dashboards reach high,
Tables and forms in harmonious test,
CLI commands put to the quest,
A burrow of tests, thoroughly blessed!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description uses the required template structure but is incomplete. The Summary and Changes sections contain only placeholder text ('Change 1', 'Change 2') without actual content describing the modifications. Fill in the Summary section with a brief description of the motivation and scope. Replace placeholder Changes with actual file changes (e.g., added wiki helpers, added wiki workflow test, added base E2E tests and helpers).
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'test: add mail and wiki shortcut e2e coverage' accurately describes the main changes: adding E2E test coverage for wiki workflows and base functionality, though it mentions 'mail' which is not evident in the file changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 7, 2026

Greptile Summary

This PR adds E2E test coverage for the base and wiki CLI surfaces: four new base workflow test files (core, dashboard/form, role/workflow, table/field/record/view) plus a new wiki node workflow test. The base tests are well-structured — every helper registers a parentT.Cleanup to delete created resources after the test. The wiki helper (createWikiNode) does not follow this pattern and leaves orphaned nodes in the bot's wiki library after every test run.

Confidence Score: 5/5

Safe to merge — all findings are P2 style issues that do not affect test correctness.

The only actionable issue (missing wiki node cleanup) is a best-practice concern that does not break CI or produce incorrect results; it merely leaves test artifacts in the wiki library. All remaining findings are P2 or lower.

tests/cli_e2e/wiki/helpers_test.go — createWikiNode should register cleanup analogous to the base helpers.

Important Files Changed

Filename Overview
tests/cli_e2e/wiki/helpers_test.go New wiki helper — createWikiNode creates wiki nodes but registers no cleanup, leaving orphaned nodes after every test run.
tests/cli_e2e/wiki/wiki_workflow_test.go New E2E test covering wiki node create/get/copy/list workflow; no cleanup for created nodes.
tests/cli_e2e/base/helpers_test.go New base helpers with well-structured parentT.Cleanup registered for all created resources.
tests/cli_e2e/base/base_core_workflow_test.go New E2E test for base get and copy operations; follows established patterns.
tests/cli_e2e/base/base_dashboard_form_workflow_test.go New E2E tests for dashboard/form/block CRUD with proper skip guards and cleanup.
tests/cli_e2e/base/base_role_workflow_workflow_test.go New E2E tests for role and workflow operations with permission-skip guards.
tests/cli_e2e/base/base_table_record_view_workflow_test.go New E2E tests for table/field/record/view CRUD; well-structured with parentT cleanup for all resources.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[TestWiki_Workflow_Bot] --> B[createWikiNode\ncreate node in my_library]
    B --> C{node created?}
    C -- yes --> D[get created node\nwiki spaces get_node]
    D --> E[get space\nwiki spaces get]
    E --> F[list spaces\nwiki spaces list]
    F --> G[list nodes\nwiki nodes list]
    G --> H[createWikiNode\ncopy node]
    H --> I[list nodes\nfind copied node]
    C -- no --> Z[t.FailNow]
    style B fill:#f9a,stroke:#c00
    style H fill:#f9a,stroke:#c00
    note1["⚠ No cleanup registered\nfor created/copied nodes"]
Loading

Greploops — Automatically fix all review issues by running /greploops in Claude Code. It iterates: fix, push, re-review, repeat until 5/5 confidence.
Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal.

Reviews (2): Last reviewed commit: "test: add base shortcut e2e coverage" | Re-trigger Greptile

result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, 0)

assert.True(t, gjson.Get(result.Stdout, "data.page_token").Exists(), "stdout:\n%s", result.Stdout)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 page_token assertion may be flaky

page_token is only included in the API response when there are additional pages beyond the current result set. With page_size: 1, if the bot can access only a single wiki space, the API will return everything in one page with no page_token field, causing this assertion to fail. The data.items check on line 107 already confirms the list returned data.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
tests/cli_e2e/mail/helpers_test.go (2)

23-31: JSON extraction assumes payload extends to end of string.

The logic finds the start of JSON via \n{ or {, then takes raw[start:] to the end. If there's trailing non-JSON content after the closing }, gjson.Valid would fail. This works for current CLI output but is fragile.

For robustness, consider matching balanced braces or documenting that CLI output must end with the JSON payload.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/mail/helpers_test.go` around lines 23 - 31, The test currently
takes payload := raw[start:] which assumes JSON runs to EOF; change this to
locate the end of the JSON by scanning from start to the matching closing brace
and slice raw[start:end+1] instead. Implement a small brace-matching scan that
tracks depth, in-string state and escape chars (so braces inside strings are
ignored) to find the correct end index, then set payload to that substring and
use it for gjson.Valid checks; reference the existing variables start, raw and
payload in helpers_test.go when making the change.

18-21: Fallback from Stdout to Stderr may extract error payloads on failed commands.

Per tests/cli_e2e/core.go:48-95, Result populates both Stdout and Stderr regardless of exit code. When a command fails (exit code ≠ 0), error JSON typically goes to Stderr while Stdout may be empty. This fallback would then extract the error payload—which may be intentional for tests like TestMail_TriagePermissionConstraint_Bot, but could mask issues if a test incorrectly expects success output.

Consider documenting this behavior or adding an explicit parameter to control which stream to parse, so callers can be explicit about their intent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/mail/helpers_test.go` around lines 18 - 21, The helper
currently falls back from result.Stdout to result.Stderr by assigning raw :=
strings.TrimSpace(result.Stdout) then using stderr when stdout is empty, which
can unintentionally parse error payloads; modify the helper to accept an
explicit flag (e.g., parseFromStderr bool or preferStderr bool) or an enum
(StdoutOnly/AllowStderr) and use that to choose between result.Stdout and
result.Stderr instead of implicit fallback, update the call sites (e.g., tests
that expect error JSON like TestMail_TriagePermissionConstraint_Bot) to pass the
appropriate flag, and add a brief comment documenting the semantics of the new
parameter so callers are explicit about which stream to parse.
tests/cli_e2e/mail/mail_user_reference_test.go (1)

21-32: Consider adding AssertStdoutStatus for consistency with the other subtest.

The "watch print output schema" subtest calls AssertExitCode but not AssertStdoutStatus, while the "draft edit print patch template" subtest at line 41 calls both. For consistency across subtests, consider adding the status assertion here as well.

Proposed fix
 		require.NoError(t, err)
 		result.AssertExitCode(t, 0)
+		result.AssertStdoutStatus(t, true)
 
 		payload := mailJSONPayload(t, result)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/mail/mail_user_reference_test.go` around lines 21 - 32, The
"watch print output schema" subtest calls result.AssertExitCode but is missing
the stdout status check for consistency; update the subtest (inside t.Run "watch
print output schema") to call result.AssertStdoutStatus(t, "ok") after
require.NoError and before parsing payload so it mirrors the other subtest's use
of AssertStdoutStatus on the same result object returned by clie2e.RunCmd.
tests/cli_e2e/wiki/wiki_workflow_test.go (1)

21-23: Strengthen title uniqueness for parallel CI runs (Line 21).

Second-level timestamps can collide; include sub-second entropy.

🔧 Suggested patch
-	suffix := time.Now().UTC().Format("20060102-150405")
+	suffix := time.Now().UTC().Format("20060102-150405.000000000")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/wiki/wiki_workflow_test.go` around lines 21 - 23, The test uses
a second-granularity suffix for createdTitle and copiedTitle which can collide
in parallel CI; update the suffix generation (variable suffix) to include
sub-second entropy (e.g., append milliseconds or nanoseconds via
time.Now().UTC().Format with .000 or .000000000 or use
time.Now().UTC().UnixNano()) so createdTitle and copiedTitle become globally
unique across fast concurrent runs; change the suffix variable and keep the same
createdTitle and copiedTitle identifiers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/cli_e2e/wiki/wiki_workflow_test.go`:
- Around line 17-20: TestWiki_Workflow_Bot currently uses shared CLI config and
can leak state between tests; at the start of the TestWiki_Workflow_Bot function
add config isolation by calling t.Setenv("LARKSUITE_CLI_CONFIG_DIR",
t.TempDir()) so the test uses a unique temporary config directory, ensuring
cleanup via t.TempDir(); place this call immediately after ctx/cancel setup in
TestWiki_Workflow_Bot to isolate CLI config state for the test.

---

Nitpick comments:
In `@tests/cli_e2e/mail/helpers_test.go`:
- Around line 23-31: The test currently takes payload := raw[start:] which
assumes JSON runs to EOF; change this to locate the end of the JSON by scanning
from start to the matching closing brace and slice raw[start:end+1] instead.
Implement a small brace-matching scan that tracks depth, in-string state and
escape chars (so braces inside strings are ignored) to find the correct end
index, then set payload to that substring and use it for gjson.Valid checks;
reference the existing variables start, raw and payload in helpers_test.go when
making the change.
- Around line 18-21: The helper currently falls back from result.Stdout to
result.Stderr by assigning raw := strings.TrimSpace(result.Stdout) then using
stderr when stdout is empty, which can unintentionally parse error payloads;
modify the helper to accept an explicit flag (e.g., parseFromStderr bool or
preferStderr bool) or an enum (StdoutOnly/AllowStderr) and use that to choose
between result.Stdout and result.Stderr instead of implicit fallback, update the
call sites (e.g., tests that expect error JSON like
TestMail_TriagePermissionConstraint_Bot) to pass the appropriate flag, and add a
brief comment documenting the semantics of the new parameter so callers are
explicit about which stream to parse.

In `@tests/cli_e2e/mail/mail_user_reference_test.go`:
- Around line 21-32: The "watch print output schema" subtest calls
result.AssertExitCode but is missing the stdout status check for consistency;
update the subtest (inside t.Run "watch print output schema") to call
result.AssertStdoutStatus(t, "ok") after require.NoError and before parsing
payload so it mirrors the other subtest's use of AssertStdoutStatus on the same
result object returned by clie2e.RunCmd.

In `@tests/cli_e2e/wiki/wiki_workflow_test.go`:
- Around line 21-23: The test uses a second-granularity suffix for createdTitle
and copiedTitle which can collide in parallel CI; update the suffix generation
(variable suffix) to include sub-second entropy (e.g., append milliseconds or
nanoseconds via time.Now().UTC().Format with .000 or .000000000 or use
time.Now().UTC().UnixNano()) so createdTitle and copiedTitle become globally
unique across fast concurrent runs; change the suffix variable and keep the same
createdTitle and copiedTitle identifiers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0912aad9-a823-45c2-8200-f7495a9654e2

📥 Commits

Reviewing files that changed from the base of the PR and between f4afa47 and dccdb71.

📒 Files selected for processing (7)
  • tests/cli_e2e/mail/helpers_test.go
  • tests/cli_e2e/mail/mail_bot_constraints_test.go
  • tests/cli_e2e/mail/mail_read_reference_test.go
  • tests/cli_e2e/mail/mail_triage_permission_test.go
  • tests/cli_e2e/mail/mail_user_reference_test.go
  • tests/cli_e2e/wiki/helpers_test.go
  • tests/cli_e2e/wiki/wiki_workflow_test.go

Comment on lines +17 to +20
func TestWiki_Workflow_Bot(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add config isolation at test start (Line 17).

This test should isolate CLI config state to avoid cross-test leakage/flakes.

🔧 Suggested patch
 func TestWiki_Workflow_Bot(t *testing.T) {
+	t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
 	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
 	t.Cleanup(cancel)

As per coding guidelines **/*_test.go: Isolate config state in Go tests by using t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/wiki/wiki_workflow_test.go` around lines 17 - 20,
TestWiki_Workflow_Bot currently uses shared CLI config and can leak state
between tests; at the start of the TestWiki_Workflow_Bot function add config
isolation by calling t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) so the
test uses a unique temporary config directory, ensuring cleanup via t.TempDir();
place this call immediately after ctx/cancel setup in TestWiki_Workflow_Bot to
isolate CLI config state for the test.

zhao.yuxuan added 2 commits April 7, 2026 20:42
Change-Id: Ic1f8f4fc4a28499c20154b3f7e3a4b5f4ddc46f2
Change-Id: I966c752cec04d52c6af76348b9227ec9e97f4ca4
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/cli_e2e/base/base_core_workflow_test.go`:
- Around line 17-21: The test TestBase_CoreWorkflow currently shells out to the
real CLI and can read/write shared config; isolate its CLI config by setting the
env var LARKSUITE_CLI_CONFIG_DIR to a temp dir for the test (use
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())) before invoking
createBase/uniqueName so the CLI uses the test-specific config directory and
avoids cross-test contamination.

In `@tests/cli_e2e/base/base_dashboard_form_workflow_test.go`:
- Around line 22-25: The dashboard block payloads are referencing the literal
"DashboardTable" instead of the actual generated name created by uniqueName;
update the calls that build block JSON (used by createBlock) to interpolate or
pass the generated table name returned/used by createTable (e.g., store the
uniqueName result in a variable like tableName or use the value returned by
createTable) and replace all occurrences of the string "DashboardTable" in the
block config (including the series.field_name/table_name entries) with that
variable so the block targets the real table; adjust the calls near createTable
and createBlock (and the other occurrences at lines referenced in the comment)
accordingly.
- Around line 17-20: The tests call the real CLI and must isolate shared CLI
config; update the entrypoints (e.g., TestBase_DashboardWorkflow and the other
test referenced around lines 142-145) to set a sandboxed config dir by calling
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) at the start (before any CLI
shell-out) so each test uses its own temporary config directory and cannot
inherit or mutate global auth/state.

In `@tests/cli_e2e/base/base_role_workflow_workflow_test.go`:
- Around line 17-23: In TestBase_RoleAdvpermAndWorkflowCoverage, isolate the CLI
config by setting the env var before any CLI interactions: call
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) near the top of the test
(before createBase and any CLI usage) so the test uses an isolated config
directory; update TestBase_RoleAdvpermAndWorkflowCoverage to set this
environment variable (and ensure cleanup via t.TempDir) to avoid sharing host
CLI state.

In `@tests/cli_e2e/base/base_table_record_view_workflow_test.go`:
- Around line 17-25: The test TestBase_TableFieldRecordViewWorkflow runs the
real CLI without isolating its config, causing cross-run state leakage; update
the test to set the environment variable LARKSUITE_CLI_CONFIG_DIR to an isolated
temp directory at the start (use t.Setenv("LARKSUITE_CLI_CONFIG_DIR",
t.TempDir())) so the CLI uses a per-test config directory before calling
createBase/createTable; place this call near the top of
TestBase_TableFieldRecordViewWorkflow so all subsequent CLI invocations use the
isolated config.
- Around line 178-189: The test's assertion using gjson.Get(...,
"data.items.#").Int() >= 0 is weak because a missing path returns 0; update the
"record history list" test to explicitly assert that the "data.items" array
exists (and if expected, that its count is > 0) by checking the presence/type of
the path from result.Stdout before using the count—i.e., replace the current
gjson count check with an assertion that gjson.Get(result.Stdout,
"data.items").Exists() or IsArray(), and then assert gjson.Get(...,
"data.items.#").Int() > 0 if non-empty history is required; keep assertions
using t/require consistent with the surrounding test helpers (result, recordID,
tableID).

In `@tests/cli_e2e/base/helpers_test.go`:
- Around line 51-71: The helpers createBase (and likewise copyBase) currently
provision remote bases but never register cleanup, so add a t.Cleanup
registration inside createBase that calls clie2e.RunCmd to delete the created
base using the returned baseToken (e.g., RunCmd(ctx, clie2e.Request{Args:
[]string{"base", "+base-delete", "--token", baseToken}, DefaultAs: "bot"})) and
assert it succeeds; mirror the same pattern in copyBase to ensure any
created/copied base is removed after the test finishes.
- Around line 381-386: The helper writeTempAttachment currently writes directly
with os.WriteFile; change it to use the repository VFS APIs and validate the
constructed path before writing: call validate.SafeInputPath on the final path
derived from t.TempDir() and the file name, then use vfs.WriteFile (or the
appropriate vfs file write helper) to write the content and check the returned
error; ensure t.Helper(), require.NoError remains and reference
writeTempAttachment, vfs.WriteFile (or vfs.Open/Write), and
validate.SafeInputPath when making the change.
- Around line 133-140: The cleanup handler using parentT.Cleanup calls
clie2e.RunCmd with context.Background() and dereferences
deleteResult.Stdout/Stderr without checking deleteResult; replace
context.Background() with a bounded context (e.g., context.WithTimeout) to avoid
hanging and after calling clie2e.RunCmd check deleteErr and whether deleteResult
is nil before accessing deleteResult.Stdout or deleteResult.Stderr (log a clear
message including deleteErr when deleteResult is nil); apply the same change
pattern to the other Cleanup blocks that call clie2e.RunCmd (lines referencing
deleteResult/deleteErr, parentT.Cleanup, baseToken, tableID).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff0722d3-b727-4712-bd8e-23b50ca45dd2

📥 Commits

Reviewing files that changed from the base of the PR and between dccdb71 and 819d09e.

📒 Files selected for processing (5)
  • tests/cli_e2e/base/base_core_workflow_test.go
  • tests/cli_e2e/base/base_dashboard_form_workflow_test.go
  • tests/cli_e2e/base/base_role_workflow_workflow_test.go
  • tests/cli_e2e/base/base_table_record_view_workflow_test.go
  • tests/cli_e2e/base/helpers_test.go

Comment on lines +17 to +21
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"))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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)
As per coding guidelines, `**/*_test.go`: Isolate config state in Go tests by using `t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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"))
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)
baseToken := createBase(t, ctx, uniqueName("lark-cli-e2e-base"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_core_workflow_test.go` around lines 17 - 21, The test
TestBase_CoreWorkflow currently shells out to the real CLI and can read/write
shared config; isolate its CLI config by setting the env var
LARKSUITE_CLI_CONFIG_DIR to a temp dir for the test (use
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())) before invoking
createBase/uniqueName so the CLI uses the test-specific config directory and
avoids cross-test contamination.

Comment on lines +17 to +20
func TestBase_DashboardWorkflow(t *testing.T) {
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
t.Cleanup(cancel)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Isolate the CLI config in both E2E entrypoints.

Both tests shell out to the real CLI without sandboxing LARKSUITE_CLI_CONFIG_DIR, so they can inherit and mutate shared config/auth state.

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)
As per coding guidelines, `**/*_test.go`: Isolate config state in Go tests by using `t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())`.

Also applies to: 142-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_dashboard_form_workflow_test.go` around lines 17 -
20, The tests call the real CLI and must isolate shared CLI config; update the
entrypoints (e.g., TestBase_DashboardWorkflow and the other test referenced
around lines 142-145) to set a sandboxed config dir by calling
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) at the start (before any CLI
shell-out) so each test uses its own temporary config directory and cannot
inherit or mutate global auth/state.

Comment on lines +22 to +25
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}`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use the actual generated table name in dashboard block config.

The table is created with uniqueName("DashboardTable"), but both block payloads still reference the literal "DashboardTable". That makes this workflow target the wrong table or fail when no plain DashboardTable exists.

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}`},
@@
-	_ = tableID

Also applies to: 97-99, 139-139

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_dashboard_form_workflow_test.go` around lines 22 -
25, The dashboard block payloads are referencing the literal "DashboardTable"
instead of the actual generated name created by uniqueName; update the calls
that build block JSON (used by createBlock) to interpolate or pass the generated
table name returned/used by createTable (e.g., store the uniqueName result in a
variable like tableName or use the value returned by createTable) and replace
all occurrences of the string "DashboardTable" in the block config (including
the series.field_name/table_name entries) with that variable so the block
targets the real table; adjust the calls near createTable and createBlock (and
the other occurrences at lines referenced in the comment) accordingly.

Comment on lines +17 to +23
func TestBase_RoleAdvpermAndWorkflowCoverage(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-admin"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Isolate the CLI config for this E2E test.

This workflow uses the real CLI but shares whatever config directory happens to be on the machine, which makes the test stateful across runs.

Suggested fix
 func TestBase_RoleAdvpermAndWorkflowCoverage(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)
As per coding guidelines, `**/*_test.go`: Isolate config state in Go tests by using `t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_role_workflow_workflow_test.go` around lines 17 - 23,
In TestBase_RoleAdvpermAndWorkflowCoverage, isolate the CLI config by setting
the env var before any CLI interactions: call
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) near the top of the test
(before createBase and any CLI usage) so the test uses an isolated config
directory; update TestBase_RoleAdvpermAndWorkflowCoverage to set this
environment variable (and ensure cleanup via t.TempDir) to avoid sharing host
CLI state.

Comment on lines +17 to +25
func TestBase_TableFieldRecordViewWorkflow(t *testing.T) {
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
t.Cleanup(cancel)

baseToken := createBase(t, ctx, uniqueName("lark-cli-e2e-base-main"))
tableID, primaryFieldID, primaryViewID := createTable(t, parentT, ctx, baseToken, uniqueName("Orders"), `[{"name":"Name","type":"text"}]`, `{"name":"Main","type":"grid"}`)
require.NotEmpty(t, primaryFieldID)
require.NotEmpty(t, primaryViewID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Isolate the CLI config for this E2E test.

This test invokes the real CLI but never overrides LARKSUITE_CLI_CONFIG_DIR, so it can bleed auth/config state across runs.

Suggested fix
 func TestBase_TableFieldRecordViewWorkflow(t *testing.T) {
+	t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
 	parentT := t
 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
 	t.Cleanup(cancel)
As per coding guidelines, `**/*_test.go`: Isolate config state in Go tests by using `t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_table_record_view_workflow_test.go` around lines 17 -
25, The test TestBase_TableFieldRecordViewWorkflow runs the real CLI without
isolating its config, causing cross-run state leakage; update the test to set
the environment variable LARKSUITE_CLI_CONFIG_DIR to an isolated temp directory
at the start (use t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())) so the CLI
uses a per-test config directory before calling createBase/createTable; place
this call near the top of TestBase_TableFieldRecordViewWorkflow so all
subsequent CLI invocations use the isolated config.

Comment on lines +178 to +189
t.Run("record history list", func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"base", "+record-history-list", "--base-token", baseToken, "--table-id", tableID, "--record-id", recordID, "--page-size", "10"},
DefaultAs: "bot",
})
require.NoError(t, err)
if result.ExitCode != 0 {
skipIfBaseUnavailable(t, result, "requires bot record history capability")
}
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)
assert.True(t, gjson.Get(result.Stdout, "data.items.#").Int() >= 0, "stdout:\n%s", result.Stdout)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

record history list isn't asserting anything useful right now.

gjson.Get(..., "data.items.#").Int() >= 0 also passes when data.items is missing, because the missing path becomes 0. Please assert the array exists at minimum, or assert a non-zero count if history should already be visible here.

Minimal fix
-		assert.True(t, gjson.Get(result.Stdout, "data.items.#").Int() >= 0, "stdout:\n%s", result.Stdout)
+		assert.True(t, gjson.Get(result.Stdout, "data.items").Exists(), "stdout:\n%s", result.Stdout)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
t.Run("record history list", func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"base", "+record-history-list", "--base-token", baseToken, "--table-id", tableID, "--record-id", recordID, "--page-size", "10"},
DefaultAs: "bot",
})
require.NoError(t, err)
if result.ExitCode != 0 {
skipIfBaseUnavailable(t, result, "requires bot record history capability")
}
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)
assert.True(t, gjson.Get(result.Stdout, "data.items.#").Int() >= 0, "stdout:\n%s", result.Stdout)
t.Run("record history list", func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"base", "+record-history-list", "--base-token", baseToken, "--table-id", tableID, "--record-id", recordID, "--page-size", "10"},
DefaultAs: "bot",
})
require.NoError(t, err)
if result.ExitCode != 0 {
skipIfBaseUnavailable(t, result, "requires bot record history capability")
}
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)
assert.True(t, gjson.Get(result.Stdout, "data.items").Exists(), "stdout:\n%s", result.Stdout)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_table_record_view_workflow_test.go` around lines 178
- 189, The test's assertion using gjson.Get(..., "data.items.#").Int() >= 0 is
weak because a missing path returns 0; update the "record history list" test to
explicitly assert that the "data.items" array exists (and if expected, that its
count is > 0) by checking the presence/type of the path from result.Stdout
before using the count—i.e., replace the current gjson count check with an
assertion that gjson.Get(result.Stdout, "data.items").Exists() or IsArray(), and
then assert gjson.Get(..., "data.items.#").Int() > 0 if non-empty history is
required; keep assertions using t/require consistent with the surrounding test
helpers (result, recordID, tableID).

Comment on lines +51 to +71
func createBase(t *testing.T, ctx context.Context, name string) string {
t.Helper()

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"base", "+base-create", "--name", name, "--time-zone", "Asia/Shanghai"},
DefaultAs: "bot",
})
require.NoError(t, err)
if result.ExitCode != 0 {
skipIfBaseUnavailable(t, result, "requires bot base create capability")
}
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

baseToken := gjson.Get(result.Stdout, "data.base.app_token").String()
if baseToken == "" {
baseToken = gjson.Get(result.Stdout, "data.base.base_token").String()
}
require.NotEmpty(t, baseToken, "stdout:\n%s", result.Stdout)
return baseToken
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add teardown for created and copied bases.

createBase and copyBase are the only provisioning helpers here without any cleanup registration, so every E2E run leaks remote bases. That will make the suite quota/state dependent over time.

Also applies to: 73-93

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/helpers_test.go` around lines 51 - 71, The helpers
createBase (and likewise copyBase) currently provision remote bases but never
register cleanup, so add a t.Cleanup registration inside createBase that calls
clie2e.RunCmd to delete the created base using the returned baseToken (e.g.,
RunCmd(ctx, clie2e.Request{Args: []string{"base", "+base-delete", "--token",
baseToken}, DefaultAs: "bot"})) and assert it succeeds; mirror the same pattern
in copyBase to ensure any created/copied base is removed after the test
finishes.

Comment on lines +133 to +140
parentT.Cleanup(func() {
deleteResult, deleteErr := clie2e.RunCmd(context.Background(), clie2e.Request{
Args: []string{"base", "+table-delete", "--base-token", baseToken, "--table-id", tableID, "--yes"},
DefaultAs: "bot",
})
if deleteErr != nil || deleteResult.ExitCode != 0 {
parentT.Logf("best-effort table cleanup skipped: table=%s err=%v stdout=%s stderr=%s", tableID, deleteErr, deleteResult.Stdout, deleteResult.Stderr)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bound cleanup CLI calls and guard deleteResult before logging.

These cleanup handlers use context.Background() for external CLI calls, so teardown can hang indefinitely, and they dereference deleteResult.Stdout/Stderr even when RunCmd returns an infrastructure error with a nil result.

Suggested pattern
-		deleteResult, deleteErr := clie2e.RunCmd(context.Background(), clie2e.Request{
+		cleanupCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+		defer cancel()
+		deleteResult, deleteErr := clie2e.RunCmd(cleanupCtx, clie2e.Request{
 			Args:      []string{"base", "+table-delete", "--base-token", baseToken, "--table-id", tableID, "--yes"},
 			DefaultAs: "bot",
 		})
-		if deleteErr != nil || deleteResult.ExitCode != 0 {
-			parentT.Logf("best-effort table cleanup skipped: table=%s err=%v stdout=%s stderr=%s", tableID, deleteErr, deleteResult.Stdout, deleteResult.Stderr)
+		if deleteErr != nil {
+			parentT.Logf("best-effort table cleanup skipped: table=%s err=%v", tableID, deleteErr)
+			return
+		}
+		if deleteResult.ExitCode != 0 {
+			parentT.Logf("best-effort table cleanup skipped: table=%s stdout=%s stderr=%s", tableID, deleteResult.Stdout, deleteResult.Stderr)
 		}

Also applies to: 166-173, 196-203, 229-236, 259-266, 289-296, 319-326, 349-356

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/helpers_test.go` around lines 133 - 140, The cleanup
handler using parentT.Cleanup calls clie2e.RunCmd with context.Background() and
dereferences deleteResult.Stdout/Stderr without checking deleteResult; replace
context.Background() with a bounded context (e.g., context.WithTimeout) to avoid
hanging and after calling clie2e.RunCmd check deleteErr and whether deleteResult
is nil before accessing deleteResult.Stdout or deleteResult.Stderr (log a clear
message including deleteErr when deleteResult is nil); apply the same change
pattern to the other Cleanup blocks that call clie2e.RunCmd (lines referencing
deleteResult/deleteErr, parentT.Cleanup, baseToken, tableID).

Comment on lines +381 to +386
func writeTempAttachment(t *testing.T, content string) string {
t.Helper()

path := filepath.Join(t.TempDir(), "attachment.txt")
err := os.WriteFile(path, []byte(content), 0o644)
require.NoError(t, err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use the repo filesystem abstraction for temp attachments.

This helper writes through os.WriteFile without path validation. Please route this through vfs.* and validate the path before writing. As per coding guidelines, **/*.go: Use vfs.* instead of os.* for all filesystem access and validate paths using validate.SafeInputPath before any file I/O operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/helpers_test.go` around lines 381 - 386, The helper
writeTempAttachment currently writes directly with os.WriteFile; change it to
use the repository VFS APIs and validate the constructed path before writing:
call validate.SafeInputPath on the final path derived from t.TempDir() and the
file name, then use vfs.WriteFile (or the appropriate vfs file write helper) to
write the content and check the returned error; ensure t.Helper(),
require.NoError remains and reference writeTempAttachment, vfs.WriteFile (or
vfs.Open/Write), and validate.SafeInputPath when making the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/S Low-risk docs, CI, test, or chore only changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants