import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import TOML from '@iarna/toml';
import { providerMcpService } from '../../../modules/providers/services/mcp.service.js';
import { AppError } from '../../../shared/utils.js';
const patchHomeDir = (nextHomeDir) => {
    const original = os.homedir;
    os.homedir = () => nextHomeDir;
    return () => {
        os.homedir = original;
    };
};
const readJson = async (filePath) => {
    const content = await fs.readFile(filePath, 'utf8');
    return JSON.parse(content);
};
/**
 * This test covers Claude MCP support for all scopes (user/local/project) and all transports (stdio/http/sse),
 * including add, update/list, and remove operations.
 */
test('providerMcpService handles claude MCP scopes/transports with file-backed persistence', { concurrency: false }, async () => {
    const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-claude-'));
    const workspacePath = path.join(tempRoot, 'workspace');
    await fs.mkdir(workspacePath, { recursive: true });
    const restoreHomeDir = patchHomeDir(tempRoot);
    try {
        await providerMcpService.upsertProviderMcpServer('claude', {
            name: 'claude-user-stdio',
            scope: 'user',
            transport: 'stdio',
            command: 'npx',
            args: ['-y', 'my-server'],
            env: { API_KEY: 'secret' },
        });
        await providerMcpService.upsertProviderMcpServer('claude', {
            name: 'claude-local-http',
            scope: 'local',
            transport: 'http',
            url: 'https://example.com/mcp',
            headers: { Authorization: 'Bearer token' },
            workspacePath,
        });
        await providerMcpService.upsertProviderMcpServer('claude', {
            name: 'claude-project-sse',
            scope: 'project',
            transport: 'sse',
            url: 'https://example.com/sse',
            headers: { 'X-API-Key': 'abc' },
            workspacePath,
        });
        const grouped = await providerMcpService.listProviderMcpServers('claude', { workspacePath });
        assert.ok(grouped.user.some((server) => server.name === 'claude-user-stdio' && server.transport === 'stdio'));
        assert.ok(grouped.local.some((server) => server.name === 'claude-local-http' && server.transport === 'http'));
        assert.ok(grouped.project.some((server) => server.name === 'claude-project-sse' && server.transport === 'sse'));
        // update behavior is the same upsert route with same name
        await providerMcpService.upsertProviderMcpServer('claude', {
            name: 'claude-project-sse',
            scope: 'project',
            transport: 'sse',
            url: 'https://example.com/sse-updated',
            headers: { 'X-API-Key': 'updated' },
            workspacePath,
        });
        const projectConfig = await readJson(path.join(workspacePath, '.mcp.json'));
        const projectServers = projectConfig.mcpServers;
        const projectServer = projectServers['claude-project-sse'];
        assert.equal(projectServer.url, 'https://example.com/sse-updated');
        const removeResult = await providerMcpService.removeProviderMcpServer('claude', {
            name: 'claude-local-http',
            scope: 'local',
            workspacePath,
        });
        assert.equal(removeResult.removed, true);
    }
    finally {
        restoreHomeDir();
        await fs.rm(tempRoot, { recursive: true, force: true });
    }
});
/**
 * This test covers Codex MCP support for user/project scopes, stdio/http formats,
 * and validation for unsupported scope/transport combinations.
 */
test('providerMcpService handles codex MCP TOML config and capability validation', { concurrency: false }, async () => {
    const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-codex-'));
    const workspacePath = path.join(tempRoot, 'workspace');
    await fs.mkdir(workspacePath, { recursive: true });
    const restoreHomeDir = patchHomeDir(tempRoot);
    try {
        await providerMcpService.upsertProviderMcpServer('codex', {
            name: 'codex-user-stdio',
            scope: 'user',
            transport: 'stdio',
            command: 'python',
            args: ['server.py'],
            env: { API_KEY: 'x' },
            envVars: ['API_KEY'],
            cwd: '/tmp',
        });
        await providerMcpService.upsertProviderMcpServer('codex', {
            name: 'codex-project-http',
            scope: 'project',
            transport: 'http',
            url: 'https://codex.example.com/mcp',
            headers: { 'X-Custom-Header': 'value' },
            envHttpHeaders: { 'X-API-Key': 'MY_API_KEY_ENV' },
            bearerTokenEnvVar: 'MY_API_TOKEN',
            workspacePath,
        });
        const userTomlPath = path.join(tempRoot, '.codex', 'config.toml');
        const userConfig = TOML.parse(await fs.readFile(userTomlPath, 'utf8'));
        const userServers = userConfig.mcp_servers;
        const userStdio = userServers['codex-user-stdio'];
        assert.equal(userStdio.command, 'python');
        const projectTomlPath = path.join(workspacePath, '.codex', 'config.toml');
        const projectConfig = TOML.parse(await fs.readFile(projectTomlPath, 'utf8'));
        const projectServers = projectConfig.mcp_servers;
        const projectHttp = projectServers['codex-project-http'];
        assert.equal(projectHttp.url, 'https://codex.example.com/mcp');
        await assert.rejects(providerMcpService.upsertProviderMcpServer('codex', {
            name: 'codex-local',
            scope: 'local',
            transport: 'stdio',
            command: 'node',
        }), (error) => error instanceof AppError &&
            error.code === 'MCP_SCOPE_NOT_SUPPORTED' &&
            error.statusCode === 400);
        await assert.rejects(providerMcpService.upsertProviderMcpServer('codex', {
            name: 'codex-sse',
            scope: 'project',
            transport: 'sse',
            url: 'https://example.com/sse',
            workspacePath,
        }), (error) => error instanceof AppError &&
            error.code === 'MCP_TRANSPORT_NOT_SUPPORTED' &&
            error.statusCode === 400);
    }
    finally {
        restoreHomeDir();
        await fs.rm(tempRoot, { recursive: true, force: true });
    }
});
/**
 * This test covers OpenCode MCP support for user/project config files, JSONC-compatible
 * reads, and validation for unsupported scope/transport combinations.
 */
