Skip to content

Commit 456291f

Browse files
committed
test(core): cover env-prefix substitution + buildShellExecWarnings dual-check (#4386 R4)
Round-4 test-coverage findings from wenshao: 1. **env-prefix integration test in `shell.test.ts`** (cid 3293075622) — the `command substitution warning (issue #4093)` describe block in `shell.test.ts` covered `$()`, backticks, `<()`, `>()`, and the no-warning case, but had no test for the shape where `stripShellWrapper` strips the env-prefix + `bash -c` wrapper to yield a substitution-free inner command (`echo ok`) while the raw command has substitution in the env assignment (`FOO=$(cat secret.txt) bash -c 'echo ok'`). This is the exact shape that exercises the `|| detectCommandSubstitution(rawCommand)` branch of `buildShellExecWarnings` via integration through `getConfirmationDetails`. Without this test, removing the `||` clause wouldn't regress any case here. 2. **`buildShellExecWarnings` unit tests in `shell-utils.test.ts`** (cid 3293078758 second half) — round 3 extracted `buildShellExecWarnings` as an exported helper but added no direct unit test. Added a 5-case describe block covering: no substitution, stripped-form substitution, the env-prefix dual-check case (with a sanity-check that the stripped form actually lacks `$(` to make the dual-check load-bearing), backticks, and process substitution. These complement the integration tests in shell.test.ts / monitor.test.ts which exercise the helper through the tool confirmation paths. Closes wenshao R4 findings: env-prefix integration test (cid 3293075622), `buildShellExecWarnings` direct unit coverage (cid 3293078758 — second half).
1 parent a244850 commit 456291f

2 files changed

Lines changed: 73 additions & 0 deletions

File tree

packages/core/src/tools/shell.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5035,6 +5035,26 @@ describe('ShellTool', () => {
50355035
// `warnings` should be omitted entirely when there's nothing to flag.
50365036
expect(details.warnings).toBeUndefined();
50375037
});
5038+
5039+
// Regression coverage for PR #4386 R4 (cid 3293075622): the
5040+
// `|| detectCommandSubstitution(rawCommand)` branch of
5041+
// `buildShellExecWarnings` only fires for shapes where
5042+
// `stripShellWrapper` yields a substitution-free inner command
5043+
// (here `echo ok`) but the raw command has substitution in the
5044+
// env-prefix. Without this case, removing the `||` clause would
5045+
// not regress any test in this describe block.
5046+
it('surfaces a warning for substitution in the env-prefix of a shell wrapper', async () => {
5047+
const invocation = shellTool.build({
5048+
command: `FOO=$(cat secret.txt) bash -c 'echo ok'`,
5049+
is_background: false,
5050+
});
5051+
const details = (await invocation.getConfirmationDetails(
5052+
new AbortController().signal,
5053+
)) as { warnings?: string[] };
5054+
5055+
expect(details.warnings).toBeDefined();
5056+
expect(details.warnings?.[0]).toMatch(/command substitution/i);
5057+
});
50385058
});
50395059
});
50405060

packages/core/src/utils/shell-utils.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
import { expect, describe, it, beforeEach, vi, afterEach } from 'vitest';
88
import {
9+
buildShellExecWarnings,
910
checkArgumentSafety,
1011
checkCommandPermissions,
12+
COMMAND_SUBSTITUTION_WARNING,
1113
escapeShellArg,
1214
getCommandRoots,
1315
getShellConfiguration,
@@ -1098,3 +1100,54 @@ describe('checkArgumentSafety', () => {
10981100
});
10991101
});
11001102
});
1103+
1104+
// Regression coverage for PR #4386 R4 (cid 3293078758): the dual-check
1105+
// branch of `buildShellExecWarnings` — where the stripped form has no
1106+
// substitution but the raw command does (e.g. env-prefix wrapped in
1107+
// `bash -c`) — was untested. Without coverage, removing the
1108+
// `|| detectCommandSubstitution(rawCommand)` clause would not regress
1109+
// any test in this file.
1110+
describe('buildShellExecWarnings', () => {
1111+
it('returns undefined when neither stripped nor raw command has substitution', () => {
1112+
expect(
1113+
buildShellExecWarnings('npm install', 'npm install'),
1114+
).toBeUndefined();
1115+
});
1116+
1117+
it('returns the substitution warning when the stripped command has $()', () => {
1118+
const result = buildShellExecWarnings(
1119+
'echo $(cat secret)',
1120+
'echo $(cat secret)',
1121+
);
1122+
expect(result).toEqual([COMMAND_SUBSTITUTION_WARNING]);
1123+
});
1124+
1125+
it('returns the substitution warning when the raw command has substitution but the stripped form does not (env-prefix wrapper case)', () => {
1126+
// `stripShellWrapper("FOO=$(cat secret) bash -c 'echo ok'")` yields
1127+
// `echo ok` — no substitution — so the `|| rawCommand` branch is the
1128+
// only thing that fires the warning here.
1129+
const raw = `FOO=$(cat secret) bash -c 'echo ok'`;
1130+
const stripped = stripShellWrapper(raw);
1131+
// Sanity-check the precondition before asserting on the helper.
1132+
expect(stripped).not.toContain('$(');
1133+
1134+
expect(buildShellExecWarnings(stripped, raw)).toEqual([
1135+
COMMAND_SUBSTITUTION_WARNING,
1136+
]);
1137+
});
1138+
1139+
it('returns the warning for backtick substitution in either input', () => {
1140+
expect(buildShellExecWarnings('echo `whoami`', 'echo `whoami`')).toEqual([
1141+
COMMAND_SUBSTITUTION_WARNING,
1142+
]);
1143+
});
1144+
1145+
it('returns the warning for process substitution <(...)', () => {
1146+
expect(
1147+
buildShellExecWarnings(
1148+
'diff <(ls /a) <(ls /b)',
1149+
'diff <(ls /a) <(ls /b)',
1150+
),
1151+
).toEqual([COMMAND_SUBSTITUTION_WARNING]);
1152+
});
1153+
});

0 commit comments

Comments
 (0)