mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-17 20:54:53 +00:00
fix: prevent bogus shell permission rules in tests
This commit is contained in:
parent
fbf5ed57d6
commit
fcd31e2adf
5 changed files with 64 additions and 2 deletions
|
|
@ -9,4 +9,10 @@ if (process.env['NO_COLOR'] !== undefined) {
|
|||
delete process.env['NO_COLOR'];
|
||||
}
|
||||
|
||||
// Avoid writing per-session debug log files during CLI tests.
|
||||
// Individual tests can still opt in by overriding this env var explicitly.
|
||||
if (process.env['QWEN_DEBUG_LOG_FILE'] === undefined) {
|
||||
process.env['QWEN_DEBUG_LOG_FILE'] = '0';
|
||||
}
|
||||
|
||||
import './src/test-utils/customMatchers.js';
|
||||
|
|
|
|||
|
|
@ -949,6 +949,24 @@ describe('ShellTool', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should not surface file descriptor redirects as standalone commands in confirmation details', async () => {
|
||||
const params = {
|
||||
command: 'npm run build 2>&1 | head -100',
|
||||
is_background: false,
|
||||
};
|
||||
const invocation = shellTool.build(params);
|
||||
|
||||
const permission = await invocation.getDefaultPermission();
|
||||
expect(permission).toBe('ask');
|
||||
|
||||
const details = (await invocation.getConfirmationDetails(
|
||||
new AbortController().signal,
|
||||
)) as { rootCommand: string; permissionRules: string[] };
|
||||
|
||||
expect(details.rootCommand).toBe('npm');
|
||||
expect(details.permissionRules).toEqual(['Bash(npm run *)']);
|
||||
});
|
||||
|
||||
it('should throw an error if validation fails', () => {
|
||||
expect(() =>
|
||||
shellTool.build({ command: '', is_background: false }),
|
||||
|
|
|
|||
|
|
@ -439,6 +439,16 @@ describe('getCommandRoots', () => {
|
|||
const result = getCommandRoots('ls\n\ngrep foo');
|
||||
expect(result).toEqual(['ls', 'grep']);
|
||||
});
|
||||
|
||||
it('should not treat file descriptor redirection as a command separator', () => {
|
||||
const result = getCommandRoots('npm run build 2>&1 | head -100');
|
||||
expect(result).toEqual(['npm', 'head']);
|
||||
});
|
||||
|
||||
it('should not treat >| redirection as a pipeline separator', () => {
|
||||
const result = getCommandRoots('echo hello >| out.txt');
|
||||
expect(result).toEqual(['echo']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stripShellWrapper', () => {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,16 @@ export function splitCommands(command: string): string[] {
|
|||
let inDoubleQuotes = false;
|
||||
let i = 0;
|
||||
|
||||
const previousNonWhitespaceChar = (index: number): string | undefined => {
|
||||
for (let j = index - 1; j >= 0; j--) {
|
||||
const ch = command[j];
|
||||
if (ch && !/\s/.test(ch)) {
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
while (i < command.length) {
|
||||
const char = command[i];
|
||||
const nextChar = command[i + 1];
|
||||
|
|
@ -145,14 +155,30 @@ export function splitCommands(command: string): string[] {
|
|||
if (!inSingleQuotes && !inDoubleQuotes) {
|
||||
if (
|
||||
(char === '&' && nextChar === '&') ||
|
||||
(char === '|' && nextChar === '|')
|
||||
(char === '|' && (nextChar === '|' || nextChar === '&'))
|
||||
) {
|
||||
commands.push(currentCommand.trim());
|
||||
currentCommand = '';
|
||||
i++; // Skip the next character
|
||||
} else if (char === ';' || char === '&' || char === '|') {
|
||||
} else if (char === ';') {
|
||||
commands.push(currentCommand.trim());
|
||||
currentCommand = '';
|
||||
} else if (char === '&') {
|
||||
const prevChar = previousNonWhitespaceChar(i);
|
||||
if (prevChar === '>' || prevChar === '<') {
|
||||
currentCommand += char;
|
||||
} else {
|
||||
commands.push(currentCommand.trim());
|
||||
currentCommand = '';
|
||||
}
|
||||
} else if (char === '|') {
|
||||
const prevChar = previousNonWhitespaceChar(i);
|
||||
if (prevChar === '>') {
|
||||
currentCommand += char;
|
||||
} else {
|
||||
commands.push(currentCommand.trim());
|
||||
currentCommand = '';
|
||||
}
|
||||
} else if (char === '\r' && nextChar === '\n') {
|
||||
// Windows-style \r\n newline - treat as command separator
|
||||
commands.push(currentCommand.trim());
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ describe('AcpConnection readTextFile error mapping', () => {
|
|||
const prompt = vi.fn().mockResolvedValue({});
|
||||
const onEndTurn = vi.fn();
|
||||
const conn = new AcpConnection() as unknown as {
|
||||
child: { killed: boolean; exitCode: number | null } | null;
|
||||
sdkConnection: {
|
||||
prompt: (params: {
|
||||
sessionId: string;
|
||||
|
|
@ -92,6 +93,7 @@ describe('AcpConnection readTextFile error mapping', () => {
|
|||
},
|
||||
];
|
||||
|
||||
conn.child = { killed: false, exitCode: null };
|
||||
conn.sdkConnection = { prompt };
|
||||
conn.sessionId = 'session-1';
|
||||
conn.onEndTurn = onEndTurn;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue