Ctrl K
ComunidadGuías…

tests/e2e/team-repository-scope.spec.ts

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

Archivo original ↗Preguntar a KiBot
import { expect, test, type Page } from "@playwright/test";
import { buildE2EAuthHeaders, E2E_SCOPE_USER } from "./helpers/auth";
import {
  fulfillJson,
  mockBaseChrome,
} from "./helpers/automation-control-plane-fixtures";

const team = {
  id: "00000000-0000-4000-8000-000000000002",
  slug: "acme",
  name: "Acme",
  iconUrl: null,
};

test("a team repository opens and starts its Control task in the same team scope", async ({
  page,
}) => {
  await installTeamRepositories(page);
  const session = {
    id: "team-task",
    title: "Review the team project",
    project: "acme/team-project",
    repo_id: "team-repo",
    model_id: null,
    orchestration_run_id: null,
    messages: [],
    pinned: false,
    archived: false,
    updated_at: "2026-10-10T12:00:00Z",
  };
  let created = false;
  await page.route("**/api/control/sessions**", (route) => {
    const request = route.request();
    if (request.method() === "POST") {
      if (request.headers()["x-mogplex-team-id"] !== team.id)
        return fulfillJson(
          route,
          { error: "Repository not found in personal scope" },
          404
        );
      created = true;
      return fulfillJson(route, session);
    }
    if (request.method() === "PUT") return fulfillJson(route, { session });
    return fulfillJson(
      route,
      new URL(request.url()).searchParams.has("id")
        ? session
        : created
          ? [session]
          : []
    );
  });
  await page.route("**/api/control/chat", (route) =>
    route.fulfill({
      headers: {
        "content-type": "text/event-stream",
        "x-vercel-ai-ui-message-stream": "v1",
      },
      body:
        [
          { type: "start", messageId: "reply" },
          { type: "text-start", id: "text" },
          { type: "text-delta", id: "text", delta: "Team task ready." },
          { type: "text-end", id: "text" },
          { type: "finish" },
        ]
          .map((event) => `data: ${JSON.stringify(event)}\n\n`)
          .join("") + "data: [DONE]\n\n",
    })
  );
  await page.goto("/acme/control?repo=team-repo");
  await expect(
    page.getByRole("combobox", { name: "Project", exact: true })
  ).toHaveText("acme/team-project");
  await page
    .getByPlaceholder("Ask anything or run a command...")
    .fill("Review the team project");
  await page
    .getByRole("button", { name: "Start mission", exact: true })
    .click();
  await expect(page.getByRole("log", { name: "Conversation" })).toContainText(
    "Team task ready."
  );
  await expect(page).toHaveURL(/\/acme\/control\?mission=team-task/);
});

async function installTeamRepositories(page: Page, canManage = true) {
  await page.context().setExtraHTTPHeaders({
    ...buildE2EAuthHeaders(E2E_SCOPE_USER.id),
    "x-mogplex-scope-kind": "team",
    "x-mogplex-scope-slug": team.slug,
    "x-mogplex-scope-id": team.id,
  });
  await mockBaseChrome(page);
  await page.route("**/api/memberships", (route) =>
    fulfillJson(route, {
      personal: {
        slug: E2E_SCOPE_USER.username,
        name: "Alex",
        avatarUrl: null,
      },
      teams: [{ ...team, role: "owner" }],
    })
  );
  await page.route("**/api/connections", (route) =>
    fulfillJson(route, { connections: [] })
  );
  await page.route("**/api/sandbox", (route) =>
    fulfillJson(route, { sandboxes: [] })
  );
  await page.route("**/api/agents", (route) =>
    fulfillJson(route, [
      {
        id: "agent-1",
        name:
          route.request().headers()["x-mogplex-team-id"] === team.id
            ? "Reviewer"
            : "Personal agent",
      },
    ])
  );
  await page.route("**/api/assignments", (route) =>
    fulfillJson(route, [
      {
        id: "assignment-1",
        repo_id:
          route.request().headers()["x-mogplex-team-id"] === team.id
            ? "team-repo"
            : "personal-repo",
        agent_id: "agent-1",
        enabled: true,
        can_manage: canManage,
      },
    ])
  );
  const teams: Array<string | undefined> = [];
  await page.route("**/api/repos**", (route) => {
    const teamId = route.request().headers()["x-mogplex-team-id"];
    teams.push(teamId);
    const rows = [
      {
        id: teamId ? "team-repo" : "personal-repo",
        full_name: teamId ? "acme/team-project" : "alex/personal-project",
        owner: teamId ? "acme" : "alex",
        name: teamId ? "team-project" : "personal-project",
        github_installation_id: 42,
        github_app_covered: true,
        github_prefer_installation_coverage: true,
      },
      {
        id: "hidden-team-repo",
        full_name: "acme/removed-project",
        owner: "acme",
        name: "removed-project",
        github_installation_id: 42,
        github_app_covered: true,
        is_hidden: true,
      },
      {
        id: "legacy-repo",
        full_name: "acme/legacy-project",
        name: "legacy-project",
        owner: "acme",
        github_prefer_installation_coverage: true,
      },
    ];
    return fulfillJson(
      route,
      new URL(route.request().url()).searchParams.get("show_hidden") === "true"
        ? rows
        : rows.filter((row) => !row.is_hidden)
    );
  });
  await page.route("**/api/workspaces", (route) => fulfillJson(route, []));
  await page.route("**/api/github/repos", (route) =>
    fulfillJson(route, [
      {
        id: "team-repo",
        full_name: "acme/team-project",
        owner: "acme",
        name: "team-project",
      },
    ])
  );
  return teams;
}

