"use strict";
/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Camunda License 1.0. You may not use this file
 * except in compliance with the Camunda License 1.0.
 */
Object.defineProperty(exports, "__esModule", { value: true });
/*
 * Tests for POST /mcp/processes — tools/list endpoint.
 *
 * Direct JSON-RPC 2.0 calls to the /mcp/processes MCP server. No browser
 * interaction; authentication and BPMN deployment use the same helpers as
 * other tests in this folder.
 */
const _8_10_1 = require("../../../../fixtures/8.10");
const test_1 = require("@playwright/test");
const apiHelpers_1 = require("../../../../utils/apiHelpers");
const constants_1 = require("../../../../utils/constants");
const mcpHelpers_1 = require("../../../../utils/mcpHelpers");
let authToken;
let mcpProcessesUrl;
let messageSubscriptionsSearchUrl;
let mcpSessionId = null;
_8_10_1.test.describe
    .parallel('Orchestration Cluster MCP Server — /mcp/processes tools/list', () => {
    _8_10_1.test.beforeAll(async () => {
        authToken = await (0, apiHelpers_1.authSaasAPI)(undefined, 'agentic_cluster');
        mcpProcessesUrl = (0, apiHelpers_1.buildZeebeApiUrl)('/mcp/processes', 'saas', 'agentic_cluster');
        messageSubscriptionsSearchUrl = (0, apiHelpers_1.buildZeebeApiUrl)('/v2/message-subscriptions/search', 'saas', 'agentic_cluster');
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        mcpSessionId = await (0, mcpHelpers_1.initializeMcpSession)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
        // Deploy independent processes concurrently to reduce beforeAll runtime.
        // Process definitions are never deleted (the C8 REST API does not expose a
        // deletion endpoint), so each run on the shared agentic cluster accumulates
        // versions. Tests use regex matchers and subscription-key lookups rather
        // than fixed names to stay robust across accumulated versions.
        const [toolProcessKey, withInputsKey, plainKey] = await Promise.all([
            (0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_as_tool_process.bpmn', authToken, 'saas', 'agentic_cluster'),
            (0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_as_tool_with_inputs.bpmn', authToken, 'saas', 'agentic_cluster'),
            (0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_plain_process.bpmn', authToken, 'saas', 'agentic_cluster'),
        ]);
        if (toolProcessKey == null)
            throw new Error('Failed to deploy mcp_as_tool_process.bpmn');
        if (withInputsKey == null)
            throw new Error('Failed to deploy mcp_as_tool_with_inputs.bpmn');
        if (plainKey == null)
            throw new Error('Failed to deploy mcp_plain_process.bpmn');
        // Deploy v1 then v2 sequentially — order matters so version 2 is the latest.
        // Camunda deduplicates identical BPMN content, so v1 and v2 differ only in
        // their purpose property to force a genuine new version.
        const v1Key = await (0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_as_tool_v1.bpmn', authToken, 'saas', 'agentic_cluster');
        if (v1Key == null)
            throw new Error('Failed to deploy mcp_as_tool_v1.bpmn');
        const v2Key = await (0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_as_tool_v2.bpmn', authToken, 'saas', 'agentic_cluster');
        if (v2Key == null)
            throw new Error('Failed to deploy mcp_as_tool_v2.bpmn');
    });
    (0, _8_10_1.test)('SC-MCP-02 — Static observability tools always present in tools/list', async () => {
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        await (0, test_1.expect)(async () => {
            const tools = await (0, mcpHelpers_1.listTools)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
            const names = tools.map((t) => t.name);
            (0, test_1.expect)(names).toContain('getProcessInstance');
            (0, test_1.expect)(names).toContain('searchVariables');
            (0, test_1.expect)(names).toContain('searchIncidents');
        }).toPass(constants_1.defaultAssertionOptions);
    });
    (0, _8_10_1.test)('SC-MCP-03 — Deployed MCP process appears as a dynamic tool', async () => {
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        await (0, test_1.expect)(async () => {
            const tools = await (0, mcpHelpers_1.listTools)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
            const hasToolProcess = tools.some((t) => /^mcp-as-tool-process_\d+$/.test(t.name));
            (0, test_1.expect)(hasToolProcess).toBe(true);
        }).toPass(constants_1.defaultAssertionOptions);
    });
    (0, _8_10_1.test)('SC-MCP-04 — Tool name encodes the messageSubscriptionKey from the search API', async () => {
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        // Resolve the subscription key from the REST API first.
        // Sort by processDefinitionVersion DESC so we always get the latest deployed
        // version's subscription key, not a stale one from a previous CI run.
        let subscriptionKey = null;
        await _8_10_1.test.step('Resolve messageSubscriptionKey via REST API', async () => {
            await (0, test_1.expect)(async () => {
                const res = await apiContext.post(messageSubscriptionsSearchUrl, {
                    headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
                    data: {
                        filter: {
                            processDefinitionId: 'mcpAsToolProcess',
                            messageSubscriptionType: 'START_EVENT',
                        },
                        sort: [{ field: 'processDefinitionVersion', order: 'desc' }],
                        page: { limit: 1 },
                    },
                });
                await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
                const json = await res.json();
                (0, test_1.expect)(json.items.length).toBeGreaterThanOrEqual(1);
                subscriptionKey = String(json.items[0].messageSubscriptionKey);
            }).toPass(constants_1.defaultAssertionOptions);
        });
        await _8_10_1.test.step('Verify tool name in tools/list matches subscriptionKey', async () => {
            const expectedName = `mcp-as-tool-process_${subscriptionKey}`;
            await (0, test_1.expect)(async () => {
                const tools = await (0, mcpHelpers_1.listTools)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
                const names = tools.map((t) => t.name);
                (0, test_1.expect)(names).toContain(expectedName);
            }).toPass(constants_1.defaultAssertionOptions);
        });
    });
    (0, _8_10_1.test)('SC-MCP-05 — Process with io.camunda.tool:inputs appears as a tool with an object inputSchema', async () => {
        // The server exposes processes as tools regardless of their io.camunda.tool:inputs
        // content. inputSchema is always an empty object schema in this server version
        // (typed JSON Schema generation from extension properties is not yet implemented).
        // extensionProperties are not populated on the entity returned by the internal
        // search service, so description is always empty — not asserted here.
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        await (0, test_1.expect)(async () => {
            const tools = await (0, mcpHelpers_1.listTools)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
            const withInputsTool = tools.find((t) => /^mcp-as-tool-with-inputs_\d+$/.test(t.name));
            (0, test_1.expect)(withInputsTool).toBeDefined();
            const schema = withInputsTool.inputSchema;
            (0, test_1.expect)(schema).toBeDefined();
            (0, test_1.expect)(schema.type).toBe('object');
        }).toPass(constants_1.defaultAssertionOptions);
    });
    (0, _8_10_1.test)('SC-MCP-06 — Multiple deployed MCP processes each appear as a separate tool', async () => {
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        await (0, test_1.expect)(async () => {
            const tools = await (0, mcpHelpers_1.listTools)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
            const hasToolProcess = tools.some((t) => /^mcp-as-tool-process_\d+$/.test(t.name));
            const hasWithInputs = tools.some((t) => /^mcp-as-tool-with-inputs_\d+$/.test(t.name));
            (0, test_1.expect)(hasToolProcess).toBe(true);
            (0, test_1.expect)(hasWithInputs).toBe(true);
        }).toPass(constants_1.defaultAssertionOptions);
    });
    (0, _8_10_1.test)('SC-MCP-07 — Message start event without io.camunda.tool:name does not appear as a tool', async () => {
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        // mcp_plain_process.bpmn has a message start event but no io.camunda.tool:* properties.
        // It must not produce a dynamic tool entry in tools/list.
        // Both conditions are checked on the same snapshot inside the retry loop so
        // that eventual consistency cannot produce a false-positive on the negative case.
        await (0, test_1.expect)(async () => {
            const tools = await (0, mcpHelpers_1.listTools)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
            // Positive anchor: wait until the cluster is settled (mcp-as-tool-process present).
            (0, test_1.expect)(tools.some((t) => /^mcp-as-tool-process_\d+$/.test(t.name))).toBe(true);
            // Negative assertion on the same snapshot.
            (0, test_1.expect)(tools.some((t) => /mcp-plain-process/.test(t.name))).toBe(false);
        }).toPass(constants_1.defaultAssertionOptions);
    });
    (0, _8_10_1.test)('SC-MCP-08 — Tool disappears from tools/list after redeploying without MCP annotation', async () => {
        // Process definition deletion is not available in the C8 REST API.
        // Redeploying the same process with the io.camunda.tool:name property removed
        // creates a new version whose message subscription has no toolName. Camunda
        // marks the old subscription as DELETED, which removes it from tools/list.
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        await _8_10_1.test.step('Deploy temp MCP tool process', async () => {
            const tempKey = await (0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_as_tool_temp.bpmn', authToken, 'saas', 'agentic_cluster');
            if (tempKey == null) {
                throw new Error('Failed to deploy temp tool process');
            }
        });
        await _8_10_1.test.step('Wait for temp tool to appear in tools/list', async () => {
            await (0, test_1.expect)(async () => {
                const tools = await (0, mcpHelpers_1.listTools)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
                const hasTemp = tools.some((t) => /^mcp-temp-tool_\d+$/.test(t.name));
                (0, test_1.expect)(hasTemp).toBe(true);
            }).toPass(constants_1.defaultAssertionOptions);
        });
        await _8_10_1.test.step('Redeploy without io.camunda.tool:name annotation', async () => {
            // Old subscription transitions to DELETED, new subscription has no toolName
            // and is excluded from tools/list by the server's filter.
            const disabledKey = await (0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_as_tool_temp_disabled.bpmn', authToken, 'saas', 'agentic_cluster');
            if (disabledKey == null) {
                throw new Error('Failed to deploy temp tool disabled process');
            }
        });
        await _8_10_1.test.step('Wait for temp tool to disappear from tools/list', async () => {
            await (0, test_1.expect)(async () => {
                const tools = await (0, mcpHelpers_1.listTools)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
                const hasTemp = tools.some((t) => /^mcp-temp-tool_\d+$/.test(t.name));
                (0, test_1.expect)(hasTemp).toBe(false);
            }).toPass(constants_1.deletionAssertionOptions);
        });
    });
    (0, _8_10_1.test)('SC-MCP-09 — Latest version of a versioned process appears as a tool', async () => {
        // The server exposes ALL active message subscriptions that carry the
        // io.camunda.tool:name property, regardless of process definition version.
        // A subscription transitions to DELETED only when the process is redeployed
        // WITHOUT the MCP annotation (tested in SC-MCP-08); redeploying WITH the same
        // annotation keeps both the old and new subscriptions ACTIVE.
        //
        // On a shared cluster, previous CI runs accumulate versions — each deployment
        // adds a new subscription that stays active indefinitely (no deletion API).
        // Therefore we only assert the POSITIVE case: the latest version's subscription
        // key IS present in tools/list. We do not assert that older keys are absent,
        // because the server correctly surfaces all active annotated subscriptions.
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        // Fetch the latest subscription for mcpVersionedTool (version DESC, limit 1).
        let latestSubscriptionKey = null;
        await _8_10_1.test.step('Fetch latest subscription key for mcpVersionedTool', async () => {
            await (0, test_1.expect)(async () => {
                const res = await apiContext.post(messageSubscriptionsSearchUrl, {
                    headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
                    data: {
                        filter: {
                            processDefinitionId: 'mcpVersionedTool',
                            messageSubscriptionType: 'START_EVENT',
                        },
                        sort: [{ field: 'processDefinitionVersion', order: 'desc' }],
                        page: { limit: 1 },
                    },
                });
                await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
                const json = await res.json();
                (0, test_1.expect)(json.items.length).toBeGreaterThanOrEqual(1);
                latestSubscriptionKey = String(json.items[0].messageSubscriptionKey);
            }).toPass(constants_1.defaultAssertionOptions);
        });
        await _8_10_1.test.step('Verify latest subscription key appears as a tool in tools/list', async () => {
            // The latest version's subscription key must appear as a tool in tools/list.
            await (0, test_1.expect)(async () => {
                const tools = await (0, mcpHelpers_1.listTools)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
                const toolNames = tools.map((t) => t.name);
                const expectedTool = `mcp-versioned-tool_${latestSubscriptionKey}`;
                (0, test_1.expect)(toolNames).toContain(expectedTool);
            }).toPass(constants_1.defaultAssertionOptions);
        });
    });
    (0, _8_10_1.test)('SC-MCP-10 — Unauthenticated request to /mcp/processes is rejected', async () => {
        const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
        const res = await apiContext.post(mcpProcessesUrl, {
            headers: {
                'Content-Type': 'application/json',
                Accept: 'application/json, text/event-stream',
            },
            data: { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} },
        });
        await (0, mcpHelpers_1.assertResponseStatus)(res, [401, 403]);
    });
});
