Ctrl K
ComunidadGuías…

tests/e2e/control-changed-files.spec.ts

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

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

const NOW = new Date().toISOString();

function runningSandbox() {
  return {
    id: "rec-live",
    user_id: "00000000-0000-4000-8000-000000000001",
    repo_id: "repo-control-default",
    sandbox_id: "sbx_live123",
    base_branch: "main",
    working_branch: "mogplex/agent-live",
    limit_claim_id: null,
    status: "running",
    preview_url: null,
    snapshot_id: null,
    error: null,
    stop_reason: null,
    install_log: null,
    dev_log: null,
    runtime: null,
    terminal_cwd: null,
    root_directory: null,
    created_at: NOW,
    last_active_at: NOW,
    runtime_summary: {
      sandbox_id: "sbx_live123",
      status: "running",
      health_status: "unknown",
      preview_url: null,
      last_health_check_at: null,
      last_preview_http_status: null,
      boot_attempts: 0,
      last_boot_started_at: null,
      last_boot_completed_at: null,
    },
    billing_summary: {
      source: "platform",
      label: "Platform",
      project_id: null,
      team_id: null,
      team_label: null,
    },
    error_summary: {
      current_error: null,
      last_preview_error: null,
      last_boot_error: null,
      display_error: null,
      has_errors: false,
    },
  };
}

test("control shows the sandbox working tree with diff, revert, and commit", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);
  await mockBaseChrome(page);
  await mockControlSessionBootstrap(page);
  await page.route("**/api/connections", (route) =>
    fulfillJson(route, { connections: [] })
  );
  await page.route("**/api/sandbox", (route) =>
    fulfillJson(route, { sandboxes: [runningSandbox()] })
  );

  let changes = {
    branch: "mogplex/agent-live",
    baseBranch: "main",
    ahead: 0,
    behind: 0,
    files: [
      { path: "src/app.ts", status: "modified", additions: 3, deletions: 1 },
      { path: "docs/new.md", status: "untracked", additions: 7, deletions: 0 },
    ],
  };
  const posts: Array<Record<string, unknown>> = [];
  await page.route(
    /\/api\/sandbox\/rec-live\/changes(?:\?.*)?$/,
    async (route) => {
      const request = route.request();
      const url = new URL(request.url());
      if (request.method() === "GET" && url.searchParams.has("path")) {
        await route.fulfill({
          json: {
            path: url.searchParams.get("path"),
            diff: "--- a/src/app.ts\n+++ b/src/app.ts\n@@ -1,2 +1,2 @@\n-const old = 1;\n+const fresh = 2;\n unchanged\n",
          },
        });
        return;
      }
      if (request.method() === "GET") {
        await route.fulfill({ json: changes });
        return;
      }
      const body = request.postDataJSON() as Record<string, unknown>;
      posts.push(body);
      if (body.action === "revert") {
        const reverted = new Set(body.paths as string[]);
        changes = {
          ...changes,
          files: changes.files.filter((file) => !reverted.has(file.path)),
        };
        await route.fulfill({ json: { reverted: [...reverted], changes } });
        return;
      }
      changes = { ...changes, files: [], ahead: 1 };
      await route.fulfill({
        json: {
          committed: true,
          sha: "abc123",
          pushed: true,
          pullRequestUrl: "https://github.com/acme/widgets/pull/7",
          changes,
        },
      });
    }
  );

  const streamBody =
    [
      { type: "start" },
      { type: "text-start", id: "t1" },
      { type: "text-delta", id: "t1", delta: "Done." },
      { type: "text-end", id: "t1" },
      { type: "finish" },
    ]
      .map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`)
      .join("") + "data: [DONE]\n\n";
  await page.route("**/api/control/chat", (route) =>
    route.fulfill({
      status: 200,
      headers: {
        "content-type": "text/event-stream",
        "x-vercel-ai-ui-message-stream": "v1",
      },
      body: streamBody,
    })
  );

  await page.goto(scopedPath("control"));
  await page.waitForLoadState("networkidle");
  await page
    .getByPlaceholder("Ask anything or run a command...")
    .fill("Rename the constant");
  await page.getByRole("button", { name: "Start mission" }).click();

  await page.getByRole("button", { name: "Open changes", exact: true }).click();
  const bar = page.getByTestId("changed-files-bar");
  await expect(bar).toContainText("2 files changed");
  await expect(bar).toContainText("mogplex/agent-live");

  await page
    .getByTestId("changed-file-src/app.ts")
    .getByText("src/app.ts")
    .click();
  await expect(
    page.getByRole("complementary", { name: "Task tools" })
  ).toContainText("const fresh = 2;");
  await page.getByRole("button", { name: "Close diff" }).click();

  await page
    .getByTestId("changed-file-docs/new.md")
    .getByTestId("changed-file-revert")
    .click();
  await page
    .getByTestId("changed-file-docs/new.md")
    .getByTestId("changed-file-revert-confirm")
    .click();
  await expect(bar).toContainText("1 file changed");
  expect(posts[0]).toEqual({ action: "revert", paths: ["docs/new.md"] });

  await page.getByTestId("changed-files-commit").click();
  await expect(page.getByTestId("changed-files-commit-message")).toHaveValue(
    "Rename the constant"
  );
  await page.getByTestId("changed-files-commit-pr").click();
  await expect(
    page.getByRole("link", { name: "Open pull request" })
  ).toHaveAttribute("href", "https://github.com/acme/widgets/pull/7");
  expect(posts[1]).toEqual({
    action: "commit",
    message: "Rename the constant",
    push: true,
    openPullRequest: true,
  });
});