Skip to content

feat(test): Add a CLI E2E testing framework for lark-cli, task domain testcase and ci action#236

Merged
yaozhen00 merged 3 commits intomainfrom
feat/e2e_test
Apr 3, 2026
Merged

feat(test): Add a CLI E2E testing framework for lark-cli, task domain testcase and ci action#236
yaozhen00 merged 3 commits intomainfrom
feat/e2e_test

Conversation

@yaozhen00
Copy link
Copy Markdown
Collaborator

@yaozhen00 yaozhen00 commented Apr 2, 2026

Summary

Add a CLI E2E testing framework for lark-cli, cover core task workflows with real end-to-end cases, and wire the suite into GitHub Actions so the targeted CLI E2E cases run automatically in CI with bot credentials and test reports.

Changes

  • Change 1
    Added the tests/cli_e2e E2E test harness, including binary discovery, CLI invocation helpers, shared assertions, and framework self-tests. Also added an initial demo testcase to validate the framework structure and usage.
  • Change 2
    Added real task-domain CLI E2E coverage and contributor guidance. This includes multiple workflow-style task testcases under tests/cli_e2e/task, helper utilities for setup/cleanup, a cli-e2e-testcase-writer skill for adding future cases consistently, and README documentation for the new E2E module.
  • Change 3
    Added a dedicated GitHub Actions workflow for CLI E2E. The workflow builds lark-cli, initializes bot credentials from TEST_BOT1_APP_ID / TEST_BOT1_APP_SECRET, runs all tests/cli_e2e packages except the framework root package and demo, uses gotestsum for readable test output, publishes JUnit reports, and skips forked PR runs that cannot access repository secrets.

Test Plan

  • Unit tests pass
  • Manual local verification confirms the lark xxx command works as expected
  • e2e task domain testcase

Related Issues

  • None

Summary by CodeRabbit

  • Tests

    • Added a comprehensive CLI end-to-end test suite covering task lifecycle, comments, reminders, status transitions, tasklists, helpers, and core CLI invocation/validation utilities.
  • Documentation

    • Added E2E test README and guidance for writing scenario-based CLI testcases.
  • Chores

    • Added a new "CLI E2E Tests" CI workflow, updated coverage CI to ignore E2E tests, bumped CI checkout pin, and added test dependencies for assertions and JSON parsing.

@github-actions github-actions bot added the size/M Single-domain feat or fix with limited business impact label Apr 2, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

Adds a CLI end-to-end test suite and CI integration: new GitHub Actions workflow for CLI E2E tests, a clie2e Go test harness and helpers, multiple scenario-based E2E tests under tests/cli_e2e/, documentation, and test dependency updates in go.mod.

Changes