test('providerMcpService handles opencode MCP config and capability validation', { concurrency: false }, async () => {
    const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-opencode-'));
    const workspacePath = path.join(tempRoot, 'workspace');
    await fs.mkdir(workspacePath, { recursive: true });
    await fs.mkdir(path.join(tempRoot, '.config', 'opencode'), { recursive: true });
    await fs.writeFile(path.join(tempRoot, '.config', 'opencode', 'opencode.jsonc'), `{
      // Existing comments should not block OpenCode MCP reads.
      "mcp": {}
    }\n`, 'utf8');
    const restoreHomeDir = patchHomeDir(tempRoot);
    try {
        await providerMcpService.upsertProviderMcpServer('opencode', {
            name: 'opencode-user-stdio',
            scope: 'user',
            transport: 'stdio',
            command: 'node',
            args: ['server.js'],
            env: { API_KEY: 'x' },
        });
        await providerMcpService.upsertProviderMcpServer('opencode', {
            name: 'opencode-project-http',
            scope: 'project',
            transport: 'http',
            url: 'https://opencode.example.com/mcp',
            headers: { Authorization: 'Bearer token' },
            workspacePath,
        });
        const userConfig = await readJson(path.join(tempRoot, '.config', 'opencode', 'opencode.jsonc'));
        const userServers = userConfig.mcp;
        const userStdio = userServers['opencode-user-stdio'];
        assert.equal(userStdio.type, 'local');
        assert.deepEqual(userStdio.command, ['node', 'server.js']);
        assert.deepEqual(userStdio.environment, { API_KEY: 'x' });
        const projectConfig = await readJson(path.join(workspacePath, 'opencode.json'));
        const projectServers = projectConfig.mcp;
        const projectHttp = projectServers['opencode-project-http'];
        assert.equal(projectHttp.type, 'remote');
        assert.equal(projectHttp.url, 'https://opencode.example.com/mcp');
        const grouped = await providerMcpService.listProviderMcpServers('opencode', { workspacePath });
        assert.ok(grouped.user.some((server) => server.name === 'opencode-user-stdio' && server.transport === 'stdio'));
        assert.ok(grouped.project.some((server) => server.name === 'opencode-project-http' && server.transport === 'http'));
        await assert.rejects(providerMcpService.upsertProviderMcpServer('opencode', {
            name: 'opencode-local',
            scope: 'local',
            transport: 'stdio',
            command: 'node',
        }), (error) => error instanceof AppError &&
            error.code === 'MCP_SCOPE_NOT_SUPPORTED' &&
            error.statusCode === 400);
        await assert.rejects(providerMcpService.upsertProviderMcpServer('opencode', {
            name: 'opencode-sse',
            scope: 'project',
            transport: 'sse',
            url: 'https://example.com/sse',
            workspacePath,
        }), (error) => error instanceof AppError &&
            error.code === 'MCP_TRANSPORT_NOT_SUPPORTED' &&
            error.statusCode === 400);
    }
    finally {
        restoreHomeDir();
        await fs.rm(tempRoot, { recursive: true, force: true });
    }
});
/**
 * This test covers Gemini/Cursor MCP JSON formats and user/project scope persistence.
 */
