Ctrl K
ComunidadGuías…

tests/e2e/agent-roster-models.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 { linkedVercelCapability } from "./helpers/activation-fixtures";
import type { Route } from "@playwright/test";
import type { Agent } from "../../lib/types";

const enabledModel = {
  id: "minimax/minimax-m2.7",
  provider: "minimax",
  name: "MiniMax M2.7",
  context_length: 1_000_000,
  capabilities: ["tool-use"],
  is_available: true,
};

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,
  vercel: linkedVercelCapability,
};

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

async function fulfillText(route: Route, body: string, status = 200) {
  await route.fulfill({
    status,
    contentType: "text/plain",
    body,
  });
}

test("new agents surface API validation errors instead of failing silently", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);

  await page.route("**/api/auth/user", (route) =>
    fulfillJson(route, { user: connectedUser })
  );
  await page.route("**/api/settings", (route) =>
    fulfillJson(route, { default_model: enabledModel.id, theme: "dark" })
  );
  await page.route("**/api/agents", async (route) => {
    if (route.request().method() === "POST") {
      await fulfillJson(route, { error: "Server rejected payload" }, 400);
      return;
    }

    await fulfillJson(route, []);
  });
  await page.route("**/api/models", (route) =>
    fulfillJson(route, {
      models: [enabledModel],
      catalog: [{ ...enabledModel, is_enabled: true }],
    })
  );

  await page.goto(scopedPath("agents/roster"));
  await page.getByRole("button", { name: "New Agent" }).click();
  await page.locator("#agent-name-input").fill("PR Reviewer");
  const createButton = page.getByRole("button", { name: "Create Agent" });
  await expect(createButton).toBeDisabled();
  await page.getByLabel("Category").selectOption("code-review");
  await expect(createButton).toBeEnabled();
  await createButton.scrollIntoViewIfNeeded();
  await createButton.click();

  await expect(page.getByText("Server rejected payload")).toBeVisible();
});

test("roster uses standard categories and badges user-owned agent origin", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);

  const agents: Agent[] = [
    {
      id: "agent-custom",
      user_id: "user-1",
      name: "CUSTOM-REVIEWER",
      slug: "custom-reviewer",
      model: enabledModel.id,
      system_prompt: "Review code carefully.",
      description: "Reviews pull requests.",
      category: "code-review",
      source_template: null,
      is_preset: false,
      created_at: "2026-04-12T00:00:00.000Z",
    },
    {
      id: "agent-customized",
      user_id: "user-1",
      name: "CUSTOMIZED-REVIEWER",
      slug: "customized-reviewer",
      model: enabledModel.id,
      system_prompt: "Review code carefully.",
      description: "Reviews pull requests with template context.",
      category: "code-review",
      source_template: "NEXTJS-REVIEWER",
      is_preset: false,
      created_at: "2026-04-12T00:00:00.000Z",
    },
    {
      id: "agent-uncategorized",
      user_id: "user-1",
      name: "UNCATEGORIZED-REVIEWER",
      slug: "uncategorized-reviewer",
      model: enabledModel.id,
      system_prompt: "Review code carefully.",
      description: "Needs a category before reuse.",
      category: null,
      source_template: null,
      is_preset: false,
      created_at: "2026-04-12T00:00:00.000Z",
    },
  ];

  await page.route("**/api/auth/user", (route) =>
    fulfillJson(route, { user: connectedUser })
  );
  await page.route("**/api/settings", (route) =>
    fulfillJson(route, { default_model: enabledModel.id, theme: "dark" })
  );
  await page.route("**/api/agents", (route) => fulfillJson(route, agents));
  await page.route("**/api/models", (route) =>
    fulfillJson(route, {
      models: [enabledModel],
      catalog: [{ ...enabledModel, is_enabled: true }],
    })
  );

  await page.goto(scopedPath("agents/roster"));

  await expect(page.getByRole("tab", { name: /^Custom \(/ })).toHaveCount(0);
  await expect(page.getByRole("heading", { name: "PR Review" })).toBeVisible();
  await expect(
    page.getByRole("tab", { name: /^Needs Category \(1\)$/ })
  ).toBeVisible();
  await expect(
    page.getByRole("heading", { name: "Needs Category" })
  ).toBeVisible();
  await expect(page.getByText("Custom", { exact: true }).first()).toBeVisible();
  await expect(page.getByText("Customized", { exact: true })).toBeVisible();
  await expect(page.getByText("Needs category", { exact: true })).toBeVisible();

  await page.getByRole("tab", { name: /^PR Review \(2\)$/ }).click();
  await expect(page.getByText("CUSTOM-REVIEWER")).toBeVisible();
  await expect(page.getByText("CUSTOMIZED-REVIEWER")).toBeVisible();
  await expect(page.getByText("UNCATEGORIZED-REVIEWER")).not.toBeVisible();

  await page.getByRole("tab", { name: /^Needs Category \(1\)$/ }).click();
  await expect(page.getByText("UNCATEGORIZED-REVIEWER")).toBeVisible();
  await expect(page.getByText("CUSTOM-REVIEWER")).not.toBeVisible();

  await page.getByRole("tab", { name: /^All \(3\)$/ }).click();
  await page.getByLabel("Agent actions").last().click();
  await page.getByRole("menuitem", { name: "Edit" }).click();

  const saveButton = page.getByRole("button", { name: "Save" });
  await expect(page.getByLabel("Category", { exact: true })).toHaveValue("");
  await expect(saveButton).toBeDisabled();

  await page
    .getByLabel("Category", { exact: true })
    .selectOption("code-review");
  await expect(saveButton).toBeEnabled();
});

