Ctrl K
ComunidadGuías…

tests/e2e/mcp-servers-settings.spec.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { Route } from "@playwright/test";
import { expect, test } from "./helpers/run-checks-fixtures";
import { enableScopedE2EAuth, scopedPath } from "./helpers/auth";

type MockServer = {
  id: string;
  name: string;
  enabled: boolean;
  transport: "stdio" | "http";
  command: string | null;
  args: string[];
  envPlain: Record<string, string>;
  envSecretNames: string[];
  url: string | null;
  headerPlain: Record<string, string>;
  headerSecretNames: string[];
  extra: Record<string, unknown>;
  createdAt: string;
  updatedAt: string;
};

const connectedUser = {
  id: "user-1",
  email: "alex@example.com",
  username: "alex",
  name: "Alex",
  avatar_url: "https://example.com/avatar.png",
  github_connected: true,
  github_app_connected: true,
  github_app_available: true,
  github_connection_mode: "app" as const,
  platform_access: {
    allowPlatformAi: true,
    allowPlatformSandbox: true,
  },
};

async function fulfillJson(route: Route, data: unknown, status = 200) {
  await route.fulfill({
    status,
    contentType: "application/json",
    body: JSON.stringify(data),
  });
}

test("Connections MCP tab supports add, edit, secret overwrite, and delete without exposing refs", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);

  const postBodies: unknown[] = [];
  const patchBodies: unknown[] = [];
  let servers: MockServer[] = [];

  await page.route("**/api/auth/user", (route) =>
    fulfillJson(route, { user: connectedUser })
  );
  await page.route(/\/api\/repos(?:\?.*)?$/, (route) => fulfillJson(route, []));
  await page.route(
    /\/api\/mcp-servers(?:\/[^/?]+)?(?:\?.*)?$/,
    async (route) => {
      const request = route.request();
      const url = new URL(request.url());
      const pathSegments = url.pathname.split("/").filter(Boolean);
      const maybeId = pathSegments.length === 3 ? pathSegments[2] : null;

      if (request.method() === "GET") {
        if (maybeId) {
          const server = servers.find((entry) => entry.id === maybeId);
          if (!server) {
            await fulfillJson(route, { error: "Server not found" }, 404);
            return;
          }
          await fulfillJson(route, { server });
          return;
        }

        await fulfillJson(route, { servers });
        return;
      }

      if (request.method() === "POST") {
        const body = JSON.parse(request.postData() || "{}") as Record<
          string,
          unknown
        >;
        postBodies.push(body);

        expect(body).not.toHaveProperty("env_refs");
        expect(body).not.toHaveProperty("header_refs");

        const now = new Date().toISOString();
        const server: MockServer = {
          id: "server-1",
          name: String(body.name),
          enabled: body.enabled !== false,
          transport: body.transport === "http" ? "http" : "stdio",
          command: typeof body.command === "string" ? body.command : null,
          args: Array.isArray(body.args) ? (body.args as string[]) : [],
          envPlain: (body.envPlain as Record<string, string>) ?? {},
          envSecretNames: Object.keys(
            (body.envSecrets as Record<string, string>) ?? {}
          ),
          url: typeof body.url === "string" ? body.url : null,
          headerPlain: (body.headerPlain as Record<string, string>) ?? {},
          headerSecretNames: Object.keys(
            (body.headerSecrets as Record<string, string>) ?? {}
          ),
          extra: (body.extra as Record<string, unknown>) ?? {},
          createdAt: now,
          updatedAt: now,
        };
        servers = [server];
        await fulfillJson(route, { server }, 201);
        return;
      }

      if (request.method() === "PATCH" && maybeId) {
        const body = JSON.parse(request.postData() || "{}") as Record<
          string,
          unknown
        >;
        patchBodies.push(body);

        expect(body).not.toHaveProperty("env_refs");
        expect(body).not.toHaveProperty("header_refs");

        const current = servers.find((entry) => entry.id === maybeId);
        expect(current).toBeTruthy();

        const envSecrets =
          (body.envSecrets as Record<string, string | null>) ?? {};
        const nextEnvSecretNames = new Set(current?.envSecretNames);
        for (const [key, value] of Object.entries(envSecrets)) {
          if (value === null) {
            nextEnvSecretNames.delete(key);
          } else {
            nextEnvSecretNames.add(key);
          }
        }

        const headerSecrets =
          (body.headerSecrets as Record<string, string | null>) ?? {};
        const nextHeaderSecretNames = new Set(current?.headerSecretNames);
        for (const [key, value] of Object.entries(headerSecrets)) {
          if (value === null) {
            nextHeaderSecretNames.delete(key);
          } else {
            nextHeaderSecretNames.add(key);
          }
        }

        const updated: MockServer = {
          id: maybeId,
          name: String(body.name),
          enabled: body.enabled !== false,
          transport: body.transport === "http" ? "http" : "stdio",
          command: typeof body.command === "string" ? body.command : null,
          args: Array.isArray(body.args) ? (body.args as string[]) : [],
          envPlain: (body.envPlain as Record<string, string>) ?? {},
          envSecretNames: [...nextEnvSecretNames],
          url: typeof body.url === "string" ? body.url : null,
          headerPlain: (body.headerPlain as Record<string, string>) ?? {},
          headerSecretNames: [...nextHeaderSecretNames],
          extra: (body.extra as Record<string, unknown>) ?? {},
          createdAt: current?.createdAt ?? new Date().toISOString(),
          updatedAt: new Date().toISOString(),
        };

        servers = [updated];
        await fulfillJson(route, { server: updated });
        return;
      }

      if (request.method() === "DELETE" && maybeId) {
        servers = servers.filter((entry) => entry.id !== maybeId);
        await fulfillJson(route, { ok: true });
        return;
      }

      await fulfillJson(route, { error: "Unsupported route in test" }, 500);
    }
  );

  await page.goto(scopedPath("connections?tab=mcp"));

  await page.getByRole("button", { name: "Add server" }).click();
  await expect(
    page.getByRole("tab", { name: "Streamable HTTP" })
  ).toHaveAttribute("aria-selected", "true");
  await expect(
    page.getByText(
      "Available in web chat and the CLI. Web chat requires a public server URL."
    )
  ).toBeVisible();
  await page.getByRole("tab", { name: "Local (CLI only)" }).click();
  await expect(
    page.getByText(
      "Runs on your computer through the CLI. Not available in web chat."
    )
  ).toBeVisible();
  await page.getByLabel("Name", { exact: true }).fill("supabase");
  await page.getByLabel("Command").fill("npx");
  await page
    .getByLabel("Args")
    .fill("-y\n@supabase/mcp-server-supabase@latest");
  await page.getByRole("button", { name: "Add secret environment" }).click();
  await page.getByPlaceholder("Environment name").fill("SUPABASE_ACCESS_TOKEN");
  await page.getByPlaceholder("Secret value").fill("sbp_initial_123");
  await page.getByRole("button", { name: "Add secret environment" }).click();
  await page
    .getByPlaceholder("Environment name")
    .last()
    .fill("SUPABASE_PROJECT_TOKEN");
  await page
    .getByPlaceholder("Secret value")
    .last()
    .fill("project_preview_456");
  const primarySecret = page.getByLabel("SUPABASE_ACCESS_TOKEN", {
    exact: true,
  });
  const secondarySecret = page.getByLabel("SUPABASE_PROJECT_TOKEN", {
    exact: true,
  });
  await page
    .getByRole("button", { name: "Show SUPABASE_ACCESS_TOKEN", exact: true })
    .click();
  await expect(primarySecret).toHaveAttribute("type", "text");
  await expect(primarySecret).toHaveValue("sbp_initial_123");
  expect(postBodies).toHaveLength(0);
  await expect(secondarySecret).toHaveAttribute("type", "password");
  await expect(secondarySecret).toHaveValue("project_preview_456");
  await page
    .getByRole("button", { name: "Hide SUPABASE_ACCESS_TOKEN", exact: true })
    .click();
  await expect(primarySecret).toHaveAttribute("type", "password");
  await page.getByRole("button", { name: "Create server" }).click();

  await expect(page.getByText("supabase")).toBeVisible();
  await expect(page.getByText("CLI only", { exact: true })).toBeVisible();
  expect(postBodies).toHaveLength(1);

  await page.getByRole("button", { name: "Edit" }).click();
  await expect(
    page.getByRole("dialog").getByText("Saved", { exact: true })
  ).toHaveCount(2);
  await expect(primarySecret).toHaveValue("");
  await expect(secondarySecret).toHaveValue("");
  await expect(
    page.getByRole("button", {
      name: "Saved SUPABASE_ACCESS_TOKEN cannot be shown",
      exact: true,
    })
  ).toBeDisabled();
  await page.getByRole("button", { name: "Save changes" }).click();

  await page.getByRole("button", { name: "Edit" }).click();
  await primarySecret.fill("sbp_updated_456");
  await page
    .getByRole("button", { name: "Show SUPABASE_ACCESS_TOKEN", exact: true })
    .click();
  await expect(primarySecret).toHaveValue("sbp_updated_456");
  await expect(primarySecret).toHaveAttribute("type", "text");
  await page.getByRole("button", { name: "Save changes" }).click();

  expect(patchBodies).toHaveLength(2);
  expect(patchBodies[0]).toMatchObject({
    envSecrets: {},
  });
  expect(patchBodies[1]).toMatchObject({
    envSecrets: {
      SUPABASE_ACCESS_TOKEN: "sbp_updated_456",
    },
  });
  expect(patchBodies[1]).not.toHaveProperty(
    "envSecrets.SUPABASE_PROJECT_TOKEN"
  );

  await page.getByRole("button", { name: "Delete" }).click();
  await page
    .getByRole("alertdialog")
    .getByRole("button", { name: "Delete" })
    .click();
  await expect(page.getByText("No synced MCP servers yet.")).toBeVisible();

  await page.getByRole("button", { name: "Add server" }).click();
  await page.getByLabel("Name", { exact: true }).fill("remote-docs");
  await page
    .getByLabel("URL", { exact: true })
    .fill("https://docs.example.com/mcp");
  await page.getByRole("button", { name: "Add secret header" }).click();
  await page.getByPlaceholder("Header name").fill("Authorization");
  await page.getByPlaceholder("Secret value").fill("Bearer saved-http-secret");
  await page.getByRole("button", { name: "Create server" }).click();
  await expect(page.getByText("remote-docs", { exact: true })).toBeVisible();
  expect(postBodies[1]).toMatchObject({
    transport: "http",
    enabled: true,
    url: "https://docs.example.com/mcp",
    headerSecrets: { Authorization: "Bearer saved-http-secret" },
  });
  await page.getByRole("button", { name: "Edit" }).click();
  await expect(page.getByPlaceholder("Overwrite saved secret")).toHaveValue("");
  await page.getByRole("switch", { name: "Server enabled" }).click();
  await page.getByRole("button", { name: "Save changes" }).click();
  await expect(page.getByText("Disabled", { exact: true })).toBeVisible();
  expect(patchBodies[2]).toMatchObject({ enabled: false, headerSecrets: {} });
});