/**
 * @license
 * Copyright 2026 HopCode Team Team
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { DualOutputBridge, DUAL_OUTPUT_PROTOCOL_VERSION, SUPPORTED_EVENTS, } from './DualOutputBridge.js';
function createMockConfig() {
    return {
        getSessionId: vi.fn().mockReturnValue('test-session'),
        getModel: vi.fn().mockReturnValue('test-model'),
    };
}
function readJsonl(file) {
    return fs
        .readFileSync(file, 'utf8')
        .split('\n')
        .filter(Boolean)
        .map((line) => JSON.parse(line));
}
describe('DualOutputBridge', () => {
    let tmpDir;
    let target;
    let config;
    let bridge = null;
    beforeEach(() => {
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qwen-dual-output-'));
        target = path.join(tmpDir, 'events.jsonl');
        fs.writeFileSync(target, '');
        config = createMockConfig();
    });
    afterEach(async () => {
        await bridge?.shutdown();
        bridge = null;
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    describe('--json-fd validation', () => {
        it.each([0, 1, 2])('rejects reserved fd %d', (fd) => {
            expect(() => new DualOutputBridge(config, { fd })).toThrow(/reserved/);
        });
        it('rejects an unopened fd with a clear message', () => {
            // 9999 is extremely unlikely to be open in the test process
            expect(() => new DualOutputBridge(config, { fd: 9999 })).toThrow(/file descriptor is not open/);
        });
    });
    describe('--json-file output', () => {
        it('creates the file automatically when it does not exist (ENOENT fallback)', async () => {
            const newFile = path.join(tmpDir, 'does-not-exist.jsonl');
            // newFile is NOT pre-created — tests the ENOENT fallback path
            bridge = new DualOutputBridge(config, { filePath: newFile });
            await bridge.shutdown();
            const lines = readJsonl(newFile);
            expect(lines.length).toBeGreaterThan(0);
            expect(lines[0]).toMatchObject({
                type: 'system',
                subtype: 'session_start',
            });
        });
        it('emits a session_start event immediately on construction', async () => {
            bridge = new DualOutputBridge(config, { filePath: target });
            await bridge.shutdown();
            const lines = readJsonl(target);
            expect(lines.length).toBeGreaterThan(0);
            expect(lines[0]).toMatchObject({
                type: 'system',
                subtype: 'session_start',
                data: { session_id: 'test-session' },
            });
        });
        it('session_start carries a capability handshake (version, protocol_version, supported_events)', async () => {
            bridge = new DualOutputBridge(config, { filePath: target }, { version: '1.2.3' });
            await bridge.shutdown();
            const lines = readJsonl(target);
            const start = lines.find((l) => l['type'] === 'system' && l['subtype'] === 'session_start');
            expect(start).toBeDefined();
            const data = start.data;
            expect(data['version']).toBe('1.2.3');
            expect(data['protocol_version']).toBe(DUAL_OUTPUT_PROTOCOL_VERSION);
            expect(data['supported_events']).toEqual([...SUPPORTED_EVENTS]);
        });
        it('emits session_end on shutdown for a clean termination signal', async () => {
            bridge = new DualOutputBridge(config, { filePath: target });
            await bridge.shutdown();
            const lines = readJsonl(target);
            const end = lines.find((l) => l['type'] === 'system' && l['subtype'] === 'session_end');
            expect(end).toMatchObject({
                type: 'system',
                subtype: 'session_end',
                data: { session_id: 'test-session' },
            });
        });
        it('shutdown is idempotent — calling it twice emits session_end only once', async () => {
            bridge = new DualOutputBridge(config, { filePath: target });
            await bridge.shutdown();
            await bridge.shutdown();
            const lines = readJsonl(target);
            const endEvents = lines.filter((l) => l['type'] === 'system' && l['subtype'] === 'session_end');
            expect(endEvents).toHaveLength(1);
        });
        it('emitControlError routes through the adapter as a control_response error', async () => {
            bridge = new DualOutputBridge(config, { filePath: target });
            bridge.emitControlError('req-missing', 'unknown request_id');
            await bridge.shutdown();
            const lines = readJsonl(target);
            const errorResponse = lines.find((l) => l['type'] === 'control_response' &&
                l['response']?.subtype === 'error');
            expect(errorResponse).toMatchObject({
                type: 'control_response',
                response: {
                    subtype: 'error',
                    request_id: 'req-missing',
                    error: 'unknown request_id',
                },
            });
        });
        it('routes permission requests + responses through the adapter', async () => {
            bridge = new DualOutputBridge(config, { filePath: target });
            bridge.emitPermissionRequest('req-1', 'shell', 'tu-1', { cmd: 'ls' });
            bridge.emitControlResponse('req-1', false);
            await bridge.shutdown();
            const lines = readJsonl(target);
            const request = lines.find((l) => l['type'] === 'control_request');
            const response = lines.find((l) => l['type'] === 'control_response');
            expect(request).toMatchObject({
                type: 'control_request',
                request_id: 'req-1',
                request: {
                    subtype: 'can_use_tool',
                    tool_name: 'shell',
                    tool_use_id: 'tu-1',
                    input: { cmd: 'ls' },
                    blocked_path: null,
                },
            });
            expect(response).toMatchObject({
                type: 'control_response',
                response: {
                    subtype: 'success',
                    request_id: 'req-1',
                    response: { allowed: false },
                },
            });
        });
        it('reports isConnected=false after shutdown and silently drops further events', async () => {
            bridge = new DualOutputBridge(config, { filePath: target });
            await bridge.shutdown();
            expect(bridge.isConnected).toBe(false);
            // Should not throw
            expect(() => bridge.emitPermissionRequest('req', 'tool', 'tu', {})).not.toThrow();
            expect(() => bridge.emitControlResponse('req', true)).not.toThrow();
        });
    });
});
//# sourceMappingURL=DualOutputBridge.test.js.map