All files / src/entities/integrations registry.ts

97.36% Statements 37/38
86.66% Branches 13/15
100% Functions 6/6
97.36% Lines 37/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 1553x 3x 3x 3x 3x                   3x                         3x 3x   3x               3x                                   11x     7x       7x 7x     7x 7x 2x         5x     2x                       2x     2x 2x 1x 1x     2x                     1x     1x                                 3x 11x           3x 4x           3x     2x 1x 1x 2x     1x    
import * as z from "zod";
import { ListIntegrationsSchema } from "./schema-readonly";
import { ManageIntegrationSchema } from "./schema";
import { gitlab, toQuery } from "../../utils/gitlab-api";
import { getEffectiveProjectId, isActionDenied } from "../../config";
import { ToolRegistry, EnhancedToolDefinition } from "../../types";
 
/**
 * Integrations tools registry - 2 CQRS tools for managing GitLab project integrations
 * Uses discriminated union schemas for type-safe action handling.
 *
 * list_integrations (Query): list all active integrations
 * manage_integration (Command): get, update, disable
 */
export const integrationsToolRegistry: ToolRegistry = new Map<string, EnhancedToolDefinition>([
  // ============================================================================
  // list_integrations - CQRS Query Tool
  // ============================================================================
  [
    "list_integrations",
    {
      name: "list_integrations",
      description:
        "LIST all active integrations for a project. Returns integrations like Slack, Jira, Discord, Microsoft Teams, Jenkins, etc. Only shows enabled/configured integrations.",
      inputSchema: z.toJSONSchema(ListIntegrationsSchema, {}),
      gate: { envVar: "USE_INTEGRATIONS", defaultValue: true },
      handler: async (args: unknown) => {
        const input = ListIntegrationsSchema.parse(args);
        const projectId = getEffectiveProjectId(input.project_id);
 
        const query = toQuery(
          {
            per_page: input.per_page,
            page: input.page,
          },
          []
        );
 
        return gitlab.get(`projects/${encodeURIComponent(projectId)}/integrations`, { query });
      },
    },
  ],
 
  // ============================================================================
  // manage_integration - CQRS Command Tool (discriminated union schema)
  // TypeScript automatically narrows types in each switch case
  // ============================================================================
  [
    "manage_integration",
    {
      name: "manage_integration",
      description:
        'MANAGE project integrations. Actions: "get" retrieves integration settings (read-only), "update" modifies or enables integration with specific config, "disable" removes integration. Supports 50+ integrations: Slack, Jira, Discord, Teams, Jenkins, etc. Note: gitlab-slack-application cannot be created via API - requires OAuth install from UI.',
      inputSchema: z.toJSONSchema(ManageIntegrationSchema, {}),
      gate: { envVar: "USE_INTEGRATIONS", defaultValue: true },
      handler: async (args: unknown) => {
        const input = ManageIntegrationSchema.parse(args);
 
        // Runtime validation: reject denied actions even if they bypass schema filtering
        Iif (isActionDenied("manage_integration", input.action)) {
          throw new Error(`Action '${input.action}' is not allowed for manage_integration tool`);
        }
 
        const projectId = getEffectiveProjectId(input.project_id);
        const integrationSlug = input.integration;
 
        // Enforce read-only mode for write actions (check dynamically for testability)
        const isReadOnly = process.env.GITLAB_READ_ONLY_MODE === "true";
        if (isReadOnly && (input.action === "update" || input.action === "disable")) {
          throw new Error(
            `Action '${input.action}' is not allowed in read-only mode. Only 'get' action is permitted.`
          );
        }
 
        switch (input.action) {
          case "get": {
            // TypeScript knows: input has project_id, integration (required)
            return gitlab.get(
              `projects/${encodeURIComponent(projectId)}/integrations/${integrationSlug}`
            );
          }
 
          case "update": {
            // TypeScript knows: input has project_id, integration (required), plus event fields and config (optional)
            const {
              action: _action,
              project_id: _project_id,
              integration: _integration,
              ...body
            } = input;
 
            // Flatten config object if provided
            let finalBody = { ...body };
            if (body.config) {
              const { config, ...rest } = body;
              finalBody = { ...rest, ...config };
            }
 
            return gitlab.put(
              `projects/${encodeURIComponent(projectId)}/integrations/${integrationSlug}`,
              {
                body: finalBody,
                contentType: "json",
              }
            );
          }
 
          case "disable": {
            // TypeScript knows: input has project_id, integration (required)
            await gitlab.delete(
              `projects/${encodeURIComponent(projectId)}/integrations/${integrationSlug}`
            );
            return { deleted: true };
          }
 
          /* istanbul ignore next -- unreachable with Zod discriminatedUnion */
          default:
            throw new Error(`Unknown action: ${(input as { action: string }).action}`);
        }
      },
    },
  ],
]);
 
/**
 * Get read-only tool names from the registry
 * Note: manage_integration is included because it supports a read-only action ('get').
 * Write actions ('update', 'disable') are blocked at the handler level when GITLAB_READ_ONLY_MODE is enabled.
 */
export function getIntegrationsReadOnlyToolNames(): string[] {
  return ["list_integrations", "manage_integration"];
}
 
/**
 * Get all tool definitions from the registry
 */
export function getIntegrationsToolDefinitions(): EnhancedToolDefinition[] {
  return Array.from(integrationsToolRegistry.values());
}
 
/**
 * Get filtered tools based on read-only mode
 */
export function getFilteredIntegrationsTools(
  readOnlyMode: boolean = false
): EnhancedToolDefinition[] {
  if (readOnlyMode) {
    const readOnlyNames = getIntegrationsReadOnlyToolNames();
    return Array.from(integrationsToolRegistry.values()).filter(tool =>
      readOnlyNames.includes(tool.name)
    );
  }
  return getIntegrationsToolDefinitions();
}