test("preset customization surfaces API failures", async ({ page }) => {
  await enableScopedE2EAuth(page);

  const agents: Agent[] = [
    {
      id: "preset:legacy-reviewer",
      user_id: "user-1",
      name: "LEGACY-REVIEWER",
      slug: "legacy-reviewer",
      model: enabledModel.id,
      system_prompt: "Review carefully.",
      description: "Preset reviewer.",
      category: "code-review",
      source_template: "LEGACY-REVIEWER",
      is_preset: true,
      has_fork: false,
      created_at: "1970-01-01T00:00:00.000Z",
    },
  ];

  await page.route("**/api/auth/user", (route) =>
    fulfillJson(route, { user: connectedUser })
  );
  await page.route("**/api/settings", (route) =>
    fulfillJson(route, { default_model: enabledModel.id, theme: "dark" })
  );
  await page.route("**/api/agents", async (route) => {
    if (route.request().method() === "POST") {
      await fulfillJson(route, { error: "Category is required" }, 400);
      return;
    }

    await fulfillJson(route, agents);
  });
  await page.route("**/api/models", (route) =>
    fulfillJson(route, {
      models: [enabledModel],
      catalog: [{ ...enabledModel, is_enabled: true }],
    })
  );

  await page.goto(scopedPath("agents/roster"));
  await page.getByRole("tab", { name: /^PR Review \(1\)$/ }).click();
  await page.getByRole("button", { name: "Customize" }).click();

  await expect(page.getByText("Category is required")).toBeVisible();
});

test("new agents can draft the panel fields with AI assistance", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);

  const existingAgentPayloads: Array<unknown> = [];
  let draftCallCount = 0;

  await page.route("**/api/auth/user", (route) =>
    fulfillJson(route, { user: connectedUser })
  );
  await page.route("**/api/settings", (route) =>
    fulfillJson(route, { default_model: enabledModel.id, theme: "dark" })
  );
  await page.route("**/api/agents", (route) => fulfillJson(route, []));
  await page.route("**/api/models", (route) =>
    fulfillJson(route, {
      models: [enabledModel],
      catalog: [{ ...enabledModel, is_enabled: true }],
    })
  );
  await page.route("**/api/agents/generate", async (route) => {
    draftCallCount += 1;
    const payload = route.request().postDataJSON() as {
      existingAgent?: unknown;
    };
    existingAgentPayloads.push(payload.existingAgent ?? null);

    await fulfillText(
      route,
      JSON.stringify(
        draftCallCount === 1
          ? {
              name: "PR-REVIEWER",
              description: "Reviews pull requests for release risk",
              category: "code-review",
              system_prompt:
                "You are a pull request reviewer specialized in release risk.\n\n## REVIEW FOCUS\n- Risk\n\n## OUTPUT FORMAT\n- Findings",
            }
          : {
              name: "PR-REVIEWER-V2",
              description: "Reviews pull requests for deployment safety",
              category: "code-review",
              system_prompt:
                "You are a pull request reviewer specialized in deployment safety.\n\n## REVIEW FOCUS\n- Safety\n\n## OUTPUT FORMAT\n- Findings",
            }
      )
    );
  });

  await page.goto(scopedPath("agents/roster"));
  await page.getByRole("button", { name: "New Agent" }).click();
  await page
    .getByLabel("AI request")
    .fill(
      "Create a PR review agent that flags risky migrations and weak test coverage."
    );
  await page.getByRole("button", { name: "Draft with AI" }).click();

  await expect(page.locator("#agent-name-input")).toHaveValue("PR-REVIEWER");
  await expect(page.locator("#agent-description-input")).toHaveValue(
    "Reviews pull requests for release risk"
  );
  await expect(page.getByLabel("Category")).toHaveValue("code-review");
  await expect(page.locator("#agent-system-prompt-input")).toContainText(
    "pull request reviewer specialized in release risk"
  );

  await page
    .getByLabel("AI request")
    .fill("Actually make this focus on deployment safety instead.");
  await page.getByRole("button", { name: "Draft with AI" }).click();

  await expect(page.locator("#agent-name-input")).toHaveValue("PR-REVIEWER-V2");
  await expect(page.locator("#agent-description-input")).toHaveValue(
    "Reviews pull requests for deployment safety"
  );
  await expect(page.locator("#agent-system-prompt-input")).toContainText(
    "pull request reviewer specialized in deployment safety"
  );
  await expect.poll(() => existingAgentPayloads).toEqual([null, null]);
});

