This commit is contained in:
Paul Wang 2026-02-03 15:16:41 -08:00
parent 349657d5bd
commit b3695f781e
2 changed files with 163 additions and 19 deletions

View File

@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import assert from 'assert';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
import { findHookCommandSelection } from '../../../browser/promptSyntax/hookUtils.js';
suite('hookUtils', () => {
ensureNoDisposablesAreLeakedInTestSuite();
suite('findHookCommandSelection', () => {
test('finds command field in first hook entry', () => {
const content = '{\n\t"sessionStart": [\n\t\t{\n\t\t\t"command": "echo hello"\n\t\t}\n\t]\n}';
const result = findHookCommandSelection(content, 'sessionStart', 0, 'command');
assert.deepStrictEqual(result, {
startLineNumber: 4,
startColumn: 16,
endLineNumber: 4,
endColumn: 26
});
});
test('finds command field in second hook entry', () => {
const content = '{\n\t"sessionStart": [\n\t\t{\n\t\t\t"command": "first"\n\t\t},\n\t\t{\n\t\t\t"command": "second"\n\t\t}\n\t]\n}';
const result = findHookCommandSelection(content, 'sessionStart', 1, 'command');
assert.deepStrictEqual(result, {
startLineNumber: 7,
startColumn: 16,
endLineNumber: 7,
endColumn: 22
});
});
test('finds bash field for platform-specific hook', () => {
const content = '{\n\t"preToolUse": [\n\t\t{\n\t\t\t"bash": "bash command",\n\t\t\t"powershell": "powershell command"\n\t\t}\n\t]\n}';
const result = findHookCommandSelection(content, 'preToolUse', 0, 'bash');
assert.deepStrictEqual(result, {
startLineNumber: 4,
startColumn: 12,
endLineNumber: 4,
endColumn: 24
});
});
test('finds powershell field for platform-specific hook', () => {
const content = '{\n\t"preToolUse": [\n\t\t{\n\t\t\t"bash": "bash command",\n\t\t\t"powershell": "powershell command"\n\t\t}\n\t]\n}';
const result = findHookCommandSelection(content, 'preToolUse', 0, 'powershell');
assert.deepStrictEqual(result, {
startLineNumber: 5,
startColumn: 18,
endLineNumber: 5,
endColumn: 36
});
});
test('returns undefined for non-existent hook type', () => {
const content = '{\n\t"sessionStart": [\n\t\t{\n\t\t\t"command": "echo hello"\n\t\t}\n\t]\n}';
const result = findHookCommandSelection(content, 'nonExistent', 0, 'command');
assert.strictEqual(result, undefined);
});
test('returns undefined for out-of-bounds index', () => {
const content = '{\n\t"sessionStart": [\n\t\t{\n\t\t\t"command": "echo hello"\n\t\t}\n\t]\n}';
const result = findHookCommandSelection(content, 'sessionStart', 5, 'command');
assert.strictEqual(result, undefined);
});
test('returns undefined for non-existent field', () => {
const content = '{\n\t"sessionStart": [\n\t\t{\n\t\t\t"command": "echo hello"\n\t\t}\n\t]\n}';
const result = findHookCommandSelection(content, 'sessionStart', 0, 'bash');
assert.strictEqual(result, undefined);
});
test('returns undefined for invalid JSON', () => {
const content = '{ invalid json }';
const result = findHookCommandSelection(content, 'sessionStart', 0, 'command');
assert.strictEqual(result, undefined);
});
test('returns undefined for empty content', () => {
const result = findHookCommandSelection('', 'sessionStart', 0, 'command');
assert.strictEqual(result, undefined);
});
test('handles command with special characters', () => {
const content = '{\n\t"sessionStart": [\n\t\t{\n\t\t\t"command": "echo \\"quoted\\""\n\t\t}\n\t]\n}';
const result = findHookCommandSelection(content, 'sessionStart', 0, 'command');
assert.deepStrictEqual(result, {
startLineNumber: 4,
startColumn: 16,
endLineNumber: 4,
endColumn: 32
});
});
test('works with different hook types', () => {
const content = '{\n\t"userPromptSubmitted": [\n\t\t{\n\t\t\t"command": "validate"\n\t\t}\n\t],\n\t"postToolUse": [\n\t\t{\n\t\t\t"command": "cleanup"\n\t\t}\n\t]\n}';
const result1 = findHookCommandSelection(content, 'userPromptSubmitted', 0, 'command');
assert.deepStrictEqual(result1, {
startLineNumber: 4,
startColumn: 16,
endLineNumber: 4,
endColumn: 24
});
const result2 = findHookCommandSelection(content, 'postToolUse', 0, 'command');
assert.deepStrictEqual(result2, {
startLineNumber: 9,
startColumn: 16,
endLineNumber: 9,
endColumn: 23
});
});
test('handles hooks with additional properties', () => {
const content = '{\n\t"sessionStart": [\n\t\t{\n\t\t\t"command": "my-command",\n\t\t\t"cwd": "/some/path",\n\t\t\t"timeoutSec": 30\n\t\t}\n\t]\n}';
const result = findHookCommandSelection(content, 'sessionStart', 0, 'command');
assert.deepStrictEqual(result, {
startLineNumber: 4,
startColumn: 16,
endLineNumber: 4,
endColumn: 26
});
});
});
});

