mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 10:01:29 -07:00
test(integration): fix windows integration issues for hook tests
Fixes several issues preventing integration tests from passing on Windows: - Replaces brittle inline 'node -e' commands with script files to avoid shell quoting issues in PowerShell. - Normalizes paths to forward slashes to prevent escape sequence misinterpretation in generated scripts. - Ensures rig.setup() is called before accessing rig.testDir to properly initialize test environment. - Corrects hooks settings structure (enabled moved to hooksConfig) to match schema validation.
This commit is contained in:
@@ -48,7 +48,10 @@ describe('Hooks Agent Flow', () => {
|
|||||||
console.error('DEBUG: BeforeAgent hook executed');
|
console.error('DEBUG: BeforeAgent hook executed');
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const scriptPath = join(rig.testDir!, 'before_agent_context.cjs');
|
const scriptPath = join(rig.testDir!, 'before_agent_context.cjs').replace(
|
||||||
|
/\\/g,
|
||||||
|
'/',
|
||||||
|
);
|
||||||
writeFileSync(scriptPath, hookScript);
|
writeFileSync(scriptPath, hookScript);
|
||||||
|
|
||||||
await rig.setup('should inject additional context via BeforeAgent hook', {
|
await rig.setup('should inject additional context via BeforeAgent hook', {
|
||||||
@@ -113,7 +116,10 @@ describe('Hooks Agent Flow', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const scriptPath = join(rig.testDir!, 'after_agent_verify.cjs');
|
const scriptPath = join(rig.testDir!, 'after_agent_verify.cjs').replace(
|
||||||
|
/\\/g,
|
||||||
|
'/',
|
||||||
|
);
|
||||||
writeFileSync(scriptPath, hookScript);
|
writeFileSync(scriptPath, hookScript);
|
||||||
|
|
||||||
await rig.setup('should receive prompt and response in AfterAgent hook', {
|
await rig.setup('should receive prompt and response in AfterAgent hook', {
|
||||||
@@ -165,7 +171,10 @@ describe('Hooks Agent Flow', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// BeforeModel hook to track message counts across LLM calls
|
// BeforeModel hook to track message counts across LLM calls
|
||||||
const messageCountFile = join(rig.testDir!, 'message-counts.json');
|
const messageCountFile = join(
|
||||||
|
rig.testDir!,
|
||||||
|
'message-counts.json',
|
||||||
|
).replace(/\\/g, '/');
|
||||||
const beforeModelScript = `
|
const beforeModelScript = `
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
|
const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
|
||||||
@@ -179,13 +188,31 @@ describe('Hooks Agent Flow', () => {
|
|||||||
const beforeModelScriptPath = join(
|
const beforeModelScriptPath = join(
|
||||||
rig.testDir!,
|
rig.testDir!,
|
||||||
'before_model_counter.cjs',
|
'before_model_counter.cjs',
|
||||||
);
|
).replace(/\\/g, '/');
|
||||||
writeFileSync(beforeModelScriptPath, beforeModelScript);
|
writeFileSync(beforeModelScriptPath, beforeModelScript);
|
||||||
|
|
||||||
|
const afterAgentScript = `
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
decision: 'block',
|
||||||
|
reason: 'Security policy triggered',
|
||||||
|
hookSpecificOutput: {
|
||||||
|
hookEventName: 'AfterAgent',
|
||||||
|
clearContext: true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
`;
|
||||||
|
const afterAgentScriptPath = join(
|
||||||
|
rig.testDir!,
|
||||||
|
'after_agent_clear.cjs',
|
||||||
|
).replace(/\\/g, '/');
|
||||||
|
writeFileSync(afterAgentScriptPath, afterAgentScript);
|
||||||
|
|
||||||
await rig.setup('should process clearContext in AfterAgent hook output', {
|
await rig.setup('should process clearContext in AfterAgent hook output', {
|
||||||
settings: {
|
settings: {
|
||||||
hooks: {
|
hooksConfig: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
BeforeModel: [
|
BeforeModel: [
|
||||||
{
|
{
|
||||||
hooks: [
|
hooks: [
|
||||||
@@ -202,7 +229,7 @@ describe('Hooks Agent Flow', () => {
|
|||||||
hooks: [
|
hooks: [
|
||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command: `node -e "console.log(JSON.stringify({decision: 'block', reason: 'Security policy triggered', hookSpecificOutput: {hookEventName: 'AfterAgent', clearContext: true}}))"`,
|
command: `node "${afterAgentScriptPath}"`,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -237,6 +264,24 @@ describe('Hooks Agent Flow', () => {
|
|||||||
|
|
||||||
describe('Multi-step Loops', () => {
|
describe('Multi-step Loops', () => {
|
||||||
it('should fire BeforeAgent and AfterAgent exactly once per turn despite tool calls', async () => {
|
it('should fire BeforeAgent and AfterAgent exactly once per turn despite tool calls', async () => {
|
||||||
|
await rig.setup(
|
||||||
|
'should fire BeforeAgent and AfterAgent exactly once per turn despite tool calls',
|
||||||
|
);
|
||||||
|
|
||||||
|
const beforeAgentScript = "console.log('BeforeAgent Fired')";
|
||||||
|
const beforeAgentScriptPath = join(
|
||||||
|
rig.testDir!,
|
||||||
|
'before_agent_loop.cjs',
|
||||||
|
).replace(/\\/g, '/');
|
||||||
|
writeFileSync(beforeAgentScriptPath, beforeAgentScript);
|
||||||
|
|
||||||
|
const afterAgentScript = "console.log('AfterAgent Fired')";
|
||||||
|
const afterAgentScriptPath = join(
|
||||||
|
rig.testDir!,
|
||||||
|
'after_agent_loop.cjs',
|
||||||
|
).replace(/\\/g, '/');
|
||||||
|
writeFileSync(afterAgentScriptPath, afterAgentScript);
|
||||||
|
|
||||||
await rig.setup(
|
await rig.setup(
|
||||||
'should fire BeforeAgent and AfterAgent exactly once per turn despite tool calls',
|
'should fire BeforeAgent and AfterAgent exactly once per turn despite tool calls',
|
||||||
{
|
{
|
||||||
@@ -254,7 +299,7 @@ describe('Hooks Agent Flow', () => {
|
|||||||
hooks: [
|
hooks: [
|
||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command: `node -e "console.log('BeforeAgent Fired')"`,
|
command: `node "${beforeAgentScriptPath}"`,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -265,7 +310,7 @@ describe('Hooks Agent Flow', () => {
|
|||||||
hooks: [
|
hooks: [
|
||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command: `node -e "console.log('AfterAgent Fired')"`,
|
command: `node "${afterAgentScriptPath}"`,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ describe('Hooks System Integration', () => {
|
|||||||
|
|
||||||
describe('Command Hooks - Blocking Behavior', () => {
|
describe('Command Hooks - Blocking Behavior', () => {
|
||||||
it('should block tool execution when hook returns block decision', async () => {
|
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,
|
||||||
|
"console.log(JSON.stringify({decision: 'block', reason: 'File writing blocked by security policy'}))",
|
||||||
|
);
|
||||||
|
|
||||||
rig.setup(
|
rig.setup(
|
||||||
'should block tool execution when hook returns block decision',
|
'should block tool execution when hook returns block decision',
|
||||||
{
|
{
|
||||||
@@ -43,8 +50,7 @@ describe('Hooks System Integration', () => {
|
|||||||
hooks: [
|
hooks: [
|
||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command:
|
command: `node "${scriptPath}"`,
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'block', reason: 'File writing blocked by security policy'}))\"",
|
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -78,6 +84,15 @@ describe('Hooks System Integration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should block tool execution and use stderr as reason when hook exits with code 2', async () => {
|
it('should block tool execution and use stderr as reason when hook exits with code 2', async () => {
|
||||||
|
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,
|
||||||
|
"process.stderr.write('File writing blocked by security policy'); process.exit(2)",
|
||||||
|
);
|
||||||
|
|
||||||
rig.setup(
|
rig.setup(
|
||||||
'should block tool execution and use stderr as reason when hook exits with code 2',
|
'should block tool execution and use stderr as reason when hook exits with code 2',
|
||||||
{
|
{
|
||||||
@@ -97,8 +112,7 @@ describe('Hooks System Integration', () => {
|
|||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
// Exit with code 2 and write reason to stderr
|
// Exit with code 2 and write reason to stderr
|
||||||
command:
|
command: `node "${scriptPath}"`,
|
||||||
'node -e "process.stderr.write(\'File writing blocked by security policy\'); process.exit(2)"',
|
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -137,6 +151,13 @@ describe('Hooks System Integration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow tool execution when hook returns allow decision', async () => {
|
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,
|
||||||
|
"console.log(JSON.stringify({decision: 'allow', reason: 'File writing approved'}))",
|
||||||
|
);
|
||||||
|
|
||||||
rig.setup(
|
rig.setup(
|
||||||
'should allow tool execution when hook returns allow decision',
|
'should allow tool execution when hook returns allow decision',
|
||||||
{
|
{
|
||||||
@@ -155,8 +176,7 @@ describe('Hooks System Integration', () => {
|
|||||||
hooks: [
|
hooks: [
|
||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command:
|
command: `node "${scriptPath}"`,
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', reason: 'File writing approved'}))\"",
|
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -187,8 +207,13 @@ describe('Hooks System Integration', () => {
|
|||||||
|
|
||||||
describe('Command Hooks - Additional Context', () => {
|
describe('Command Hooks - Additional Context', () => {
|
||||||
it('should add additional context from AfterTool hooks', async () => {
|
it('should add additional context from AfterTool hooks', async () => {
|
||||||
const command =
|
rig.setup('should add additional context from AfterTool hooks');
|
||||||
"node -e \"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'Security scan: File content appears safe'}}))\"";
|
const scriptPath = join(rig.testDir!, 'after_tool_context.cjs');
|
||||||
|
writeFileSync(
|
||||||
|
scriptPath,
|
||||||
|
"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'Security scan: File content appears safe'}}))",
|
||||||
|
);
|
||||||
|
const command = `node "${scriptPath}"`;
|
||||||
rig.setup('should add additional context from AfterTool hooks', {
|
rig.setup('should add additional context from AfterTool hooks', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
import.meta.dirname,
|
import.meta.dirname,
|
||||||
@@ -279,7 +304,7 @@ console.log(JSON.stringify({
|
|||||||
hooks: [
|
hooks: [
|
||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command: `node "${scriptPath}"`,
|
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -577,7 +602,7 @@ console.log(JSON.stringify({
|
|||||||
hooks: [
|
hooks: [
|
||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command: `node "${scriptPath}"`,
|
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -600,10 +625,14 @@ console.log(JSON.stringify({
|
|||||||
|
|
||||||
describe('Notification Hooks - Permission Handling', () => {
|
describe('Notification Hooks - Permission Handling', () => {
|
||||||
it('should handle notification hooks for tool permissions', async () => {
|
it('should handle notification hooks for tool permissions', async () => {
|
||||||
// Create inline hook command (works on both Unix and Windows)
|
rig.setup('should handle notification hooks for tool permissions');
|
||||||
// Create inline hook command (works on both Unix and Windows)
|
// Create script for hook (works on both Unix and Windows)
|
||||||
const hookCommand =
|
const scriptPath = join(rig.testDir!, 'notification_hook.cjs');
|
||||||
'node -e "console.log(JSON.stringify({suppressOutput: false, systemMessage: \'Permission request logged by security hook\'}))"';
|
writeFileSync(
|
||||||
|
scriptPath,
|
||||||
|
"console.log(JSON.stringify({suppressOutput: false, systemMessage: 'Permission request logged by security hook'}))",
|
||||||
|
);
|
||||||
|
const hookCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||||
|
|
||||||
rig.setup('should handle notification hooks for tool permissions', {
|
rig.setup('should handle notification hooks for tool permissions', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
@@ -697,11 +726,21 @@ console.log(JSON.stringify({
|
|||||||
|
|
||||||
describe('Sequential Hook Execution', () => {
|
describe('Sequential Hook Execution', () => {
|
||||||
it('should execute hooks sequentially when configured', async () => {
|
it('should execute hooks sequentially when configured', async () => {
|
||||||
// Create inline hook commands (works on both Unix and Windows)
|
rig.setup('should execute hooks sequentially when configured');
|
||||||
const hook1Command =
|
// Create script for hooks (works on both Unix and Windows)
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'Step 1: Initial validation passed.'}}))\"";
|
const script1Path = join(rig.testDir!, 'hook1.cjs');
|
||||||
const hook2Command =
|
writeFileSync(
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'Step 2: Security check completed.'}}))\"";
|
script1Path,
|
||||||
|
"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'Step 1: Initial validation passed.'}}))",
|
||||||
|
);
|
||||||
|
const script2Path = join(rig.testDir!, 'hook2.cjs');
|
||||||
|
writeFileSync(
|
||||||
|
script2Path,
|
||||||
|
"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, '/')}"`;
|
||||||
|
|
||||||
rig.setup('should execute hooks sequentially when configured', {
|
rig.setup('should execute hooks sequentially when configured', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
@@ -800,7 +839,7 @@ try {
|
|||||||
hooks: [
|
hooks: [
|
||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command: `node "${scriptPath}"`,
|
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -828,6 +867,15 @@ try {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should treat mixed stdout (text + JSON) as system message and allow execution when exit code is 0', async () => {
|
it('should treat mixed stdout (text + JSON) as system message and allow execution when exit code is 0', async () => {
|
||||||
|
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,
|
||||||
|
"console.log('Pollution'); console.log(JSON.stringify({decision: 'deny', reason: 'Should be ignored'}))",
|
||||||
|
);
|
||||||
|
|
||||||
rig.setup(
|
rig.setup(
|
||||||
'should treat mixed stdout (text + JSON) as system message and allow execution when exit code is 0',
|
'should treat mixed stdout (text + JSON) as system message and allow execution when exit code is 0',
|
||||||
{
|
{
|
||||||
@@ -848,8 +896,7 @@ try {
|
|||||||
type: 'command',
|
type: 'command',
|
||||||
// Output plain text then JSON.
|
// Output plain text then JSON.
|
||||||
// This breaks JSON parsing, so it falls back to 'allow' with the whole stdout as systemMessage.
|
// This breaks JSON parsing, so it falls back to 'allow' with the whole stdout as systemMessage.
|
||||||
command:
|
command: `node "${scriptPath}"`,
|
||||||
"node -e \"console.log('Pollution'); console.log(JSON.stringify({decision: 'deny', reason: 'Should be ignored'}))\"",
|
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -876,13 +923,27 @@ try {
|
|||||||
|
|
||||||
describe('Multiple Event Types', () => {
|
describe('Multiple Event Types', () => {
|
||||||
it('should handle hooks for all major event types', async () => {
|
it('should handle hooks for all major event types', async () => {
|
||||||
// Create inline hook commands (works on both Unix and Windows)
|
rig.setup('should handle hooks for all major event types');
|
||||||
const beforeToolCommand =
|
// Create scripts for hooks (works on both Unix and Windows)
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'BeforeTool: File operation logged'}))\"";
|
const beforeToolScript = join(rig.testDir!, 'before_tool_all.cjs');
|
||||||
const afterToolCommand =
|
writeFileSync(
|
||||||
"node -e \"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'AfterTool: Operation completed successfully'}}))\"";
|
beforeToolScript,
|
||||||
const beforeAgentCommand =
|
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'BeforeTool: File operation logged'}))",
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'BeforeAgent: User request processed'}}))\"";
|
);
|
||||||
|
const afterToolScript = join(rig.testDir!, 'after_tool_all.cjs');
|
||||||
|
writeFileSync(
|
||||||
|
afterToolScript,
|
||||||
|
"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'AfterTool: Operation completed successfully'}}))",
|
||||||
|
);
|
||||||
|
const beforeAgentScript = join(rig.testDir!, 'before_agent_all.cjs');
|
||||||
|
writeFileSync(
|
||||||
|
beforeAgentScript,
|
||||||
|
"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, '/')}"`;
|
||||||
|
|
||||||
rig.setup('should handle hooks for all major event types', {
|
rig.setup('should handle hooks for all major event types', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
@@ -989,21 +1050,23 @@ try {
|
|||||||
|
|
||||||
describe('Hook Error Handling', () => {
|
describe('Hook Error Handling', () => {
|
||||||
it('should handle hook failures gracefully', async () => {
|
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,
|
||||||
|
"console.log(JSON.stringify({decision: 'allow', reason: 'Working hook succeeded'}))",
|
||||||
|
);
|
||||||
|
|
||||||
|
const failingCommand = `node "${failingScript.replace(/\\/g, '/')}"`;
|
||||||
|
const workingCommand = `node "${workingScript.replace(/\\/g, '/')}"`;
|
||||||
|
|
||||||
rig.setup('should handle hook failures gracefully', {
|
rig.setup('should handle hook failures gracefully', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
import.meta.dirname,
|
import.meta.dirname,
|
||||||
'hooks-system.error-handling.responses',
|
'hooks-system.error-handling.responses',
|
||||||
),
|
),
|
||||||
});
|
|
||||||
// Create a hook script that fails
|
|
||||||
// Create inline hook commands (works on both Unix and Windows)
|
|
||||||
// Failing hook: exits with non-zero code
|
|
||||||
const failingCommand = 'node -e "process.exit(1)"';
|
|
||||||
// Working hook: returns success with JSON
|
|
||||||
const workingCommand =
|
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', reason: 'Working hook succeeded'}))\"";
|
|
||||||
|
|
||||||
rig.setup('should handle hook failures gracefully', {
|
|
||||||
settings: {
|
settings: {
|
||||||
hooksConfig: {
|
hooksConfig: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -1049,9 +1112,13 @@ try {
|
|||||||
|
|
||||||
describe('Hook Telemetry and Observability', () => {
|
describe('Hook Telemetry and Observability', () => {
|
||||||
it('should generate telemetry events for hook executions', async () => {
|
it('should generate telemetry events for hook executions', async () => {
|
||||||
// Create inline hook command (works on both Unix and Windows)
|
rig.setup('should generate telemetry events for hook executions');
|
||||||
const hookCommand =
|
const scriptPath = join(rig.testDir!, 'telemetry_hook.cjs');
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', reason: 'Telemetry test hook'}))\"";
|
writeFileSync(
|
||||||
|
scriptPath,
|
||||||
|
"console.log(JSON.stringify({decision: 'allow', reason: 'Telemetry test hook'}))",
|
||||||
|
);
|
||||||
|
const hookCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||||
|
|
||||||
rig.setup('should generate telemetry events for hook executions', {
|
rig.setup('should generate telemetry events for hook executions', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
@@ -1092,9 +1159,13 @@ try {
|
|||||||
|
|
||||||
describe('Session Lifecycle Hooks', () => {
|
describe('Session Lifecycle Hooks', () => {
|
||||||
it('should fire SessionStart hook on app startup', async () => {
|
it('should fire SessionStart hook on app startup', async () => {
|
||||||
// Create inline hook command that outputs JSON
|
rig.setup('should fire SessionStart hook on app startup');
|
||||||
const sessionStartCommand =
|
const scriptPath = join(rig.testDir!, 'session_start.cjs');
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting on startup'}))\"";
|
writeFileSync(
|
||||||
|
scriptPath,
|
||||||
|
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting on startup'}))",
|
||||||
|
);
|
||||||
|
const sessionStartCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||||
|
|
||||||
rig.setup('should fire SessionStart hook on app startup', {
|
rig.setup('should fire SessionStart hook on app startup', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
@@ -1185,7 +1256,7 @@ console.log(JSON.stringify({
|
|||||||
hooks: [
|
hooks: [
|
||||||
{
|
{
|
||||||
type: 'command',
|
type: 'command',
|
||||||
command: `node "${scriptPath}"`,
|
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1316,11 +1387,23 @@ console.log(JSON.stringify({
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should fire SessionEnd and SessionStart hooks on /clear command', async () => {
|
it('should fire SessionEnd and SessionStart hooks on /clear command', async () => {
|
||||||
// Create inline hook commands for both SessionEnd and SessionStart
|
rig.setup(
|
||||||
const sessionEndCommand =
|
'should fire SessionEnd and SessionStart hooks on /clear command',
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session ending due to clear'}))\"";
|
);
|
||||||
const sessionStartCommand =
|
// Create script for hooks (works on both Unix and Windows)
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting after clear'}))\"";
|
const endScriptPath = join(rig.testDir!, 'session_end_clear.cjs');
|
||||||
|
writeFileSync(
|
||||||
|
endScriptPath,
|
||||||
|
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session ending due to clear'}))",
|
||||||
|
);
|
||||||
|
const startScriptPath = join(rig.testDir!, 'session_start_clear.cjs');
|
||||||
|
writeFileSync(
|
||||||
|
startScriptPath,
|
||||||
|
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting after clear'}))",
|
||||||
|
);
|
||||||
|
|
||||||
|
const sessionEndCommand = `node "${endScriptPath.replace(/\\/g, '/')}"`;
|
||||||
|
const sessionStartCommand = `node "${startScriptPath.replace(/\\/g, '/')}"`;
|
||||||
|
|
||||||
rig.setup(
|
rig.setup(
|
||||||
'should fire SessionEnd and SessionStart hooks on /clear command',
|
'should fire SessionEnd and SessionStart hooks on /clear command',
|
||||||
@@ -1494,9 +1577,13 @@ console.log(JSON.stringify({
|
|||||||
|
|
||||||
describe('Compression Hooks', () => {
|
describe('Compression Hooks', () => {
|
||||||
it('should fire PreCompress hook on automatic compression', async () => {
|
it('should fire PreCompress hook on automatic compression', async () => {
|
||||||
// Create inline hook command that outputs JSON
|
rig.setup('should fire PreCompress hook on automatic compression');
|
||||||
const preCompressCommand =
|
const scriptPath = join(rig.testDir!, 'pre_compress.cjs');
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'PreCompress hook executed for automatic compression'}))\"";
|
writeFileSync(
|
||||||
|
scriptPath,
|
||||||
|
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'PreCompress hook executed for automatic compression'}))",
|
||||||
|
);
|
||||||
|
const preCompressCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||||
|
|
||||||
rig.setup('should fire PreCompress hook on automatic compression', {
|
rig.setup('should fire PreCompress hook on automatic compression', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
@@ -1562,8 +1649,15 @@ console.log(JSON.stringify({
|
|||||||
|
|
||||||
describe('SessionEnd on Exit', () => {
|
describe('SessionEnd on Exit', () => {
|
||||||
it('should fire SessionEnd hook on graceful exit in non-interactive mode', async () => {
|
it('should fire SessionEnd hook on graceful exit in non-interactive mode', async () => {
|
||||||
const sessionEndCommand =
|
rig.setup(
|
||||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'SessionEnd hook executed on exit'}))\"";
|
'should fire SessionEnd hook on graceful exit in non-interactive mode',
|
||||||
|
);
|
||||||
|
const scriptPath = join(rig.testDir!, 'session_end_exit.cjs');
|
||||||
|
writeFileSync(
|
||||||
|
scriptPath,
|
||||||
|
"console.log(JSON.stringify({decision: 'allow', systemMessage: 'SessionEnd hook executed on exit'}))",
|
||||||
|
);
|
||||||
|
const sessionEndCommand = `node "${scriptPath.replace(/\\/g, '/')}"`;
|
||||||
|
|
||||||
rig.setup('should fire SessionEnd hook on graceful exit', {
|
rig.setup('should fire SessionEnd hook on graceful exit', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
|
|||||||
Reference in New Issue
Block a user