test("AI assistance surfaces invalid draft output without breaking the dialog", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);

  await page.route("**/api/auth/user", (route) =>
    fulfillJson(route, { user: connectedUser })
  );
  await page.route("**/api/settings", (route) =>
    fulfillJson(route, { default_model: enabledModel.id, theme: "dark" })
  );
  await page.route("**/api/agents", (route) => fulfillJson(route, []));
  await page.route("**/api/models", (route) =>
    fulfillJson(route, {
      models: [enabledModel],
      catalog: [{ ...enabledModel, is_enabled: true }],
    })
  );
  await page.route("**/api/agents/generate", async (route) => {
    await fulfillText(route, '{"name":"BROKEN"');
  });

  await page.goto(scopedPath("agents/roster"));
  await page.getByRole("button", { name: "New Agent" }).click();
  await page.getByLabel("AI request").fill("Create a PR review agent.");
  await page.getByRole("button", { name: "Draft with AI" }).click();

  await expect(
    page.getByText(
      "The model returned an invalid draft. Try again with a more specific request."
    )
  ).toBeVisible();
  await expect(page.locator("#agent-name-input")).toHaveValue("");
});

test("editing agents can customize the draft with AI context", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);

  let receivedExistingAgent: {
    name?: string;
    description?: string;
    category?: string;
    system_prompt?: string;
  } | null = null;
  const agents: Agent[] = [
    {
      id: "agent-1",
      user_id: "user-1",
      name: "REVIEWER-BOT",
      slug: "reviewer-bot",
      model: enabledModel.id,
      system_prompt: "Review code carefully.",
      description: "Reviews pull requests.",
      category: "code-review",
      created_at: "2026-04-12T00:00:00.000Z",
    },
  ];

  await page.route("**/api/auth/user", (route) =>
    fulfillJson(route, { user: connectedUser })
  );
  await page.route("**/api/settings", (route) =>
    fulfillJson(route, { default_model: enabledModel.id, theme: "dark" })
  );
  await page.route("**/api/agents", (route) => fulfillJson(route, agents));
  await page.route("**/api/models", (route) =>
    fulfillJson(route, {
      models: [enabledModel],
      catalog: [{ ...enabledModel, is_enabled: true }],
    })
  );
  await page.route("**/api/agents/generate", async (route) => {
    const payload = route.request().postDataJSON() as {
      existingAgent?: {
        name?: string;
        description?: string;
        category?: string;
        system_prompt?: string;
      };
    };
    receivedExistingAgent = payload.existingAgent ?? null;

    await fulfillText(
      route,
      JSON.stringify({
        name: "REVIEWER-BOT",
        description: "Reviews pull requests for rollout and caching risk",
        category: "code-review",
        system_prompt:
          "You are a pull request reviewer specialized in rollout and caching risk.\n\n## REVIEW FOCUS\n- Rollout safety\n- Caching\n\n## OUTPUT FORMAT\n- Findings",
      })
    );
  });

  await page.goto(scopedPath("agents/roster"));
  await page.getByLabel("Agent actions").first().click();
  await page.getByRole("menuitem", { name: "Edit" }).click();
  await page
    .getByLabel("AI request")
    .fill(
      "Make this stricter on rollout safety and Next.js caching regressions."
    );
  await page.getByRole("button", { name: "Update with AI" }).click();

  await expect
    .poll(() => receivedExistingAgent)
    .toEqual({
      name: "REVIEWER-BOT",
      description: "Reviews pull requests.",
      category: "code-review",
    });
  await expect(page.locator("#agent-description-input")).toHaveValue(
    "Reviews pull requests for rollout and caching risk"
  );
  await expect(page.locator("#agent-system-prompt-input")).toContainText(
    "rollout and caching risk"
  );
});