"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const jsDomInjector_1 = require("../test/jsDomInjector");
// This needs to be run before all other imports so that mynah ui gets loaded inside of jsdom
(0, jsDomInjector_1.injectJSDOM)();
const chat_client_ui_types_1 = require("@aws/chat-client-ui-types");
const language_server_runtimes_types_1 = require("@aws/language-server-runtimes-types");
const mocha_1 = require("mocha");
const sinon_1 = require("sinon");
const chat_1 = require("./chat");
const sinon = require("sinon");
const serverContracts_1 = require("../contracts/serverContracts");
const telemetry_1 = require("../contracts/telemetry");
const tabFactory_1 = require("./tabs/tabFactory");
describe('Chat', () => {
    const sandbox = sinon.createSandbox();
    const initialTabId = 'tab-1';
    let mynahUi;
    let clientApi;
    let messageHandler;
    before(() => {
        // Mock global observers for test environment
        // @ts-expect-error: mock implementation for testing
        global.ResizeObserver = null;
        // @ts-expect-error: mock implementation for testing
        global.IntersectionObserver = null;
        // @ts-expect-error: mock implementation for testing
        global.MutationObserver = null;
    });
    beforeEach(() => {
        sandbox.stub(tabFactory_1.TabFactory, 'generateUniqueId').returns(initialTabId);
        sandbox.stub(tabFactory_1.TabFactory.prototype, 'enableHistory');
        sandbox.stub(tabFactory_1.TabFactory.prototype, 'enableExport');
        clientApi = {
            postMessage: sandbox.stub(),
        };
        const originalAddEventListener = window.addEventListener.bind(window);
        sandbox.stub(window, 'addEventListener').callsFake((type, handler, ...rest) => {
            if (type === 'message') {
                messageHandler = handler;
            }
            return originalAddEventListener(type, handler, ...rest);
        });
        mynahUi = (0, chat_1.createChat)(clientApi, {
            agenticMode: true,
        });
    });
    (0, mocha_1.afterEach)(() => {
        if (messageHandler) {
            window.removeEventListener('message', messageHandler);
            messageHandler = undefined;
        }
        sandbox.restore();
        Object.keys(mynahUi.getAllTabs()).forEach(tabId => {
            mynahUi.removeTab(tabId, mynahUi.lastEventId);
        });
    });
    after(() => {
        // @ts-expect-error: mock implementation for testing
        global.ResizeObserver = undefined;
        // @ts-expect-error: mock implementation for testing
        global.MutationObserver = undefined;
    });
    it('publishes ready event when initialized', () => {
        sinon_1.assert.callCount(clientApi.postMessage, 5);
        sinon_1.assert.calledWithExactly(clientApi.postMessage.getCall(0), {
            command: serverContracts_1.TELEMETRY,
            params: { name: 'enterFocus' },
        });
        sinon_1.assert.calledWithExactly(clientApi.postMessage.getCall(1), { command: language_server_runtimes_types_1.READY_NOTIFICATION_METHOD });
        sinon_1.assert.calledWithExactly(clientApi.postMessage.getCall(2), {
            command: language_server_runtimes_types_1.TAB_ADD_NOTIFICATION_METHOD,
            params: { tabId: initialTabId, restoredTab: undefined },
        });
        sinon_1.assert.calledWithExactly(clientApi.postMessage.getCall(3), {
            command: serverContracts_1.TELEMETRY,
            params: {
                triggerType: 'click',
                name: telemetry_1.TAB_ADD_TELEMETRY_EVENT,
                tabId: initialTabId,
            },
        });
        sinon_1.assert.calledWithMatch(clientApi.postMessage.getCall(4), {
            command: 'aws/chat/listAvailableModels',
            params: { tabId: initialTabId },
        });
    });
    it('publishes telemetry event, when send to prompt is triggered', () => {
        const eventParams = { command: chat_client_ui_types_1.SEND_TO_PROMPT, params: { prompt: 'hey' } };
        const sendToPromptEvent = createInboundEvent(eventParams);
        window.dispatchEvent(sendToPromptEvent);
        sinon_1.assert.calledWithExactly(clientApi.postMessage, {
            command: serverContracts_1.TELEMETRY,
            params: {
                name: telemetry_1.SEND_TO_PROMPT_TELEMETRY_EVENT,
                tabId: mynahUi.getSelectedTabId(),
                ...eventParams.params,
            },
        });
    });
    it('publishes telemetry event, when show error is triggered', () => {
        const eventParams = { command: chat_client_ui_types_1.ERROR_MESSAGE, params: { tabId: '123' } };
        const errorEvent = createInboundEvent(eventParams);
        window.dispatchEvent(errorEvent);
        sinon_1.assert.calledWithExactly(clientApi.postMessage, {
            command: serverContracts_1.TELEMETRY,
            params: {
                name: telemetry_1.ERROR_MESSAGE_TELEMETRY_EVENT,
                ...eventParams.params,
            },
        });
    });
    it('rejects inbound messages from a foreign origin (P389799154)', () => {
        // Simulates a cross-origin postMessage from an attacker page. The
        // handleInboundMessage same-origin check should drop the event before
        // any command is dispatched.
        clientApi.postMessage.resetHistory();
        const foreignEvent = new window.MessageEvent('message', {
            data: { command: chat_client_ui_types_1.SEND_TO_PROMPT, params: { prompt: 'pwn' } },
            origin: 'https://attacker.example.com',
        });
        window.dispatchEvent(foreignEvent);
        sinon_1.assert.notCalled(clientApi.postMessage);
    });
    it('accepts inbound messages with empty origin (Eclipse SWT Browser)', () => {
        // Eclipse SWT Browser loads content via file:// protocol, so
        // postMessage events arrive with an empty string origin.
        const warnStub = sandbox.stub(console, 'warn');
        const eclipseEvent = new window.MessageEvent('message', {
            data: { command: 'noop-test-command' },
            origin: '',
        });
        window.dispatchEvent(eclipseEvent);
        sinon_1.assert.notCalled(warnStub);
    });
    it('accepts inbound messages with "null" origin (sandboxed iframes)', () => {
        // Sandboxed iframes without allow-same-origin report origin as
        // the string "null".
        const warnStub = sandbox.stub(console, 'warn');
        const nullOriginEvent = new window.MessageEvent('message', {
            data: { command: 'noop-test-command' },
            origin: 'null',
        });
        window.dispatchEvent(nullOriginEvent);
        sinon_1.assert.notCalled(warnStub);
    });
    it('accepts inbound messages with non-HTTP origin (file:// protocol)', () => {
        const warnStub = sandbox.stub(console, 'warn');
        const fileEvent = new window.MessageEvent('message', {
            data: { command: 'noop-test-command' },
            origin: 'file://',
        });
        window.dispatchEvent(fileEvent);
        sinon_1.assert.notCalled(warnStub);
    });
    it('publishes tab added event, when UI tab is added', () => {
        const tabId = mynahUi.updateStore('', {});
        sinon_1.assert.calledWithMatch(clientApi.postMessage, {
            command: language_server_runtimes_types_1.TAB_ADD_NOTIFICATION_METHOD,
            params: { tabId: tabId },
        });
    });
    it('publishes tab removed event, when UI tab is removed', () => {
        const tabId = mynahUi.updateStore('', {});
        mynahUi.removeTab(tabId, mynahUi.lastEventId);
        sinon_1.assert.calledWithMatch(clientApi.postMessage, {
            command: language_server_runtimes_types_1.TAB_REMOVE_NOTIFICATION_METHOD,
            params: { tabId: tabId },
        });
    });
    it('publishes tab changed event, when UI tab is changed', () => {
        const tabId = mynahUi.updateStore('', {});
        mynahUi.updateStore('', {});
        clientApi.postMessage.resetHistory();
        mynahUi.selectTab(tabId, mynahUi.lastEventId);
        sinon_1.assert.calledOnceWithExactly(clientApi.postMessage, {
            command: language_server_runtimes_types_1.TAB_CHANGE_NOTIFICATION_METHOD,
            params: { tabId: tabId },
        });
    });
    it('generic command creates a chat request', () => {
        const genericCommand = 'Fix';
        const selection = 'some code';
        const tabId = '123';
        const triggerType = 'click';
        const expectedPrompt = `${genericCommand} the following part of my code:\n~~~~\n${selection}\n~~~~\n`;
        const genericCommandEvent = createInboundEvent({
            command: chat_client_ui_types_1.GENERIC_COMMAND,
            params: { tabId, selection, triggerType, genericCommand },
        });
        window.dispatchEvent(genericCommandEvent);
        sinon_1.assert.calledWithMatch(clientApi.postMessage, {
            command: language_server_runtimes_types_1.CHAT_REQUEST_METHOD,
            params: {
                prompt: {
                    prompt: expectedPrompt,
                    escapedPrompt: expectedPrompt,
                },
            },
        });
    });
    it('open tab requestId was propagated from inbound to outbound message', () => {
        const requestId = 'request-1234';
        const openTabEvent = createInboundEvent({
            command: language_server_runtimes_types_1.OPEN_TAB_REQUEST_METHOD,
            params: {
                newTabOptions: {
                    data: {
                        messages: [],
                    },
                },
            },
            requestId: requestId,
        });
        window.dispatchEvent(openTabEvent);
        // Verify that postMessage was called with the correct requestId
        sinon_1.assert.calledWithExactly(clientApi.postMessage, {
            command: language_server_runtimes_types_1.OPEN_TAB_REQUEST_METHOD,
            requestId,
            params: {
                success: true,
                result: sinon.match({
                    tabId: sinon.match.string,
                }),
            },
        });
    });
    it('complete chat response triggers ui events', () => {
        const endMessageStreamStub = sandbox.stub(mynahUi, 'endMessageStream');
        const updateStoreStub = sandbox.stub(mynahUi, 'updateStore');
        const tabId = '123';
        const body = 'some response';
        const chatEvent = createInboundEvent({
            command: language_server_runtimes_types_1.CHAT_REQUEST_METHOD,
            tabId,
            params: { body },
        });
        window.dispatchEvent(chatEvent);
        sinon_1.assert.calledOnceWithExactly(endMessageStreamStub, tabId, '', {
            header: undefined,
            buttons: undefined,
            body: 'some response',
            followUp: {},
            relatedContent: undefined,
            canBeVoted: undefined,
            codeReference: undefined,
            fileList: undefined,
        });
        sinon_1.assert.calledOnceWithExactly(updateStoreStub, tabId, {
            loadingChat: false,
            promptInputDisabledState: false,
            cancelButtonWhenLoading: true,
        });
    });
    it('partial chat response triggers ui events', () => {
        const endMessageStreamStub = sandbox.stub(mynahUi, 'endMessageStream');
        const updateStoreStub = sandbox.stub(mynahUi, 'updateStore');
        const tabId = '123';
        const body = 'some response';
        const chatEvent = createInboundEvent({
            command: language_server_runtimes_types_1.CHAT_REQUEST_METHOD,
            tabId,
            params: { body },
            isPartialResult: true,
        });
        window.dispatchEvent(chatEvent);
        sinon_1.assert.notCalled(endMessageStreamStub);
        sinon_1.assert.calledOnce(updateStoreStub);
    });
    it('partial chat response with header triggers ui events', () => {
        const endMessageStreamStub = sandbox.stub(mynahUi, 'endMessageStream');
        const updateStoreStub = sandbox.stub(mynahUi, 'updateStore');
        const tabId = '123';
        const body = 'some response';
        const contextList = {
            filePaths: ['file1', 'file2'],
            details: {
                file1: {
                    lineRanges: [{ first: 1, second: 2 }],
                },
            },
        };
        const params = { body, contextList };
        const mockHeader = {
            fileList: {
                fileTreeTitle: '',
                filePaths: ['file1', 'file2'],
                rootFolderTitle: 'Context',
                flatList: true,
                collapsed: true,
                hideFileCount: true,
                details: {
                    file1: {
                        label: 'line 1 - 2',
                        description: 'file1',
                        clickable: true,
                    },
                },
            },
        };
        const chatEvent = createInboundEvent({
            command: language_server_runtimes_types_1.CHAT_REQUEST_METHOD,
            tabId,
            params,
            isPartialResult: true,
        });
        window.dispatchEvent(chatEvent);
        sinon_1.assert.notCalled(endMessageStreamStub);
        sinon_1.assert.calledOnce(updateStoreStub);
    });
    describe('chatOptions', () => {
        it('enables history and export features support', () => {
            const chatOptionsRequest = createInboundEvent({
                command: chat_client_ui_types_1.CHAT_OPTIONS,
                params: {
                    history: true,
                    export: true,
                },
            });
            window.dispatchEvent(chatOptionsRequest);
            // @ts-expect-error: accessing prototype method
            sinon_1.assert.called(tabFactory_1.TabFactory.prototype.enableHistory);
            // @ts-expect-error: accessing prototype method
            sinon_1.assert.called(tabFactory_1.TabFactory.prototype.enableExport);
        }).timeout(20000);
        it('does not enable history and export features support if flags are falsy', async () => {
            const chatOptionsRequest = createInboundEvent({
                command: chat_client_ui_types_1.CHAT_OPTIONS,
                params: {
                    history: false,
                    export: false,
                },
            });
            window.dispatchEvent(chatOptionsRequest);
            // @ts-expect-error: accessing prototype method
            sinon_1.assert.notCalled(tabFactory_1.TabFactory.prototype.enableHistory);
            // @ts-expect-error: accessing prototype method
            sinon_1.assert.notCalled(tabFactory_1.TabFactory.prototype.enableExport);
        }).timeout(20000);
        it('enables MCP when params.mcpServers is true and config.agenticMode is true', function () {
            // Create a separate sandbox for this test
            const testSandbox = sinon.createSandbox();
            // Save original window functions
            const originalAddEventListener = window.addEventListener;
            const originalDispatchEvent = window.dispatchEvent;
            try {
                // Create a clean stub for this test
                const enableMcpStub = testSandbox.stub(tabFactory_1.TabFactory.prototype, 'enableMcp');
                const localClientApi = { postMessage: testSandbox.stub() };
                // Mock the event handling to isolate this test
                let messageHandler;
                window.addEventListener = (type, handler) => {
                    if (type === 'message') {
                        messageHandler = handler;
                    }
                    return undefined;
                };
                // Create a new chat instance specifically for this test
                const localMynahUi = (0, chat_1.createChat)(localClientApi, { agenticMode: true });
                // Create a new event
                const chatOptionsRequest = createInboundEvent({
                    command: chat_client_ui_types_1.CHAT_OPTIONS,
                    params: {
                        mcpServers: true,
                        chatNotifications: [],
                    },
                });
                // Manually call the handler with our event
                if (messageHandler) {
                    messageHandler(chatOptionsRequest);
                }
                // Verify enableMcp was called exactly once
                sinon_1.assert.calledOnce(enableMcpStub);
            }
            finally {
                // Restore window functions
                window.addEventListener = originalAddEventListener;
                window.dispatchEvent = originalDispatchEvent;
                testSandbox.restore();
            }
        });
        it('does not enable MCP when params.mcpServers is true but config.agenticMode is false', function () {
            // Create a separate sandbox for this test
            const testSandbox = sinon.createSandbox();
            // Save original window functions
            const originalAddEventListener = window.addEventListener;
            const originalDispatchEvent = window.dispatchEvent;
            try {
                // Create a clean stub for this test
                const enableMcpStub = testSandbox.stub(tabFactory_1.TabFactory.prototype, 'enableMcp');
                const localClientApi = { postMessage: testSandbox.stub() };
                // Mock the event handling to isolate this test
                let messageHandler;
                window.addEventListener = (type, handler) => {
                    if (type === 'message') {
                        messageHandler = handler;
                    }
                    return undefined;
                };
                // Create a new chat instance specifically for this test
                const localMynahUi = (0, chat_1.createChat)(localClientApi, { agenticMode: false });
                // Create a new event
                const chatOptionsRequest = createInboundEvent({
                    command: chat_client_ui_types_1.CHAT_OPTIONS,
                    params: {
                        mcpServers: true,
                        chatNotifications: [],
                    },
                });
                // Manually call the handler with our event
                if (messageHandler) {
                    messageHandler(chatOptionsRequest);
                }
                // Verify enableMcp was not called
                sinon_1.assert.notCalled(enableMcpStub);
            }
            finally {
                // Restore window functions
                window.addEventListener = originalAddEventListener;
                window.dispatchEvent = originalDispatchEvent;
                testSandbox.restore();
            }
        });
        it('does not enable MCP when params.mcpServers is false and config.agenticMode is true', function () {
            // Create a separate sandbox for this test
            const testSandbox = sinon.createSandbox();
            // Save original window functions
            const originalAddEventListener = window.addEventListener;
            const originalDispatchEvent = window.dispatchEvent;
            try {
                // Create a clean stub for this test
                const enableMcpStub = testSandbox.stub(tabFactory_1.TabFactory.prototype, 'enableMcp');
                const localClientApi = { postMessage: testSandbox.stub() };
                // Mock the event handling to isolate this test
                let messageHandler;
                window.addEventListener = (type, handler) => {
                    if (type === 'message') {
                        messageHandler = handler;
                    }
                    return undefined;
                };
                // Create a new chat instance specifically for this test
                const localMynahUi = (0, chat_1.createChat)(localClientApi, { agenticMode: true });
                // Create a new event
                const chatOptionsRequest = createInboundEvent({
                    command: chat_client_ui_types_1.CHAT_OPTIONS,
                    params: {
                        mcpServers: false,
                        chatNotifications: [],
                    },
                });
                // Manually call the handler with our event
                if (messageHandler) {
                    messageHandler(chatOptionsRequest);
                }
                // Verify enableMcp was not called
                sinon_1.assert.notCalled(enableMcpStub);
            }
            finally {
                // Restore window functions
                window.addEventListener = originalAddEventListener;
                window.dispatchEvent = originalDispatchEvent;
                testSandbox.restore();
            }
        });
        it('does not enable MCP when params.mcpServers is undefined and config.agenticMode is true', function () {
            // Create a separate sandbox for this test
            const testSandbox = sinon.createSandbox();
            // Save original window functions
            const originalAddEventListener = window.addEventListener;
            const originalDispatchEvent = window.dispatchEvent;
            try {
                // Create a clean stub for this test
                const enableMcpStub = testSandbox.stub(tabFactory_1.TabFactory.prototype, 'enableMcp');
                const localClientApi = { postMessage: testSandbox.stub() };
                // Mock the event handling to isolate this test
                let messageHandler;
                window.addEventListener = (type, handler) => {
                    if (type === 'message') {
                        messageHandler = handler;
                    }
                    return undefined;
                };
                // Create a new chat instance specifically for this test
                const localMynahUi = (0, chat_1.createChat)(localClientApi, { agenticMode: true });
                // Create a new event
                const chatOptionsRequest = createInboundEvent({
                    command: chat_client_ui_types_1.CHAT_OPTIONS,
                    params: {
                        chatNotifications: [],
                    },
                });
                // Manually call the handler with our event
                if (messageHandler) {
                    messageHandler(chatOptionsRequest);
                }
                // Verify enableMcp was not called
                sinon_1.assert.notCalled(enableMcpStub);
            }
            finally {
                // Restore window functions
                window.addEventListener = originalAddEventListener;
                window.dispatchEvent = originalDispatchEvent;
                testSandbox.restore();
            }
        });
    });
    describe('onGetSerializedChat', () => {
        it('getSerializedChat requestId was propagated from inbound to outbound message', () => {
            const requestId = 'request-1234';
            const tabId = mynahUi.updateStore('', {});
            const setSerializedChatEvent = createInboundEvent({
                command: language_server_runtimes_types_1.GET_SERIALIZED_CHAT_REQUEST_METHOD,
                params: {
                    tabId: tabId,
                    format: 'markdown',
                },
                requestId: requestId,
            });
            window.dispatchEvent(setSerializedChatEvent);
            // Verify that postMessage was called with the correct requestId
            sinon_1.assert.calledWithExactly(clientApi.postMessage, {
                requestId,
                command: language_server_runtimes_types_1.GET_SERIALIZED_CHAT_REQUEST_METHOD,
                params: {
                    success: true,
                    result: sinon.match({
                        content: sinon.match.string,
                    }),
                },
            });
        });
    });
    function createInboundEvent(params) {
        // Use MessageEvent (not CustomEvent) and set origin to window.location.origin
        // so that handleInboundMessage's same-origin check accepts it. See
        // chat.ts handleInboundMessage origin validation (P389799154).
        // window.MessageEvent is the jsdom event class; using the global Node
        // MessageEvent fails dispatchEvent's instanceof check.
        return new window.MessageEvent('message', {
            data: params,
            origin: window.location.origin,
        });
    }
    describe('with client adapter', () => {
        it('should route inbound message to client adapter', () => {
            const handleMessageReceiveStub = sandbox.stub();
            const createChatEventHandlerStub = sandbox.stub().returns({});
            const clientAdapter = {
                createChatEventHandler: createChatEventHandlerStub,
                handleMessageReceive: handleMessageReceiveStub,
                isSupportedTab: () => false,
            };
            mynahUi = (0, chat_1.createChat)(clientApi, {
                agenticMode: true,
            }, clientAdapter);
            const tabId = '123';
            const body = 'some response';
            const chatEvent = createInboundEvent({
                command: language_server_runtimes_types_1.CHAT_REQUEST_METHOD,
                tabId,
                params: { body },
                sender: 'ide-extension',
            });
            window.dispatchEvent(chatEvent);
            sinon_1.assert.calledOnce(handleMessageReceiveStub);
            sinon_1.assert.match(handleMessageReceiveStub.getCall(0).args[0].data, JSON.stringify(chatEvent.data));
        });
    });
});
//# sourceMappingURL=chat.test.js.map