test('providerMcpService handles gemini and cursor MCP JSON config formats', { concurrency: false }, async () => {
    const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-gc-'));
    const workspacePath = path.join(tempRoot, 'workspace');
    await fs.mkdir(workspacePath, { recursive: true });
    const restoreHomeDir = patchHomeDir(tempRoot);
    try {
        await providerMcpService.upsertProviderMcpServer('gemini', {
            name: 'gemini-stdio',
            scope: 'user',
            transport: 'stdio',
            command: 'node',
            args: ['server.js'],
            env: { TOKEN: '$TOKEN' },
            cwd: './server',
        });
        await providerMcpService.upsertProviderMcpServer('gemini', {
            name: 'gemini-http',
            scope: 'project',
            transport: 'http',
            url: 'https://gemini.example.com/mcp',
            headers: { Authorization: 'Bearer token' },
            workspacePath,
        });
        await providerMcpService.upsertProviderMcpServer('cursor', {
            name: 'cursor-stdio',
            scope: 'project',
            transport: 'stdio',
            command: 'npx',
            args: ['-y', 'mcp-server'],
            env: { API_KEY: 'value' },
            workspacePath,
        });
        await providerMcpService.upsertProviderMcpServer('cursor', {
            name: 'cursor-http',
            scope: 'user',
            transport: 'http',
            url: 'http://localhost:3333/mcp',
            headers: { API_KEY: 'value' },
        });
        const geminiUserConfig = await readJson(path.join(tempRoot, '.gemini', 'settings.json'));
        const geminiUserServer = geminiUserConfig.mcpServers['gemini-stdio'];
        assert.equal(geminiUserServer.command, 'node');
        assert.equal(geminiUserServer.type, undefined);
        const geminiProjectConfig = await readJson(path.join(workspacePath, '.gemini', 'settings.json'));
        const geminiProjectServer = geminiProjectConfig.mcpServers['gemini-http'];
        assert.equal(geminiProjectServer.type, 'http');
        const cursorUserConfig = await readJson(path.join(tempRoot, '.cursor', 'mcp.json'));
        const cursorHttpServer = cursorUserConfig.mcpServers['cursor-http'];
        assert.equal(cursorHttpServer.url, 'http://localhost:3333/mcp');
        assert.equal(cursorHttpServer.type, undefined);
    }
    finally {
        restoreHomeDir();
        await fs.rm(tempRoot, { recursive: true, force: true });
    }
});
/**
 * This test covers the global MCP adder requirement: only http/stdio are allowed and
 * one payload is written to all providers.
 */
test('providerMcpService global adder writes to all providers and rejects unsupported transports', { concurrency: false }, async () => {
    const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-global-'));
    const workspacePath = path.join(tempRoot, 'workspace');
    await fs.mkdir(workspacePath, { recursive: true });
    const restoreHomeDir = patchHomeDir(tempRoot);
    try {
        const globalResult = await providerMcpService.addMcpServerToAllProviders({
            name: 'global-http',
            scope: 'project',
            transport: 'http',
            url: 'https://global.example.com/mcp',
            workspacePath,
        });
        const expectCursorGlobal = process.platform !== 'win32';
        assert.equal(globalResult.length, expectCursorGlobal ? 5 : 4);
        assert.ok(globalResult.every((entry) => entry.created === true));
        const claudeProject = await readJson(path.join(workspacePath, '.mcp.json'));
        assert.ok(claudeProject.mcpServers['global-http']);
        const codexProject = TOML.parse(await fs.readFile(path.join(workspacePath, '.codex', 'config.toml'), 'utf8'));
        assert.ok(codexProject.mcp_servers['global-http']);
        const geminiProject = await readJson(path.join(workspacePath, '.gemini', 'settings.json'));
        assert.ok(geminiProject.mcpServers['global-http']);
        const opencodeProject = await readJson(path.join(workspacePath, 'opencode.json'));
        assert.ok(opencodeProject.mcp['global-http']);
        if (expectCursorGlobal) {
            const cursorProject = await readJson(path.join(workspacePath, '.cursor', 'mcp.json'));
            assert.ok(cursorProject.mcpServers['global-http']);
        }
        await assert.rejects(providerMcpService.addMcpServerToAllProviders({
            name: 'global-sse',
            scope: 'project',
            transport: 'sse',
            url: 'https://example.com/sse',
            workspacePath,
        }), (error) => error instanceof AppError &&
            error.code === 'INVALID_GLOBAL_MCP_TRANSPORT' &&
            error.statusCode === 400);
    }
    finally {
        restoreHomeDir();
        await fs.rm(tempRoot, { recursive: true, force: true });
    }
});
//# sourceMappingURL=mcp.test.js.map