View File

@ -99,24 +99,27 @@ suite('HookSchema', () => {
});
});
test('empty command returns undefined', () => {
test('empty command returns object without command', () => {
const result = resolveHookCommand({
type: 'command',
command: ''
}, workspaceRoot, userHome);
assert.strictEqual(result, undefined);
assert.deepStrictEqual(result, {
type: 'command',
cwd: workspaceRoot
});
});
});
suite('bash shorthand', () => {
test('resolves bash to command', () => {
test('preserves bash property', () => {
const result = resolveHookCommand({
type: 'command',
bash: 'echo "hello world"'
}, workspaceRoot, userHome);
assert.deepStrictEqual(result, {
type: 'command',
command: 'bash -c "echo \\"hello world\\""',
bash: 'echo "hello world"',
cwd: workspaceRoot
});
});
@ -130,30 +133,33 @@ suite('HookSchema', () => {
}, workspaceRoot, userHome);
assert.deepStrictEqual(result, {
type: 'command',
command: 'bash -c "./test.sh"',
bash: './test.sh',
cwd: URI.file('/workspace/scripts'),
env: { DEBUG: '1' }
});
});
test('empty bash returns undefined', () => {
test('empty bash returns object without bash', () => {
const result = resolveHookCommand({
type: 'command',
bash: ''
}, workspaceRoot, userHome);
assert.strictEqual(result, undefined);
assert.deepStrictEqual(result, {
type: 'command',
cwd: workspaceRoot
});
});
});
suite('powershell shorthand', () => {
test('resolves powershell to command', () => {
test('preserves powershell property', () => {
const result = resolveHookCommand({
type: 'command',
powershell: 'Write-Host "hello"'
}, workspaceRoot, userHome);
assert.deepStrictEqual(result, {
type: 'command',
command: 'powershell -Command "Write-Host \\"hello\\""',
powershell: 'Write-Host "hello"',
cwd: workspaceRoot
});
});
@ -166,23 +172,26 @@ suite('HookSchema', () => {
}, workspaceRoot, userHome);
assert.deepStrictEqual(result, {
type: 'command',
command: 'powershell -Command "Get-Process"',
powershell: 'Get-Process',
cwd: workspaceRoot,
timeoutSec: 30
});
});
test('empty powershell returns undefined', () => {
test('empty powershell returns object without powershell', () => {
const result = resolveHookCommand({
type: 'command',
powershell: ''
}, workspaceRoot, userHome);
assert.strictEqual(result, undefined);
assert.deepStrictEqual(result, {
type: 'command',
cwd: workspaceRoot
});
});
});
suite('priority when multiple specified', () => {
test('command takes precedence over bash', () => {
suite('multiple properties specified', () => {
test('preserves both command and bash', () => {
const result = resolveHookCommand({
type: 'command',
command: 'direct-command',
@ -191,11 +200,12 @@ suite('HookSchema', () => {
assert.deepStrictEqual(result, {
type: 'command',
command: 'direct-command',
bash: 'bash-script.sh',
cwd: workspaceRoot
});
});
test('command takes precedence over powershell', () => {
test('preserves both command and powershell', () => {
const result = resolveHookCommand({
type: 'command',
command: 'direct-command',
@ -204,11 +214,12 @@ suite('HookSchema', () => {
assert.deepStrictEqual(result, {
type: 'command',
command: 'direct-command',
powershell: 'ps-script.ps1',
cwd: workspaceRoot
});
});
test('bash takes precedence over powershell when no command', () => {
test('preserves both bash and powershell when no command', () => {
const result = resolveHookCommand({
type: 'command',
bash: 'bash-script.sh',
@ -216,7 +227,8 @@ suite('HookSchema', () => {
}, workspaceRoot, userHome);
assert.deepStrictEqual(result, {
type: 'command',
command: 'bash -c "bash-script.sh"',
bash: 'bash-script.sh',
powershell: 'ps-script.ps1',
cwd: workspaceRoot
});
});
@ -265,12 +277,15 @@ suite('HookSchema', () => {
assert.strictEqual(result, undefined);
});
test('no command/bash/powershell returns undefined', () => {
test('no command/bash/powershell returns object with just type and cwd', () => {
const result = resolveHookCommand({
type: 'command',
cwd: '/workspace'
}, workspaceRoot, userHome);
assert.strictEqual(result, undefined);
assert.deepStrictEqual(result, {
type: 'command',
cwd: URI.file('/workspace')
});
});
test('ignores non-string cwd', () => {