mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-12 15:10:59 -07:00
refactor(test): finish createHookScript refactor and cleanup
- Ensure all hook tests use rig.createHookScript for consistent path normalization. - Remove unused writeFileSync and readFileSync imports from integration tests. - Fix assertions in 'input override' test to match new .cjs extension and be more robust.
This commit is contained in:
@@ -7,7 +7,6 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
import { join } from 'node:path';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
describe('Hooks Agent Flow', () => {
|
||||
let rig: TestRig;
|
||||
@@ -49,11 +48,10 @@ describe('Hooks Agent Flow', () => {
|
||||
console.error('DEBUG: BeforeAgent hook executed');
|
||||
`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'before_agent_context.cjs').replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
const scriptPath = rig.createHookScript(
|
||||
'before_agent_context.cjs',
|
||||
hookScript,
|
||||
);
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
|
||||
await rig.configure({
|
||||
settings: {
|
||||
@@ -118,11 +116,10 @@ describe('Hooks Agent Flow', () => {
|
||||
}
|
||||
`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'after_agent_verify.cjs').replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
const scriptPath = rig.createHookScript(
|
||||
'after_agent_verify.cjs',
|
||||
hookScript,
|
||||
);
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
|
||||
await rig.configure({
|
||||
settings: {
|
||||
@@ -182,11 +179,10 @@ describe('Hooks Agent Flow', () => {
|
||||
fs.writeFileSync('${messageCountFile}', JSON.stringify(counts));
|
||||
console.log(JSON.stringify({ decision: 'allow' }));
|
||||
`;
|
||||
const beforeModelScriptPath = join(
|
||||
rig.testDir!,
|
||||
const beforeModelScriptPath = rig.createHookScript(
|
||||
'before_model_counter.cjs',
|
||||
).replace(/\\/g, '/');
|
||||
writeFileSync(beforeModelScriptPath, beforeModelScript);
|
||||
beforeModelScript,
|
||||
);
|
||||
|
||||
const afterAgentScript = `
|
||||
console.log(JSON.stringify({
|
||||
@@ -198,11 +194,10 @@ describe('Hooks Agent Flow', () => {
|
||||
}
|
||||
}));
|
||||
`;
|
||||
const afterAgentScriptPath = join(
|
||||
rig.testDir!,
|
||||
const afterAgentScriptPath = rig.createHookScript(
|
||||
'after_agent_clear.cjs',
|
||||
).replace(/\\/g, '/');
|
||||
writeFileSync(afterAgentScriptPath, afterAgentScript);
|
||||
afterAgentScript,
|
||||
);
|
||||
|
||||
await rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -270,18 +265,16 @@ describe('Hooks Agent Flow', () => {
|
||||
);
|
||||
|
||||
const beforeAgentScript = "console.log('BeforeAgent Fired')";
|
||||
const beforeAgentScriptPath = join(
|
||||
rig.testDir!,
|
||||
const beforeAgentScriptPath = rig.createHookScript(
|
||||
'before_agent_loop.cjs',
|
||||
).replace(/\\/g, '/');
|
||||
writeFileSync(beforeAgentScriptPath, beforeAgentScript);
|
||||
beforeAgentScript,
|
||||
);
|
||||
|
||||
const afterAgentScript = "console.log('AfterAgent Fired')";
|
||||
const afterAgentScriptPath = join(
|
||||
rig.testDir!,
|
||||
const afterAgentScriptPath = rig.createHookScript(
|
||||
'after_agent_loop.cjs',
|
||||
).replace(/\\/g, '/');
|
||||
writeFileSync(afterAgentScriptPath, afterAgentScript);
|
||||
afterAgentScript,
|
||||
);
|
||||
|
||||
await rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig, poll } from './test-helper.js';
|
||||
import { join } from 'node:path';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
describe('Hooks System Integration', () => {
|
||||
let rig: TestRig;
|
||||
@@ -25,9 +24,8 @@ describe('Hooks System Integration', () => {
|
||||
describe('Command Hooks - Blocking Behavior', () => {
|
||||
it('should block tool execution when hook returns block decision', async () => {
|
||||
rig.setup('should block tool execution when hook returns block decision');
|
||||
const scriptPath = join(rig.testDir!, 'block_tool.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'block_tool.cjs',
|
||||
"console.log(JSON.stringify({decision: 'block', reason: 'File writing blocked by security policy'}))",
|
||||
);
|
||||
|
||||
@@ -84,9 +82,8 @@ describe('Hooks System Integration', () => {
|
||||
rig.setup(
|
||||
'should block tool execution and use stderr as reason when hook exits with code 2',
|
||||
);
|
||||
const scriptPath = join(rig.testDir!, 'block_tool_stderr.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'block_tool_stderr.cjs',
|
||||
"process.stderr.write('File writing blocked by security policy'); process.exit(2)",
|
||||
);
|
||||
|
||||
@@ -146,9 +143,8 @@ describe('Hooks System Integration', () => {
|
||||
|
||||
it('should allow tool execution when hook returns allow decision', async () => {
|
||||
rig.setup('should allow tool execution when hook returns allow decision');
|
||||
const scriptPath = join(rig.testDir!, 'allow_tool.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'allow_tool.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', reason: 'File writing approved'}))",
|
||||
);
|
||||
|
||||
@@ -199,12 +195,11 @@ describe('Hooks System Integration', () => {
|
||||
describe('Command Hooks - Additional Context', () => {
|
||||
it('should add additional context from AfterTool hooks', async () => {
|
||||
rig.setup('should add additional context from AfterTool hooks');
|
||||
const scriptPath = join(rig.testDir!, 'after_tool_context.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'after_tool_context.cjs',
|
||||
"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'Security scan: File content appears safe'}}))",
|
||||
);
|
||||
const command = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||
const command = `node "${scriptPath}"`;
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
@@ -282,8 +277,10 @@ console.log(JSON.stringify({
|
||||
}
|
||||
}));`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'before_model_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const scriptPath = rig.createHookScript(
|
||||
'before_model_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
@@ -296,7 +293,7 @@ console.log(JSON.stringify({
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -341,8 +338,10 @@ console.log(JSON.stringify({
|
||||
decision: "deny",
|
||||
reason: "Model execution blocked by security policy"
|
||||
}));`;
|
||||
const scriptPath = join(rig.testDir!, 'before_model_deny_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const scriptPath = rig.createHookScript(
|
||||
'before_model_deny_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
@@ -355,7 +354,7 @@ console.log(JSON.stringify({
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -383,8 +382,10 @@ console.log(JSON.stringify({
|
||||
decision: "block",
|
||||
reason: "Model execution blocked by security policy"
|
||||
}));`;
|
||||
const scriptPath = join(rig.testDir!, 'before_model_block_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const scriptPath = rig.createHookScript(
|
||||
'before_model_block_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
@@ -397,7 +398,7 @@ console.log(JSON.stringify({
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -450,8 +451,10 @@ console.log(JSON.stringify({
|
||||
}
|
||||
}));`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'after_model_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const scriptPath = rig.createHookScript(
|
||||
'after_model_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
@@ -464,7 +467,7 @@ console.log(JSON.stringify({
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -508,8 +511,10 @@ console.log(JSON.stringify({
|
||||
}
|
||||
}
|
||||
}));`;
|
||||
const scriptPath = join(rig.testDir!, 'before_tool_selection_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const scriptPath = rig.createHookScript(
|
||||
'before_tool_selection_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
@@ -523,7 +528,7 @@ console.log(JSON.stringify({
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -577,8 +582,10 @@ console.log(JSON.stringify({
|
||||
}
|
||||
}));`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'before_agent_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const scriptPath = rig.createHookScript(
|
||||
'before_agent_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
@@ -591,7 +598,7 @@ console.log(JSON.stringify({
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -616,12 +623,11 @@ console.log(JSON.stringify({
|
||||
it('should handle notification hooks for tool permissions', async () => {
|
||||
rig.setup('should handle notification hooks for tool permissions');
|
||||
// Create script for hook (works on both Unix and Windows)
|
||||
const scriptPath = join(rig.testDir!, 'notification_hook.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'notification_hook.cjs',
|
||||
"console.log(JSON.stringify({suppressOutput: false, systemMessage: 'Permission request logged by security hook'}))",
|
||||
);
|
||||
const hookCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||
const hookCommand = `node "${scriptPath}"`;
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -717,19 +723,17 @@ console.log(JSON.stringify({
|
||||
it('should execute hooks sequentially when configured', async () => {
|
||||
rig.setup('should execute hooks sequentially when configured');
|
||||
// Create script for hooks (works on both Unix and Windows)
|
||||
const script1Path = join(rig.testDir!, 'hook1.cjs');
|
||||
writeFileSync(
|
||||
script1Path,
|
||||
const script1Path = rig.createHookScript(
|
||||
'hook1.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'Step 1: Initial validation passed.'}}))",
|
||||
);
|
||||
const script2Path = join(rig.testDir!, 'hook2.cjs');
|
||||
writeFileSync(
|
||||
script2Path,
|
||||
const script2Path = rig.createHookScript(
|
||||
'hook2.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'Step 2: Security check completed.'}}))",
|
||||
);
|
||||
|
||||
const hook1Command = `node "${script1Path.replace(/\\/g, '/')}"`;
|
||||
const hook2Command = `node "${script2Path.replace(/\\/g, '/')}"`;
|
||||
const hook1Command = `node "${script1Path}"`;
|
||||
const hook2Command = `node "${script2Path}"`;
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -815,8 +819,10 @@ try {
|
||||
console.log(JSON.stringify({decision: "block", reason: "Invalid JSON"}));
|
||||
}`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'input_validation_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const scriptPath = rig.createHookScript(
|
||||
'input_validation_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
@@ -829,7 +835,7 @@ try {
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -860,9 +866,8 @@ try {
|
||||
rig.setup(
|
||||
'should treat mixed stdout (text + JSON) as system message and allow execution when exit code is 0',
|
||||
);
|
||||
const scriptPath = join(rig.testDir!, 'mixed_stdout.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'mixed_stdout.cjs',
|
||||
"console.log('Pollution'); console.log(JSON.stringify({decision: 'deny', reason: 'Should be ignored'}))",
|
||||
);
|
||||
|
||||
@@ -912,25 +917,22 @@ try {
|
||||
it('should handle hooks for all major event types', async () => {
|
||||
rig.setup('should handle hooks for all major event types');
|
||||
// Create scripts for hooks (works on both Unix and Windows)
|
||||
const beforeToolScript = join(rig.testDir!, 'before_tool_all.cjs');
|
||||
writeFileSync(
|
||||
beforeToolScript,
|
||||
const beforeToolScript = rig.createHookScript(
|
||||
'before_tool_all.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'BeforeTool: File operation logged'}))",
|
||||
);
|
||||
const afterToolScript = join(rig.testDir!, 'after_tool_all.cjs');
|
||||
writeFileSync(
|
||||
afterToolScript,
|
||||
const afterToolScript = rig.createHookScript(
|
||||
'after_tool_all.cjs',
|
||||
"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'AfterTool: Operation completed successfully'}}))",
|
||||
);
|
||||
const beforeAgentScript = join(rig.testDir!, 'before_agent_all.cjs');
|
||||
writeFileSync(
|
||||
beforeAgentScript,
|
||||
const beforeAgentScript = rig.createHookScript(
|
||||
'before_agent_all.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'BeforeAgent: User request processed'}}))",
|
||||
);
|
||||
|
||||
const beforeToolCommand = `node "${beforeToolScript.replace(/\\/g, '/')}"`;
|
||||
const afterToolCommand = `node "${afterToolScript.replace(/\\/g, '/')}"`;
|
||||
const beforeAgentCommand = `node "${beforeAgentScript.replace(/\\/g, '/')}"`;
|
||||
const beforeToolCommand = `node "${beforeToolScript}"`;
|
||||
const afterToolCommand = `node "${afterToolScript}"`;
|
||||
const beforeAgentCommand = `node "${beforeAgentScript}"`;
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -1038,16 +1040,17 @@ try {
|
||||
describe('Hook Error Handling', () => {
|
||||
it('should handle hook failures gracefully', async () => {
|
||||
rig.setup('should handle hook failures gracefully');
|
||||
const failingScript = join(rig.testDir!, 'failing_hook.cjs');
|
||||
writeFileSync(failingScript, 'process.exit(1)');
|
||||
const workingScript = join(rig.testDir!, 'working_hook.cjs');
|
||||
writeFileSync(
|
||||
workingScript,
|
||||
const failingScript = rig.createHookScript(
|
||||
'failing_hook.cjs',
|
||||
'process.exit(1)',
|
||||
);
|
||||
const workingScript = rig.createHookScript(
|
||||
'working_hook.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', reason: 'Working hook succeeded'}))",
|
||||
);
|
||||
|
||||
const failingCommand = `node "${failingScript.replace(/\\/g, '/')}"`;
|
||||
const workingCommand = `node "${workingScript.replace(/\\/g, '/')}"`;
|
||||
const failingCommand = `node "${failingScript}"`;
|
||||
const workingCommand = `node "${workingScript}"`;
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -1100,12 +1103,11 @@ try {
|
||||
describe('Hook Telemetry and Observability', () => {
|
||||
it('should generate telemetry events for hook executions', async () => {
|
||||
rig.setup('should generate telemetry events for hook executions');
|
||||
const scriptPath = join(rig.testDir!, 'telemetry_hook.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'telemetry_hook.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', reason: 'Telemetry test hook'}))",
|
||||
);
|
||||
const hookCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||
const hookCommand = `node "${scriptPath}"`;
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -1147,12 +1149,11 @@ try {
|
||||
describe('Session Lifecycle Hooks', () => {
|
||||
it('should fire SessionStart hook on app startup', async () => {
|
||||
rig.setup('should fire SessionStart hook on app startup');
|
||||
const scriptPath = join(rig.testDir!, 'session_start.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'session_start.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting on startup'}))",
|
||||
);
|
||||
const sessionStartCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||
const sessionStartCommand = `node "${scriptPath}"`;
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -1229,8 +1230,10 @@ console.log(JSON.stringify({
|
||||
),
|
||||
});
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'session_start_context_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const scriptPath = rig.createHookScript(
|
||||
'session_start_context_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
@@ -1244,7 +1247,7 @@ console.log(JSON.stringify({
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -1309,11 +1312,10 @@ console.log(JSON.stringify({
|
||||
),
|
||||
});
|
||||
|
||||
const scriptPath = join(
|
||||
rig.testDir!,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'session_start_interactive_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
@@ -1327,7 +1329,7 @@ console.log(JSON.stringify({
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -1376,19 +1378,17 @@ console.log(JSON.stringify({
|
||||
'should fire SessionEnd and SessionStart hooks on /clear command',
|
||||
);
|
||||
// Create script for hooks (works on both Unix and Windows)
|
||||
const endScriptPath = join(rig.testDir!, 'session_end_clear.cjs');
|
||||
writeFileSync(
|
||||
endScriptPath,
|
||||
const endScriptPath = rig.createHookScript(
|
||||
'session_end_clear.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session ending due to clear'}))",
|
||||
);
|
||||
const startScriptPath = join(rig.testDir!, 'session_start_clear.cjs');
|
||||
writeFileSync(
|
||||
startScriptPath,
|
||||
const startScriptPath = rig.createHookScript(
|
||||
'session_start_clear.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting after clear'}))",
|
||||
);
|
||||
|
||||
const sessionEndCommand = `node "${endScriptPath.replace(/\\/g, '/')}"`;
|
||||
const sessionStartCommand = `node "${startScriptPath.replace(/\\/g, '/')}"`;
|
||||
const sessionEndCommand = `node "${endScriptPath}"`;
|
||||
const sessionStartCommand = `node "${startScriptPath}"`;
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -1560,12 +1560,11 @@ console.log(JSON.stringify({
|
||||
describe('Compression Hooks', () => {
|
||||
it('should fire PreCompress hook on automatic compression', async () => {
|
||||
rig.setup('should fire PreCompress hook on automatic compression');
|
||||
const scriptPath = join(rig.testDir!, 'pre_compress.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'pre_compress.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'PreCompress hook executed for automatic compression'}))",
|
||||
);
|
||||
const preCompressCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||
const preCompressCommand = `node "${scriptPath}"`;
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -1634,12 +1633,11 @@ console.log(JSON.stringify({
|
||||
rig.setup(
|
||||
'should fire SessionEnd hook on graceful exit in non-interactive mode',
|
||||
);
|
||||
const scriptPath = join(rig.testDir!, 'session_end_exit.cjs');
|
||||
writeFileSync(
|
||||
scriptPath,
|
||||
const scriptPath = rig.createHookScript(
|
||||
'session_end_exit.cjs',
|
||||
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'SessionEnd hook executed on exit'}))",
|
||||
);
|
||||
const sessionEndCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||
const sessionEndCommand = `node "${scriptPath}"`;
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
@@ -1740,18 +1738,15 @@ console.log(JSON.stringify({decision: "allow", systemMessage: "Enabled hook exec
|
||||
const disabledHookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook should not execute", reason: "This hook should be disabled"}));`;
|
||||
|
||||
const enabledPath = join(rig.testDir!, 'enabled_hook.cjs').replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
const enabledPath = rig.createHookScript(
|
||||
'enabled_hook.cjs',
|
||||
enabledHookScript,
|
||||
);
|
||||
const disabledPath = join(rig.testDir!, 'disabled_hook.cjs').replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
const disabledPath = rig.createHookScript(
|
||||
'disabled_hook.cjs',
|
||||
disabledHookScript,
|
||||
);
|
||||
|
||||
writeFileSync(enabledPath, enabledHookScript);
|
||||
writeFileSync(disabledPath, disabledHookScript);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
hooksConfig: {
|
||||
@@ -1824,18 +1819,15 @@ console.log(JSON.stringify({decision: "allow", systemMessage: "Active hook execu
|
||||
const disabledHookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook should not execute", reason: "This hook is disabled"}));`;
|
||||
|
||||
const activePath = join(rig.testDir!, 'active_hook.cjs').replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
const activePath = rig.createHookScript(
|
||||
'active_hook.cjs',
|
||||
activeHookScript,
|
||||
);
|
||||
const disabledPath = join(rig.testDir!, 'disabled_hook.cjs').replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
const disabledPath = rig.createHookScript(
|
||||
'disabled_hook.cjs',
|
||||
disabledHookScript,
|
||||
);
|
||||
|
||||
writeFileSync(activePath, activeHookScript);
|
||||
writeFileSync(disabledPath, disabledHookScript);
|
||||
|
||||
rig.configure({
|
||||
settings: {
|
||||
hooksConfig: {
|
||||
@@ -1930,13 +1922,10 @@ console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook sho
|
||||
hookOutput,
|
||||
)}));`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'input_override_hook.js');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
|
||||
// Ensure path is properly escaped for command line usage on all platforms
|
||||
// On Windows, backslashes in the command string need to be handled carefully
|
||||
// Using forward slashes works well with Node.js on all platforms
|
||||
const commandPath = scriptPath.replace(/\\/g, '/');
|
||||
const commandPath = rig.createHookScript(
|
||||
'input_override_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
// 2. Full setup with settings and fake responses
|
||||
rig.configure({
|
||||
@@ -1990,9 +1979,9 @@ console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook sho
|
||||
expect(hookTelemetryFound).toBeTruthy();
|
||||
|
||||
const hookLogs = rig.readHookLogs();
|
||||
expect(hookLogs.length).toBe(1);
|
||||
expect(hookLogs.length).toBeGreaterThanOrEqual(1);
|
||||
expect(hookLogs[0].hookCall.hook_name).toContain(
|
||||
'input_override_hook.js',
|
||||
'input_override_hook.cjs',
|
||||
);
|
||||
|
||||
// 4. Verify that the agent didn't try to work-around the hook input change
|
||||
@@ -2021,9 +2010,10 @@ console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook sho
|
||||
)}));`;
|
||||
|
||||
rig.setup('should stop agent execution via BeforeTool hook');
|
||||
const scriptPath = join(rig.testDir!, 'before_tool_stop_hook.js');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const commandPath = scriptPath.replace(/\\/g, '/');
|
||||
const commandPath = rig.createHookScript(
|
||||
'before_tool_stop_hook.cjs',
|
||||
hookScript,
|
||||
);
|
||||
|
||||
rig.configure({
|
||||
fakeResponsesPath: join(
|
||||
|
||||
@@ -387,6 +387,19 @@ export class TestRig {
|
||||
this._createSettingsFile(options.settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hook script file and returns a normalized path suitable for cross-platform execution.
|
||||
*/
|
||||
createHookScript(fileName: string, content: string): string {
|
||||
if (!this.testDir) {
|
||||
throw new Error('TestRig must be setup before calling createHookScript');
|
||||
}
|
||||
const scriptPath = join(this.testDir, fileName);
|
||||
writeFileSync(scriptPath, content);
|
||||
// Return a path normalized for use in shell commands across platforms.
|
||||
return scriptPath.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
private _createSettingsFile(overrideSettings?: Record<string, unknown>) {
|
||||
const projectGeminiDir = join(this.testDir!, GEMINI_DIR);
|
||||
mkdirSync(projectGeminiDir, { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user