test("team assignments and dashboard chrome share one team repository request", async ({
  page,
}) => {
  const teams = await installTeamRepositories(page);
  await page.goto("/acme/assignments");
  await expect(page.getByText("team-project → Reviewer")).toBeVisible();
  await expect(page.locator(".app-statusbar")).toContainText("repos: 1");
  expect(teams).toEqual([team.id]);
});

test("showing removed team repositories reuses the scoped cache", async ({
  page,
}) => {
  const teams = await installTeamRepositories(page);
  await page.goto("/acme/settings/repositories");
  await expect(
    page.getByRole("button", { name: /Show removed/ })
  ).toBeVisible();
  await expect(page.getByText(/removed-project/)).toHaveCount(0);
  await page.waitForLoadState("networkidle");
  // Toggling removed repositories must reuse the full scoped collection
  // without another request.
  expect(teams).toEqual([team.id]);
  await page.getByRole("button", { name: /Show removed/ }).click();
  await expect(page.getByText(/removed-project/).first()).toBeVisible();
  await expect(page.locator(".app-statusbar")).toContainText("repos: 1");
  await page.waitForLoadState("networkidle");
  expect(teams).toEqual([team.id]);
});

test("team repository settings and status bar share the scoped repository cache", async ({
  page,
}) => {
  const teams = await installTeamRepositories(page);
  await page.goto("/acme/settings/repositories");
  await expect(page.getByText(/team-project/).first()).toBeVisible();
  await expect(page.locator(".app-statusbar")).toContainText("repos: 1");
  await page.waitForLoadState("networkidle");
  expect(teams).toEqual([team.id]);
});

test("team viewers see assignments with management disabled", async ({
  page,
}) => {
  await installTeamRepositories(page, false);
  await page.goto("/acme/assignments");
  await expect(page.getByText("team-project → Reviewer")).toBeVisible();
  await page.getByRole("button", { name: "Assignment actions" }).click();
  await expect(page.getByRole("menuitem", { name: "Delete" })).toBeDisabled();
});

test("team managers delete an assignment with the active team header", async ({
  page,
}) => {
  await installTeamRepositories(page);
  const deletes: Array<string | undefined> = [];
  await page.route("**/api/assignments?id=*", async (route) => {
    expect(route.request().method()).toBe("DELETE");
    deletes.push(route.request().headers()["x-mogplex-team-id"]);
    await fulfillJson(route, { ok: true });
  });
  await page.goto("/acme/assignments");
  await expect(page.getByText("team-project → Reviewer")).toBeVisible();
  await page.getByRole("button", { name: "Assignment actions" }).click();
  const deleted = page.waitForResponse(
    (response) =>
      response.url().includes("/api/assignments?id=") &&
      response.request().method() === "DELETE"
  );
  await page.getByRole("menuitem", { name: "Delete" }).click();
  await deleted;
  expect(deletes).toEqual([team.id]);
});