/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, beforeEach } from 'vitest';
import { HookAggregator } from './hookAggregator.js';
import { HookType, HookEventName } from './types.js';
// Helper function to create proper HookExecutionResult objects
function createHookExecutionResult(output, success = true, duration = 100, error) {
    return {
        success,
        output,
        duration,
        error,
        hookConfig: {
            type: HookType.Command,
            command: 'test-command',
            timeout: 30000,
        },
        eventName: HookEventName.BeforeTool,
    };
}
describe('HookAggregator', () => {
    let aggregator;
    beforeEach(() => {
        aggregator = new HookAggregator();
    });
    describe('aggregateResults', () => {
        it('should handle empty results', () => {
            const results = [];
            const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
            expect(aggregated.success).toBe(true);
            expect(aggregated.allOutputs).toHaveLength(0);
            expect(aggregated.errors).toHaveLength(0);
            expect(aggregated.totalDuration).toBe(0);
            expect(aggregated.finalOutput).toBeUndefined();
        });
        it('should aggregate successful results', () => {
            const results = [
                createHookExecutionResult({ decision: 'allow', reason: 'Hook 1 approved' }, true, 100),
                createHookExecutionResult({ decision: 'allow', reason: 'Hook 2 approved' }, true, 150),
            ];
            const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
            expect(aggregated.success).toBe(true);
            expect(aggregated.allOutputs).toHaveLength(2);
            expect(aggregated.errors).toHaveLength(0);
            expect(aggregated.totalDuration).toBe(250);
            expect(aggregated.finalOutput?.decision).toBe('allow');
            expect(aggregated.finalOutput?.reason).toBe('Hook 1 approved\nHook 2 approved');
        });
        it('should handle errors in results', () => {
            const results = [
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeTool,
                    success: false,
                    error: new Error('Hook failed'),
                    duration: 50,
                },
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeTool,
                    success: true,
                    output: { decision: 'allow' },
                    duration: 100,
                },
            ];
            const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
            expect(aggregated.success).toBe(false);
            expect(aggregated.allOutputs).toHaveLength(1);
            expect(aggregated.errors).toHaveLength(1);
            expect(aggregated.errors[0].message).toBe('Hook failed');
            expect(aggregated.totalDuration).toBe(150);
        });
        it('should handle blocking decisions with OR logic', () => {
            const results = [
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeTool,
                    success: true,
                    output: { decision: 'allow', reason: 'Hook 1 allowed' },
                    duration: 100,
                },
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeTool,
                    success: true,
                    output: { decision: 'block', reason: 'Hook 2 blocked' },
                    duration: 150,
                },
            ];
            const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
            expect(aggregated.success).toBe(true);
            expect(aggregated.finalOutput?.decision).toBe('block');
            expect(aggregated.finalOutput?.reason).toBe('Hook 1 allowed\nHook 2 blocked');
        });
        it('should handle continue=false with precedence', () => {
            const results = [
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeTool,
                    success: true,
                    output: { decision: 'allow', continue: true },
                    duration: 100,
                },
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeTool,
                    success: true,
                    output: {
                        decision: 'allow',
                        continue: false,
                        stopReason: 'Stop requested',
                    },
                    duration: 150,
                },
            ];
            const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
            expect(aggregated.success).toBe(true);
            expect(aggregated.finalOutput?.continue).toBe(false);
            expect(aggregated.finalOutput?.stopReason).toBe('Stop requested');
        });
    });
    describe('BeforeToolSelection merge strategy', () => {
        it('should merge tool configurations with NONE mode precedence', () => {
            const results = [
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeToolSelection,
                    success: true,
                    output: {
                        hookSpecificOutput: {
                            hookEventName: 'BeforeToolSelection',
                            toolConfig: {
                                mode: 'ANY',
                                allowedFunctionNames: ['tool1', 'tool2'],
                            },
                        },
                    },
                    duration: 100,
                },
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeToolSelection,
                    success: true,
                    output: {
                        hookSpecificOutput: {
                            hookEventName: 'BeforeToolSelection',
                            toolConfig: {
                                mode: 'NONE',
                                allowedFunctionNames: [],
                            },
                        },
                    },
                    duration: 150,
                },
            ];
            const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeToolSelection);
            expect(aggregated.success).toBe(true);
            const output = aggregated.finalOutput;
            const toolConfig = output.hookSpecificOutput?.toolConfig;
            expect(toolConfig?.mode).toBe('NONE');
            expect(toolConfig?.allowedFunctionNames).toEqual([]);
        });
        it('should merge tool configurations with ANY mode', () => {
            const results = [
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeToolSelection,
                    success: true,
                    output: {
                        hookSpecificOutput: {
                            hookEventName: 'BeforeToolSelection',
                            toolConfig: {
                                mode: 'AUTO',
                                allowedFunctionNames: ['tool1'],
                            },
                        },
                    },
                    duration: 100,
                },
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeToolSelection,
                    success: true,
                    output: {
                        hookSpecificOutput: {
                            hookEventName: 'BeforeToolSelection',
                            toolConfig: {
                                mode: 'ANY',
                                allowedFunctionNames: ['tool2', 'tool3'],
                            },
                        },
                    },
                    duration: 150,
                },
            ];
            const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeToolSelection);
            expect(aggregated.success).toBe(true);
            const output = aggregated.finalOutput;
            const toolConfig = output.hookSpecificOutput?.toolConfig;
            expect(toolConfig?.mode).toBe('ANY');
            expect(toolConfig?.allowedFunctionNames).toEqual([
                'tool1',
                'tool2',
                'tool3',
            ]);
        });
        it('should merge tool configurations with AUTO mode when all are AUTO', () => {
            const results = [
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeToolSelection,
                    success: true,
                    output: {
                        hookSpecificOutput: {
                            hookEventName: 'BeforeToolSelection',
                            toolConfig: {
                                mode: 'AUTO',
                                allowedFunctionNames: ['tool1'],
                            },
                        },
                    },
                    duration: 100,
                },
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeToolSelection,
                    success: true,
                    output: {
                        hookSpecificOutput: {
                            hookEventName: 'BeforeToolSelection',
                            toolConfig: {
                                mode: 'AUTO',
                                allowedFunctionNames: ['tool2'],
                            },
                        },
                    },
                    duration: 150,
                },
            ];
            const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeToolSelection);
            expect(aggregated.success).toBe(true);
            const output = aggregated.finalOutput;
            const toolConfig = output.hookSpecificOutput?.toolConfig;
            expect(toolConfig?.mode).toBe('AUTO');
            expect(toolConfig?.allowedFunctionNames).toEqual(['tool1', 'tool2']);
        });
    });
    describe('BeforeModel/AfterModel merge strategy', () => {
        it('should use field replacement strategy', () => {
            const results = [
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeModel,
                    success: true,
                    output: {
                        decision: 'allow',
                        hookSpecificOutput: {
                            hookEventName: 'BeforeModel',
                            llm_request: { model: 'model1', config: {}, contents: [] },
                        },
                    },
                    duration: 100,
                },
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.BeforeModel,
                    success: true,
                    output: {
                        decision: 'block',
                        hookSpecificOutput: {
                            hookEventName: 'BeforeModel',
                            llm_request: { model: 'model2', config: {}, contents: [] },
                        },
                    },
                    duration: 150,
                },
            ];
            const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeModel);
            expect(aggregated.success).toBe(true);
            expect(aggregated.finalOutput?.decision).toBe('block'); // Later value wins
            const output = aggregated.finalOutput;
            const llmRequest = output.hookSpecificOutput?.llm_request;
            expect(llmRequest?.['model']).toBe('model2'); // Later value wins
        });
    });
    describe('extractAdditionalContext', () => {
        it('should extract additional context from hook outputs', () => {
            const results = [
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.AfterTool,
                    success: true,
                    output: {
                        hookSpecificOutput: {
                            hookEventName: 'AfterTool',
                            additionalContext: 'Context from hook 1',
                        },
                    },
                    duration: 100,
                },
                {
                    hookConfig: {
                        type: HookType.Command,
                        command: 'test-command',
                        timeout: 30000,
                    },
                    eventName: HookEventName.AfterTool,
                    success: true,
                    output: {
                        hookSpecificOutput: {
                            hookEventName: 'AfterTool',
                            additionalContext: 'Context from hook 2',
                        },
                    },
                    duration: 150,
                },
            ];
            const aggregated = aggregator.aggregateResults(results, HookEventName.AfterTool);
            expect(aggregated.success).toBe(true);
            expect(aggregated.finalOutput?.hookSpecificOutput?.['additionalContext']).toBe('Context from hook 1\nContext from hook 2');
        });
    });
});
//# sourceMappingURL=hookAggregator.test.js.map