Cohort / File(s) Summary
New CI workflow
​.github/workflows/cli-e2e.yml
Add "CLI E2E Tests" workflow: path-filtered push/PR + manual trigger, fork-safe job, sets bot secrets, builds CLI, discovers test packages, runs tests with gotestsum, and appends a JUnit summary to GITHUB_STEP_SUMMARY.
Existing workflows
​.github/workflows/coverage.yml, ​.github/workflows/gitleaks.yml
Exclude tests/cli_e2e/** from coverage job package list; bump actions/checkout pin in gitleaks from v4→v5 commit.
Module dependencies
go.mod
Add test/runtime dependencies: github.com/stretchr/testify v1.11.1, github.com/tidwall/gjson v1.18.0 and related indirect modules.
Docs
tests/cli_e2e/README.md, tests/cli_e2e/cli-e2e-testcase-writer/SKILL.md
Add README and SKILL guidance covering test layout, harness usage, contributor instructions, and testcase-writing conventions.
Test harness & unit tests
tests/cli_e2e/core.go, tests/cli_e2e/core_test.go
Introduce clie2e harness: Request/Result, RunCmd, ResolveBinaryPath, BuildArgs, exit-code normalization, JSON helpers; unit tests cover resolution precedence, arg building, execution, default-as init, and context-cancellation.
E2E tests & helpers
tests/cli_e2e/demo/task_lifecycle_test.go, tests/cli_e2e/task/*, tests/cli_e2e/task/helpers_test.go
Add demo and task scenario tests (lifecycle, comment, reminder, status, tasklist flows) and helpers createTask/createTasklist with cleanup callbacks and JSON assertions. Review timeouts, cleanup robustness, and credential/environment assumptions.

Sequence Diagram

sequenceDiagram
    participant Test as Test Function
    participant RunCmd as clie2e.RunCmd
    participant Resolve as ResolveBinaryPath
    participant Build as BuildArgs
    participant Exec as exec.CommandContext
    participant Binary as lark-cli
    participant Parse as JSON Parser

    Test->>RunCmd: RunCmd(ctx, Request)
    RunCmd->>Resolve: Resolve binary path (explicit → env → project-relative → PATH)
    Resolve-->>RunCmd: Binary path
    RunCmd->>Build: BuildArgs(req) (append --as, --format, --params, --data)
    Build-->>RunCmd: Final CLI args
    RunCmd->>Exec: Execute command, capture stdout/stderr
    Exec->>Binary: Invoke lark-cli
    Binary-->>Exec: Exit code + output
    Exec-->>RunCmd: Captured stdout/stderr
    RunCmd->>Parse: Parse stdout/stderr as JSON (if needed)
    Parse-->>RunCmd: Parsed objects
    RunCmd-->>Test: Result{ExitCode, Stdout, Stderr, RunErr}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

size/XL

Suggested reviewers

  • liangshuo-1
  • fangshuyu-768

Poem

🐰 Hop, hop, the tests arise,

I build the CLI beneath spring skies,
Tasks and lists in JSON gleam,
E2E hops through every dream,
Pipelines dance — the green light beams!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main change: adding a CLI E2E testing framework with task domain testcases and CI integration.
Description check ✅ Passed The description comprehensively covers the summary, changes, test plan, and related issues sections with concrete details about the framework additions, task domain coverage, and GitHub Actions integration.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/e2e_test

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

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 2, 2026

CLA assistant check
All committers have signed the CLA.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@88d1b1022bee10fa8b89b52db6b8c38dcf41c5c8

🧩 Skill update

npx skills add larksuite/cli#feat/e2e_test -y -g

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 2, 2026

Greptile Summary

This PR introduces a full CLI E2E test framework (tests/cli_e2e) with harness utilities, task-domain workflow tests, a contributor skill, and a dedicated GitHub Actions workflow. Prior concerns (subtest t.Errorf panic, unpinned third-party action) are correctly resolved in this revision.

Confidence Score: 5/5

Safe to merge; all previously flagged blocking issues are resolved and remaining findings are minor robustness suggestions.

Both P1 concerns from earlier rounds are fixed. The two remaining findings are P2: a missing file-existence guard in the CI summary step and a confusing type-mismatch path in AssertStdoutStatus. Neither affects correctness of the E2E tests themselves.

.github/workflows/cli-e2e.yml (XML summary guard) and tests/cli_e2e/core.go (AssertStdoutStatus type clarity) — both are minor quality improvements.

Important Files Changed

Filename Overview
.github/workflows/cli-e2e.yml New CI workflow for E2E tests; action pins and fork guard are correct, but the XML summary step has no guard against a missing report file.
tests/cli_e2e/core.go Well-structured E2E harness; binary resolution, arg building, and result assertion helpers look correct; AssertStdoutStatus dispatch-on-key design produces confusing type-mismatch failures when the wrong style is passed.
tests/cli_e2e/core_test.go Comprehensive unit tests for the harness with fake CLI scripts; resetDefaultAsInitForTest correctly resets the sync.Once between test cases.
tests/cli_e2e/task/helpers_test.go Shared createTask / createTasklist helpers; correctly register cleanup on parentT and use context.Background() for teardown calls.
tests/cli_e2e/task/tasklist_workflow_test.go Previously-flagged t.Errorf on finished subtest is now correctly fixed to use parentT.Errorf in both cleanup closures.

Sequence Diagram

sequenceDiagram
    participant CI as GitHub Actions
    participant Build as make build
    participant Config as lark-cli config init
    participant GoTest as go test (gotestsum)
    participant Harness as clie2e.RunCmd
    participant CLI as lark-cli binary
    participant API as Lark API

    CI->>Build: make build → lark-cli binary
    CI->>Config: config init --app-id / --app-secret-stdin
    CI->>GoTest: run tests/cli_e2e/task/...
    GoTest->>Harness: clie2e.RunCmd(ctx, Request{Args, Params, Data})
    Harness->>Harness: ResolveBinaryPath (env → project root → PATH)
    Harness->>CLI: exec lark-cli config default-as bot (once)
    Harness->>CLI: exec lark-cli <args> --params <json> --data <json>
    CLI->>API: HTTP request (bot credentials)
    API-->>CLI: JSON response
    CLI-->>Harness: stdout JSON + exit code
    Harness-->>GoTest: Result{Stdout, ExitCode, ...}
    GoTest->>GoTest: AssertExitCode / AssertStdoutStatus / gjson assertions
    GoTest-->>CI: JUnit XML report
    CI->>CI: Python summary → GITHUB_STEP_SUMMARY
Loading

Reviews (4): Last reviewed commit: "feat(test): add cli e2e config and fix t..." | Re-trigger Greptile

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: 2

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

326-328: Consider adding a comment explaining the sync.Once reset pattern.

Resetting sync.Once by direct assignment works but is unconventional. A brief comment would help future maintainers understand this is intentional test infrastructure.

Proposed documentation
+// resetDefaultAsInitForTest resets the package-level sync.Once to allow
+// tests to verify the "initialize once" behavior independently.
+// This is only valid in test code within the same package.
 func resetDefaultAsInitForTest() {
 	defaultAsInitOnce = sync.Once{}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/core_test.go` around lines 326 - 328, The test helper
resetDefaultAsInitForTest currently resets the package-level sync.Once by direct
assignment to defaultAsInitOnce = sync.Once{} without explanation; add a concise
comment above the resetDefaultAsInitForTest function (or inside it) explaining
that assigning a zero-value to a sync.Once is an intentional pattern used only
for tests to allow re-running initialization, and note any caveats (e.g., only
safe in tests because it breaks Once semantics for real concurrency). This
clarifies intent for future maintainers and references the reset of
defaultAsInitOnce and the use of sync.Once.

41-58: Good test for project root resolution, but consider parallel test safety.

The test changes the global working directory which could interfere with other tests if run in parallel. Consider adding t.Parallel() constraints or documenting that this test cannot run in parallel.

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

In `@tests/cli_e2e/core_test.go` around lines 41 - 58, This test mutates the
global working directory (os.Chdir) which can race with parallel tests; either
mark the test safe for parallel execution by adding t.Parallel() at the top of
the test and ensuring the test uses an isolated temp directory for all file
operations, or explicitly document/guard that it must not run in parallel (add a
comment and avoid calling t.Parallel in this file). Locate the test that calls
ResolveBinaryPath(Request{}), touches projectRootMarkerDir, uses cliBinaryName,
sets EnvBinaryPath, and performs os.Getwd/os.Chdir; then either add t.Parallel()
as the first line of that t.Run block and confirm all file ops use tmpDir, or
add a clear comment stating the test cannot run in parallel (and do not call
t.Parallel).
tests/cli_e2e/core.go (1)

237-251: Type coercion in AssertStdoutStatus may produce confusing failures.

When expected is true (bool) but the JSON contains "code": 0, the assertion will fail because it checks ok first (which doesn't exist) then checks code against a bool. Similarly, passing 0 (int) when the response has "ok": true will fail.

The current callers use this correctly (bool for ok responses, int for code responses), but the API could be clearer:

Consider separate assertion methods for clarity
// AssertStdoutOK asserts stdout JSON has {"ok": <expected>}.
func (r *Result) AssertStdoutOK(t *testing.T, expected bool) {
    t.Helper()
    okResult := gjson.Get(r.Stdout, "ok")
    if !okResult.Exists() {
        assert.Fail(t, "stdout missing 'ok' field", "stdout:\n%s", r.Stdout)
        return
    }
    assert.Equal(t, expected, okResult.Bool(), "stdout:\n%s", r.Stdout)
}

// AssertStdoutCode asserts stdout JSON has {"code": <expected>}.
func (r *Result) AssertStdoutCode(t *testing.T, expected int) {
    t.Helper()
    codeResult := gjson.Get(r.Stdout, "code")
    if !codeResult.Exists() {
        assert.Fail(t, "stdout missing 'code' field", "stdout:\n%s", r.Stdout)
        return
    }
    assert.Equal(t, expected, int(codeResult.Int()), "stdout:\n%s", r.Stdout)
}

This would make test intent explicit: result.AssertStdoutOK(t, true) vs result.AssertStdoutCode(t, 0).

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

In `@tests/cli_e2e/core.go` around lines 237 - 251, The current AssertStdoutStatus
method on Result ambiguously coerces expected into either bool or int depending
on which key exists and can cause confusing failures; replace it with two
explicit methods: AssertStdoutOK(t *testing.T, expected bool) which checks that
gjson.Get(r.Stdout, "ok") exists then compares okResult.Bool() to expected, and
AssertStdoutCode(t *testing.T, expected int) which checks that
gjson.Get(r.Stdout, "code") exists then compares int(codeResult.Int()) to
expected; remove or deprecate AssertStdoutStatus to force callers to use
AssertStdoutOK or AssertStdoutCode and update any call sites accordingly.
.github/workflows/cli-e2e.yml (1)

62-71: Consider quoting the packages variable to prevent globbing issues.

While word splitting is intentional for passing multiple packages to the test command, using an array or explicit handling would be safer.

Proposed improvement for robustness
       - name: Run CLI E2E tests
         env:
           LARK_CLI_BIN: ${{ github.workspace }}/lark-cli
         run: |
-          packages=$(go list ./tests/cli_e2e/... | grep -v '^github.com/larksuite/cli/tests/cli_e2e$' | grep -v '/demo$')
+          packages=$(go list ./tests/cli_e2e/... | grep -v '^github.com/larksuite/cli/tests/cli_e2e$' | grep -v '/demo$' | tr '\n' ' ')
           if [ -z "$packages" ]; then
             echo "No CLI E2E packages to test after exclusions."
             exit 1
           fi
-          go run gotest.tools/gotestsum@v1.12.3 --format testname --junitfile cli-e2e-report.xml -- -count=1 -v $packages
+          # shellcheck disable=SC2086 - intentional word splitting
+          go run gotest.tools/gotestsum@v1.12.3 --format testname --junitfile cli-e2e-report.xml -- -count=1 -v $packages
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/cli-e2e.yml around lines 62 - 71, The packages
word-splitting is intentional but currently risks filename globbing; convert the
whitespace-separated packages string into a bash array and pass it safely to the
go test command. After computing packages, use mapfile -t (or readarray -t) to
populate an array (e.g., pkg_array) from "$packages" and then invoke the test
runner with "${pkg_array[@]}" in place of $packages so each package is a
separate arg and globbing is avoided; update the Run step accordingly
referencing the packages variable and pkg_array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/cli-e2e.yml:
- Around line 73-80: The step named "Publish CLI E2E test report" uses an
invalid condition syntax; replace the plain YAML condition `(!cancelled())` with
a GitHub Actions expression wrapper by changing the step's if to use `${{
!cancelled() }}` (or `${{ !cancelled }}`) so the runner evaluates the expression
correctly; update the if field in that step to the wrapped expression.

In `@tests/cli_e2e/core.go`:
- Around line 64-66: The call in defaultAsInitOnce.Do currently ignores any
error returned by setDefaultAs; update the initialization to capture the
returned error from setDefaultAs(binaryPath, defaultIdentity) and handle it (at
minimum log it via the test logger or call t.Fatalf/t.Logf as appropriate) so
CI-visible failures surface the root cause; modify the closure used with
defaultAsInitOnce.Do to check the error value and record or fail the test rather
than discarding it.

---

Nitpick comments:
In @.github/workflows/cli-e2e.yml:
- Around line 62-71: The packages word-splitting is intentional but currently
risks filename globbing; convert the whitespace-separated packages string into a
bash array and pass it safely to the go test command. After computing packages,
use mapfile -t (or readarray -t) to populate an array (e.g., pkg_array) from
"$packages" and then invoke the test runner with "${pkg_array[@]}" in place of
$packages so each package is a separate arg and globbing is avoided; update the
Run step accordingly referencing the packages variable and pkg_array.

In `@tests/cli_e2e/core_test.go`:
- Around line 326-328: The test helper resetDefaultAsInitForTest currently
resets the package-level sync.Once by direct assignment to defaultAsInitOnce =
sync.Once{} without explanation; add a concise comment above the
resetDefaultAsInitForTest function (or inside it) explaining that assigning a
zero-value to a sync.Once is an intentional pattern used only for tests to allow
re-running initialization, and note any caveats (e.g., only safe in tests
because it breaks Once semantics for real concurrency). This clarifies intent
for future maintainers and references the reset of defaultAsInitOnce and the use
of sync.Once.
- Around line 41-58: This test mutates the global working directory (os.Chdir)
which can race with parallel tests; either mark the test safe for parallel
execution by adding t.Parallel() at the top of the test and ensuring the test
uses an isolated temp directory for all file operations, or explicitly
document/guard that it must not run in parallel (add a comment and avoid calling
t.Parallel in this file). Locate the test that calls
ResolveBinaryPath(Request{}), touches projectRootMarkerDir, uses cliBinaryName,
sets EnvBinaryPath, and performs os.Getwd/os.Chdir; then either add t.Parallel()
as the first line of that t.Run block and confirm all file ops use tmpDir, or
add a clear comment stating the test cannot run in parallel (and do not call
t.Parallel).

In `@tests/cli_e2e/core.go`:
- Around line 237-251: The current AssertStdoutStatus method on Result
ambiguously coerces expected into either bool or int depending on which key
exists and can cause confusing failures; replace it with two explicit methods:
AssertStdoutOK(t *testing.T, expected bool) which checks that
gjson.Get(r.Stdout, "ok") exists then compares okResult.Bool() to expected, and
AssertStdoutCode(t *testing.T, expected int) which checks that
gjson.Get(r.Stdout, "code") exists then compares int(codeResult.Int()) to
expected; remove or deprecate AssertStdoutStatus to force callers to use
AssertStdoutOK or AssertStdoutCode and update any call sites accordingly.
🪄 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: c809658c-c2ba-4238-8e8b-ea8d9173601d

📥 Commits

Reviewing files that changed from the base of the PR and between f930d9c and 9f6a28d.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (13)
  • .github/workflows/cli-e2e.yml
  • go.mod
  • tests/cli_e2e/README.md
  • tests/cli_e2e/cli-e2e-testcase-writer/SKILL.md
  • tests/cli_e2e/core.go
  • tests/cli_e2e/core_test.go
  • tests/cli_e2e/demo/task_lifecycle_test.go
  • tests/cli_e2e/task/helpers_test.go
  • tests/cli_e2e/task/task_comment_workflow_test.go
  • tests/cli_e2e/task/task_reminder_workflow_test.go
  • tests/cli_e2e/task/task_status_workflow_test.go
  • tests/cli_e2e/task/tasklist_add_task_workflow_test.go
  • tests/cli_e2e/task/tasklist_workflow_test.go

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: 2

♻️ Duplicate comments (1)
tests/cli_e2e/core.go (1)

67-69: ⚠️ Potential issue | 🟡 Minor

Silent setDefaultAs failure is still being discarded.

Line 68 still ignores the init error, which makes root-cause diagnosis in CI harder when identity setup is the real failure.

Suggested fix
+var defaultAsInitErr error
+
 defaultAsInitOnce.Do(func() {
-	_ = setDefaultAs(binaryPath, defaultIdentity)
+	defaultAsInitErr = setDefaultAs(binaryPath, defaultIdentity)
+	if defaultAsInitErr != nil {
+		fmt.Fprintf(os.Stderr, "warning: failed to set default-as: %v\n", defaultAsInitErr)
+	}
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/core.go` around lines 67 - 69, The call inside
defaultAsInitOnce.Do currently discards the error from setDefaultAs (called with
binaryPath and defaultIdentity); change this to capture the returned error,
handle it by logging or failing the test setup (e.g., call t.Fatalf or return
the error) so CI shows the root cause—update the anonymous func passed to
defaultAsInitOnce.Do to assign err := setDefaultAs(binaryPath, defaultIdentity)
and then handle err appropriately rather than ignoring it.
🤖 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/core.go`:
- Around line 58-69: The RunCmd flow (ResolveBinaryPath, defaultAsInitOnce,
setDefaultAs) is flagged by GO-2026-4602; fix it by upgrading the Go toolchain
used to build and lint the repo to a patched release (Go >= 1.25.8 or >=
1.26.1). Update your CI configuration (GitHub Actions / Makefile /
Dockerfile/etc.) to use Go 1.25.8 or 1.26.1+, update the go directive in go.mod
if present, run go mod tidy and re-run the linter/tests to confirm the
vulnerability is no longer reported for RunCmd, ResolveBinaryPath, and related
code paths.
- Around line 189-194: The setDefaultAs function can hang because it uses
exec.Command without a context; change its signature to accept ctx
context.Context (i.e., setDefaultAs(ctx context.Context, binaryPath string,
identity string) error), replace exec.Command(...) with exec.CommandContext(ctx,
binaryPath, "config", "default-as", identity), and propagate a
cancellable/timeout-bearing context from the caller (RunCmd) or wrap ctx with
context.WithTimeout before calling setDefaultAs; update the RunCmd call site
accordingly and ensure imports for context (and time if you add a timeout) are
added.

---

Duplicate comments:
In `@tests/cli_e2e/core.go`:
- Around line 67-69: The call inside defaultAsInitOnce.Do currently discards the
error from setDefaultAs (called with binaryPath and defaultIdentity); change
this to capture the returned error, handle it by logging or failing the test
setup (e.g., call t.Fatalf or return the error) so CI shows the root
cause—update the anonymous func passed to defaultAsInitOnce.Do to assign err :=
setDefaultAs(binaryPath, defaultIdentity) and then handle err appropriately
rather than ignoring it.
🪄 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: 4af89c2d-2263-4c6d-b358-fdd375d3de45

📥 Commits

Reviewing files that changed from the base of the PR and between 9f6a28d and d683204.

📒 Files selected for processing (6)
  • .github/workflows/cli-e2e.yml
  • .github/workflows/coverage.yml
  • tests/cli_e2e/core.go
  • tests/cli_e2e/demo/task_lifecycle_test.go
  • tests/cli_e2e/task/helpers_test.go
  • tests/cli_e2e/task/tasklist_workflow_test.go
✅ Files skipped from review due to trivial changes (3)
  • tests/cli_e2e/task/tasklist_workflow_test.go
  • tests/cli_e2e/task/helpers_test.go
  • .github/workflows/cli-e2e.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/cli_e2e/demo/task_lifecycle_test.go

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.

🧹 Nitpick comments (3)
tests/cli_e2e/core_test.go (1)

42-59: Working directory change may affect parallel tests.

This test changes os.Getwd() and restores it in a defer. If tests run in parallel (t.Parallel()), this could cause race conditions. Currently the test doesn't call t.Parallel(), so this is safe, but worth noting for future maintenance.

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

In `@tests/cli_e2e/core_test.go` around lines 42 - 59, The test mutates global
process state by calling os.Chdir(oldWD)/os.Chdir(testsDir), which can race if
tests become parallel; instead avoid changing the process working directory by
passing an explicit working-dir into ResolveBinaryPath via the Request object
(use Request{Cwd: testsDir}) or, if Request lacks a Cwd field, add a Cwd string
to Request and make ResolveBinaryPath prefer it over os.Getwd(); remove the
os.Chdir/os.Getwd/oldWD logic and keep t.TempDir(), mustWriteExecutable,
t.Setenv and the assertSamePath check.
.github/workflows/cli-e2e.yml (1)

68-68: Consider pinning gotestsum to a SHA for consistency with other actions and update to the latest version.

The workflow pins actions/checkout, actions/setup-go, and actions/setup-python to commit SHAs, but gotestsum uses a version tag (v1.12.3). A newer version (v1.13.0) is available. For supply-chain consistency and to benefit from the latest improvements, consider updating to the latest version and pinning to a commit SHA, or use go install with a checksum-verified module.

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

In @.github/workflows/cli-e2e.yml at line 68, Update the gotestsum invocation
(the command starting with "go run gotest.tools/gotestsum@v1.12.3") to pin to a
reproducible source and to use the newer release: either change the module
version to v1.13.0 and replace the version tag with a commit SHA (e.g.,
gotest.tools/gotestsum@<commitSHA>) or switch to using "go install" with the
exact module@version and rely on go.sum for checksum verification; ensure the
command that produces cli-e2e-report.xml retains the same flags (--format
testname --junitfile cli-e2e-report.xml -- -count=1 -v $packages) while updating
only the gotestsum reference.
tests/cli_e2e/task/tasklist_workflow_test.go (1)

49-75: Cleanup order differs from the helper function pattern – consider deleting task before tasklist.

t.Cleanup executes in LIFO order. Here, task cleanup is registered (line 49) before tasklist cleanup (line 63), so the tasklist delete runs first. However, the helper functions in this test file (createTasklist and createTask in helpers_test.go) register cleanup in the opposite order, where task cleanup runs before tasklist cleanup. Align this test with that pattern for consistency.

♻️ Proposed fix to match helper function cleanup order
 		parentT.Cleanup(func() {
 			deleteResult, deleteErr := clie2e.RunCmd(context.Background(), clie2e.Request{
-				Args:   []string{"task", "tasks", "delete"},
-				Params: map[string]any{"task_guid": taskGUID},
+				Args:   []string{"task", "tasklists", "delete"},
+				Params: map[string]any{"tasklist_guid": tasklistGUID},
 			})
 			if deleteErr != nil {
-				parentT.Errorf("delete task %s: %v", taskGUID, deleteErr)
+				parentT.Errorf("delete tasklist %s: %v", tasklistGUID, deleteErr)
 				return
 			}
 			if deleteResult.ExitCode != 0 {
-				parentT.Errorf("delete task %s failed: exit=%d stdout=%s stderr=%s", taskGUID, deleteResult.ExitCode, deleteResult.Stdout, deleteResult.Stderr)
+				parentT.Errorf("delete tasklist %s failed: exit=%d stdout=%s stderr=%s", tasklistGUID, deleteResult.ExitCode, deleteResult.Stdout, deleteResult.Stderr)
 			}
 		})
 
 		parentT.Cleanup(func() {
 			deleteResult, deleteErr := clie2e.RunCmd(context.Background(), clie2e.Request{
-				Args:   []string{"task", "tasklists", "delete"},
-				Params: map[string]any{"tasklist_guid": tasklistGUID},
+				Args:   []string{"task", "tasks", "delete"},
+				Params: map[string]any{"task_guid": taskGUID},
 			})
 			if deleteErr != nil {
-				parentT.Errorf("delete tasklist %s: %v", tasklistGUID, deleteErr)
+				parentT.Errorf("delete task %s: %v", taskGUID, deleteErr)
 				return
 			}
 			if deleteResult.ExitCode != 0 {
-				parentT.Errorf("delete tasklist %s failed: exit=%d stdout=%s stderr=%s", tasklistGUID, deleteResult.ExitCode, deleteResult.Stdout, deleteResult.Stderr)
+				parentT.Errorf("delete task %s failed: exit=%d stdout=%s stderr=%s", taskGUID, deleteResult.ExitCode, deleteResult.Stdout, deleteResult.Stderr)
 			}
 		})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/task/tasklist_workflow_test.go` around lines 49 - 75, The
cleanup registrations are in the wrong order: parentT.Cleanup currently
registers task cleanup before tasklist cleanup which causes the tasklist delete
to run first (because Cleanup runs LIFO); swap the two parentT.Cleanup blocks so
the tasklist cleanup is registered first and the task cleanup (the block
referencing taskGUID and calling clie2e.RunCmd with Args
{"task","tasks","delete"}) is registered last, matching the helper functions
createTasklist/createTask pattern and ensuring the task is deleted before its
parent tasklist.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/cli-e2e.yml:
- Line 68: Update the gotestsum invocation (the command starting with "go run
gotest.tools/gotestsum@v1.12.3") to pin to a reproducible source and to use the
newer release: either change the module version to v1.13.0 and replace the
version tag with a commit SHA (e.g., gotest.tools/gotestsum@<commitSHA>) or
switch to using "go install" with the exact module@version and rely on go.sum
for checksum verification; ensure the command that produces cli-e2e-report.xml
retains the same flags (--format testname --junitfile cli-e2e-report.xml --
-count=1 -v $packages) while updating only the gotestsum reference.

In `@tests/cli_e2e/core_test.go`:
- Around line 42-59: The test mutates global process state by calling
os.Chdir(oldWD)/os.Chdir(testsDir), which can race if tests become parallel;
instead avoid changing the process working directory by passing an explicit
working-dir into ResolveBinaryPath via the Request object (use Request{Cwd:
testsDir}) or, if Request lacks a Cwd field, add a Cwd string to Request and
make ResolveBinaryPath prefer it over os.Getwd(); remove the
os.Chdir/os.Getwd/oldWD logic and keep t.TempDir(), mustWriteExecutable,
t.Setenv and the assertSamePath check.

In `@tests/cli_e2e/task/tasklist_workflow_test.go`:
- Around line 49-75: The cleanup registrations are in the wrong order:
parentT.Cleanup currently registers task cleanup before tasklist cleanup which
causes the tasklist delete to run first (because Cleanup runs LIFO); swap the
two parentT.Cleanup blocks so the tasklist cleanup is registered first and the
task cleanup (the block referencing taskGUID and calling clie2e.RunCmd with Args
{"task","tasks","delete"}) is registered last, matching the helper functions
createTasklist/createTask pattern and ensuring the task is deleted before its
parent tasklist.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35800f1c-3436-45c6-9392-e93df6d22363

📥 Commits

Reviewing files that changed from the base of the PR and between d683204 and 9c52273.

📒 Files selected for processing (8)
  • .github/workflows/cli-e2e.yml
  • .github/workflows/coverage.yml
  • .github/workflows/gitleaks.yml
  • tests/cli_e2e/core.go
  • tests/cli_e2e/core_test.go
  • tests/cli_e2e/demo/task_lifecycle_test.go
  • tests/cli_e2e/task/helpers_test.go
  • tests/cli_e2e/task/tasklist_workflow_test.go
✅ Files skipped from review due to trivial changes (2)
  • .github/workflows/gitleaks.yml
  • tests/cli_e2e/task/helpers_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/coverage.yml

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 (2)
tests/cli_e2e/task/task_status_workflow_test.go (1)

50-53: Inconsistent completed_at assertion approach.

Line 52 uses .Int() with NotZero, while line 76 uses .String() comparing to "0". If the API returns completed_at as a string (e.g., "0"), .Int() will return 0 even for completed tasks with a timestamp string. Use a consistent approach:

-		assert.NotZero(t, gjson.Get(result.Stdout, "data.task.completed_at").Int(), "stdout:\n%s", result.Stdout)
+		assert.NotEmpty(t, gjson.Get(result.Stdout, "data.task.completed_at").String(), "stdout:\n%s", result.Stdout)
+		assert.NotEqual(t, "0", gjson.Get(result.Stdout, "data.task.completed_at").String(), "stdout:\n%s", result.Stdout)

Or consistently use .Int() for both assertions if the field is always numeric.

Also applies to: 74-77

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

In `@tests/cli_e2e/task/task_status_workflow_test.go` around lines 50 - 53, The
tests inconsistently assert completed_at using gjson .Int() in one place and
.String() == "0" in another; pick a single robust approach: read completed_at
via gjson.Get(result.Stdout, "data.task.completed_at").String(), convert it to
an integer with strconv.ParseInt, assert no parse error, and then assert the
parsed value is non-zero (or > 0) using the existing assert helpers (replace the
current assert.NotZero and string equality checks). Update both occurrences (the
assertion around task completion and the later check at lines referenced) to use
this parsed-int approach so both locations are consistent and tolerant of
string-encoded timestamps.
tests/cli_e2e/core_test.go (1)

348-349: resetDefaultAsInitForTest mutates shared state; ensure no parallel execution.

This function directly reassigns the package-level sync.Once, which is unsafe if tests run concurrently. The current tests don't call t.Parallel(), so this is fine. Consider adding a comment to prevent future contributors from inadvertently enabling parallel execution:

 func resetDefaultAsInitForTest() {
+	// WARNING: Not safe for parallel test execution. Do not use t.Parallel() in tests that call this.
 	defaultAsInitOnce = sync.Once{}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/core_test.go` around lines 348 - 349, The
resetDefaultAsInitForTest function directly reassigns the package-level
sync.Once variable defaultAsInitOnce and therefore mutates shared state; update
the code by adding a clear comment above resetDefaultAsInitForTest warning that
it is unsafe for parallel test execution (do not call from tests that use
t.Parallel()), referencing resetDefaultAsInitForTest and defaultAsInitOnce so
future contributors know the constraint.
🤖 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/task/tasklist_workflow_test.go`:
- Around line 49-75: The two parentT.Cleanup registrations are in the wrong
order for Go's LIFO cleanup: currently the task delete is registered first and
the tasklist delete second, so at teardown the tasklist is deleted before the
task causing potential failures; swap the registrations so the tasklist delete
cleanup is registered first and the task delete cleanup is registered second
(i.e., register clie2e.RunCmd call with Args {"task","tasklists","delete"}
before the clie2e.RunCmd call with Args {"task","tasks","delete"}) so the task
delete runs before its parent tasklist; keep the same error handling/logging for
deleteResult and deleteErr for both cleanup closures.

---

Nitpick comments:
In `@tests/cli_e2e/core_test.go`:
- Around line 348-349: The resetDefaultAsInitForTest function directly reassigns
the package-level sync.Once variable defaultAsInitOnce and therefore mutates
shared state; update the code by adding a clear comment above
resetDefaultAsInitForTest warning that it is unsafe for parallel test execution
(do not call from tests that use t.Parallel()), referencing
resetDefaultAsInitForTest and defaultAsInitOnce so future contributors know the
constraint.

In `@tests/cli_e2e/task/task_status_workflow_test.go`:
- Around line 50-53: The tests inconsistently assert completed_at using gjson
.Int() in one place and .String() == "0" in another; pick a single robust
approach: read completed_at via gjson.Get(result.Stdout,
"data.task.completed_at").String(), convert it to an integer with
strconv.ParseInt, assert no parse error, and then assert the parsed value is
non-zero (or > 0) using the existing assert helpers (replace the current
assert.NotZero and string equality checks). Update both occurrences (the
assertion around task completion and the later check at lines referenced) to use
this parsed-int approach so both locations are consistent and tolerant of
string-encoded timestamps.
🪄 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: 2929d4de-fef6-4df2-b658-a9f09cf6d103

📥 Commits

Reviewing files that changed from the base of the PR and between 9c52273 and 88d1b10.

📒 Files selected for processing (13)
  • .github/workflows/cli-e2e.yml
  • .github/workflows/coverage.yml
  • .github/workflows/gitleaks.yml
  • tests/cli_e2e/cli-e2e-testcase-writer/SKILL.md
  • tests/cli_e2e/core.go
  • tests/cli_e2e/core_test.go
  • tests/cli_e2e/demo/task_lifecycle_test.go
  • tests/cli_e2e/task/helpers_test.go
  • tests/cli_e2e/task/task_comment_workflow_test.go
  • tests/cli_e2e/task/task_reminder_workflow_test.go
  • tests/cli_e2e/task/task_status_workflow_test.go
  • tests/cli_e2e/task/tasklist_add_task_workflow_test.go
  • tests/cli_e2e/task/tasklist_workflow_test.go
✅ Files skipped from review due to trivial changes (4)
  • .github/workflows/gitleaks.yml
  • tests/cli_e2e/demo/task_lifecycle_test.go
  • .github/workflows/cli-e2e.yml
  • tests/cli_e2e/task/task_reminder_workflow_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • .github/workflows/coverage.yml
  • tests/cli_e2e/task/task_comment_workflow_test.go
  • tests/cli_e2e/task/tasklist_add_task_workflow_test.go
  • tests/cli_e2e/task/helpers_test.go

@yaozhen00 yaozhen00 merged commit 5cf8667 into main Apr 3, 2026
15 checks passed
@yaozhen00 yaozhen00 deleted the feat/e2e_test branch April 3, 2026 09:26
@liangshuo-1 liangshuo-1 mentioned this pull request Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants