import assert from 'node:assert/strict';
import { afterEach, beforeEach, describe, it, mock } from 'node:test';
import { HttpClient } from './http-client.js';
describe('HttpClient', () => {
    const originalFetch = global.fetch;
    let fetchCalls = [];
    beforeEach(() => {
        fetchCalls = [];
        global.fetch = async (url, options) => {
            fetchCalls.push([url.toString(), options]);
            return {
                ok: true,
                status: 200,
                statusText: 'OK',
                json: async () => ({ result: 'success' }),
            };
        };
    });
    afterEach(() => {
        global.fetch = originalFetch;
        mock.restoreAll();
    });
    describe('constructor and URL resolution', () => {
        it('uses explicit mcpHttpUrl when provided', () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                mcpHttpUrl: 'http://custom:3000/mcp/call',
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'http://custom:3000/mcp/call');
        });
        it('appends /mcp/call to mcpHttpUrl when not present', () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                mcpHttpUrl: 'http://custom:3000',
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'http://custom:3000/mcp/call');
        });
        it('appends /mcp/call to mcpHttpUrl with trailing slash', () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                mcpHttpUrl: 'http://custom:3000/',
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'http://custom:3000/mcp/call');
        });
        it('derives HTTP URL from WebSocket URL (ws://)', () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'http://localhost:8080/mcp/call');
        });
        it('derives HTTP URL from WebSocket URL (wss://)', () => {
            const config = {
                serverUrl: 'wss://secure.example.com/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'https://secure.example.com/mcp/call');
        });
        it('handles empty mcpHttpUrl by deriving from serverUrl', () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                mcpHttpUrl: '',
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'http://localhost:8080/mcp/call');
        });
        it('handles whitespace-only mcpHttpUrl', () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                mcpHttpUrl: '   ',
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'http://localhost:8080/mcp/call');
        });
        it('repairs duplicated ports from configured mcpHttpUrl', () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                mcpHttpUrl: 'http://127.0.0.1:8080:8080/mcp/call',
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'http://127.0.0.1:8080/mcp/call');
        });
        it('repairs duplicated ports from serverUrl', () => {
            const config = {
                serverUrl: 'ws://127.0.0.1:8080:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'http://127.0.0.1:8080/mcp/call');
        });
        it('preserves configured MCP HTTP subpaths', () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                mcpHttpUrl: 'http://custom:3000/api',
            };
            const client = new HttpClient(config);
            assert.equal(client.getMcpHttpUrl(), 'http://custom:3000/api/mcp/call');
        });
    });
    describe('callTool', () => {
        it('makes POST request with runtime token headers', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                runtimeAccessToken: 'runtime-token',
            };
            const client = new HttpClient(config);
            await client.callTool('test-tool', { arg1: 'value1' });
            assert.equal(fetchCalls.length, 1);
            const [url, options] = fetchCalls[0];
            assert.equal(url, 'http://localhost:8080/mcp/call');
            assert.equal(options.method, 'POST');
            assert.equal(options.headers['Content-Type'], 'application/json');
            assert.equal(options.headers['X-Runtime-Token'], 'runtime-token');
            assert.equal(options.headers.Authorization, 'Bearer runtime-token');
            assert.equal(options.body, JSON.stringify({ tool: 'test-tool', arguments: { arg1: 'value1' } }));
        });
        it('makes POST request without runtime token when not configured', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            await client.callTool('test-tool', {});
            const [, options] = fetchCalls[0];
            assert.equal(options.headers['X-Runtime-Token'], undefined);
            assert.equal(options.headers.Authorization, undefined);
        });
        it('returns parsed result on success', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            global.fetch = async () => ({
                ok: true,
                json: async () => ({ data: 'test-data', count: 42 }),
            });
            const result = await client.callTool('test-tool', {});
            assert.deepEqual(result, { data: 'test-data', count: 42 });
        });
        it('throws error when response is not ok', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            global.fetch = async () => ({
                ok: false,
                status: 500,
                statusText: 'Internal Server Error',
            });
            await assert.rejects(() => client.callTool('test-tool', {}), /MCP tool call failed: 500 Internal Server Error/);
        });
        it('returns structured error payload when result contains error field', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            global.fetch = async () => ({
                ok: true,
                json: async () => ({ error: 'Tool execution failed' }),
            });
            const result = await client.callTool('test-tool', {});
            assert.deepEqual(result, { error: 'Tool execution failed' });
        });
        it('handles 401 unauthorized response', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            global.fetch = async () => ({
                ok: false,
                status: 401,
                statusText: 'Unauthorized',
            });
            await assert.rejects(() => client.callTool('test-tool', {}), /MCP tool call failed: 401 Unauthorized/);
        });
        it('force-refreshes runtime token and retries once on 401', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                userId: 'user-1',
                runtimeAccessToken: 'stale-token',
                runtimeBridgeBaseUrl: 'http://127.0.0.1:18081',
            };
            const client = new HttpClient(config);
            global.fetch = async (url, options) => {
                fetchCalls.push([url.toString(), options]);
                if (url.toString() === 'http://localhost:8080/mcp/call' && fetchCalls.length === 1) {
                    return {
                        ok: false,
                        status: 401,
                        statusText: 'Unauthorized',
                    };
                }
                if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
                    return {
                        ok: true,
                        status: 200,
                        statusText: 'OK',
                        json: async () => ({ ok: true, runtimeAccessToken: 'fresh-token' }),
                    };
                }
                return {
                    ok: true,
                    status: 200,
                    statusText: 'OK',
                    json: async () => ({ data: 'retried' }),
                };
            };
            const result = await client.callTool('my_task', { agentId: 'agent-1' });
            assert.deepEqual(result, { data: 'retried' });
            assert.equal(fetchCalls.length, 3);
            assert.equal(fetchCalls[0][1].headers['X-Runtime-Token'], 'stale-token');
            assert.equal(fetchCalls[1][0], 'http://127.0.0.1:18081/runtime/binding/request-token');
            assert.equal(fetchCalls[1][1].body, JSON.stringify({
                userId: 'user-1',
                serverUrl: 'ws://localhost:8080/ws/agent',
                forceRefresh: true,
            }));
            assert.equal(fetchCalls[2][1].headers['X-Runtime-Token'], 'fresh-token');
            assert.equal(config.runtimeAccessToken, 'fresh-token');
        });
        it('does not retry 401 more than once after forced token refresh', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
                runtimeAccessToken: 'stale-token',
                runtimeBridgeBaseUrl: 'http://127.0.0.1:18081',
            };
            const client = new HttpClient(config);
            global.fetch = async (url, options) => {
                fetchCalls.push([url.toString(), options]);
                if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
                    return {
                        ok: true,
                        status: 200,
                        statusText: 'OK',
                        json: async () => ({ ok: true, runtimeAccessToken: 'fresh-token' }),
                    };
                }
                return {
                    ok: false,
                    status: 401,
                    statusText: 'Unauthorized',
                };
            };
            await assert.rejects(() => client.callTool('my_task', { agentId: 'agent-1' }), /MCP tool call failed: 401 Unauthorized/);
            assert.equal(fetchCalls.length, 3);
            assert.equal(fetchCalls.filter(([url]) => url === 'http://localhost:8080/mcp/call').length, 2);
            assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/request-token').length, 1);
        });
        it('handles 404 not found response', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            global.fetch = async () => ({
                ok: false,
                status: 404,
                statusText: 'Not Found',
            });
            await assert.rejects(() => client.callTool('unknown-tool', {}), /MCP tool call failed: 404 Not Found/);
        });
        it('handles network errors', async () => {
            const config = {
                serverUrl: 'ws://localhost:8080/ws/agent',
                heartbeatInterval: 15000,
                reconnectInterval: 200,
                maxReconnectAttempts: -1,
            };
            const client = new HttpClient(config);
            global.fetch = async () => {
                throw new Error('Network error');
            };
            await assert.rejects(() => client.callTool('test-tool', {}), /Network error/);
        });
    });
});
//# sourceMappingURL=http-client.test.js.map