Ctrl K
ComunidadGuías…

tests/e2e/flows-api-crud.spec.ts

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

Archivo original ↗Preguntar a KiBot
import { expect, test } from "@playwright/test";
import {
  user1Headers,
  user2Headers,
  seedState,
  resetState,
} from "./helpers/flows-api-fixtures";

test.beforeEach(async ({ request }) => {
  await resetState(request);
});

test("GET /api/flows/:id enforces ownership", async ({ request }) => {
  await seedState(request, {
    flows: [
      {
        id: "flow-1",
        user_id: "user-1",
        installation_id: 101,
        name: "Owned Flow",
        description: null,
        notes: null,
        source_kind: "github",
        status: "inactive",
        draft_graph: {
          nodes: [],
          edges: [],
          viewport: { x: 0, y: 0, zoom: 1 },
        },
        published_version_id: null,
        created_at: "2026-03-28T10:00:00.000Z",
        updated_at: "2026-03-28T10:00:00.000Z",
      },
    ],
  });

  const okResponse = await request.get("/api/flows/flow-1", {
    headers: user1Headers,
  });
  expect(okResponse.ok()).toBeTruthy();

  const missingResponse = await request.get("/api/flows/flow-1", {
    headers: user2Headers,
  });
  expect(missingResponse.status()).toBe(404);
});

test("PUT /api/flows/:id updates metadata and rejects foreign agent bindings", async ({
  request,
}) => {
  await seedState(request, {
    agents: [
      {
        id: "agent-owned",
        user_id: "user-1",
        name: "Owned Agent",
        slug: "owned",
        model: "minimax/minimax-m2.5",
        system_prompt: null,
      },
      {
        id: "agent-foreign",
        user_id: "user-2",
        name: "Foreign Agent",
        slug: "foreign",
        model: "minimax/minimax-m2.5",
        system_prompt: null,
      },
    ],
    flows: [
      {
        id: "flow-1",
        user_id: "user-1",
        installation_id: 101,
        name: "Flow",
        description: null,
        notes: null,
        source_kind: "github",
        status: "inactive",
        draft_graph: {
          nodes: [
            {
              id: "start",
              type: "start",
              position: { x: 0, y: 0 },
              data: { label: "@mention", event: "mention", isDefault: true },
            },
            {
              id: "agent-1",
              type: "agent",
              position: { x: 100, y: 0 },
              data: { label: "Owned Agent", agentId: "agent-owned" },
            },
            {
              id: "end",
              type: "end",
              position: { x: 200, y: 0 },
              data: { label: "Done" },
            },
          ],
          edges: [
            { id: "e1", source: "start", target: "agent-1" },
            { id: "e2", source: "agent-1", target: "end" },
          ],
          viewport: { x: 0, y: 0, zoom: 1 },
        },
        published_version_id: null,
        created_at: "2026-03-28T10:00:00.000Z",
        updated_at: "2026-03-28T10:00:00.000Z",
      },
    ],
  });

  const okResponse = await request.put("/api/flows/flow-1", {
    headers: user1Headers,
    data: {
      name: "Updated Flow",
      description: "Updated description",
      notes: "Updated notes",
      draft_graph: {
        nodes: [
          {
            id: "start",
            type: "start",
            position: { x: 0, y: 0 },
            data: { label: "@mention", event: "mention", isDefault: true },
          },
          {
            id: "agent-1",
            type: "agent",
            position: { x: 100, y: 0 },
            data: { label: "Owned Agent", agentId: "agent-owned" },
          },
          {
            id: "end",
            type: "end",
            position: { x: 200, y: 0 },
            data: { label: "Done" },
          },
        ],
        edges: [
          { id: "e1", source: "start", target: "agent-1" },
          { id: "e2", source: "agent-1", target: "end" },
        ],
        viewport: { x: 5, y: 5, zoom: 1.1 },
      },
    },
  });
  expect(okResponse.ok()).toBeTruthy();
  const updated = await okResponse.json();
  expect(updated.name).toBe("Updated Flow");
  expect(updated.description).toBe("Updated description");
  expect(updated.notes).toBe("Updated notes");

  const badResponse = await request.put("/api/flows/flow-1", {
    headers: user1Headers,
    data: {
      draft_graph: {
        nodes: [
          {
            id: "start",
            type: "start",
            position: { x: 0, y: 0 },
            data: { label: "@mention", event: "mention", isDefault: true },
          },
          {
            id: "agent-1",
            type: "agent",
            position: { x: 100, y: 0 },
            data: { label: "Foreign Agent", agentId: "agent-foreign" },
          },
          {
            id: "end",
            type: "end",
            position: { x: 200, y: 0 },
            data: { label: "Done" },
          },
        ],
        edges: [
          { id: "e1", source: "start", target: "agent-1" },
          { id: "e2", source: "agent-1", target: "end" },
        ],
        viewport: { x: 0, y: 0, zoom: 1 },
      },
    },
  });
  expect(badResponse.status()).toBe(400);
  expect(await badResponse.json()).toMatchObject({
    code: "FLOW_AGENT_FORBIDDEN",
    error: expect.stringContaining('Agent "agent-foreign"'),
  });
});

test("GET /api/flows/templates pages summaries without returning stored graphs", async ({
  request,
}) => {
  await seedState(request, {
    flowTemplates: Array.from({ length: 26 }, (_, index) => ({
      id: `template-${String(index).padStart(2, "0")}`,
      user_id: "user-1",
      name: `Template ${index + 1}`,
      description: null,
      graph: {
        nodes: [],
        edges: [],
        viewport: { x: 0, y: 0, zoom: 1 },
      },
      source_flow_id: null,
      trigger_event: "pr_opened",
      reconnect: [],
      requires_repository: false,
      created_at: `2026-07-24T10:${String(index).padStart(2, "0")}:00.000Z`,
      updated_at: `2026-07-24T10:${String(index).padStart(2, "0")}:00.000Z`,
    })),
  });

  const firstResponse = await request.get("/api/flows/templates", {
    headers: user1Headers,
  });
  expect(firstResponse.status()).toBe(200);
  const firstPage = await firstResponse.json();
  expect(firstPage.templates).toHaveLength(25);
  expect(firstPage.next_cursor).toBe("25");
  expect(firstPage.templates[0].graph).toBeUndefined();

  const secondResponse = await request.get(
    `/api/flows/templates?cursor=${firstPage.next_cursor}`,
    { headers: user1Headers }
  );
  expect(secondResponse.status()).toBe(200);
  expect(await secondResponse.json()).toMatchObject({
    templates: [{ name: "Template 1" }],
    next_cursor: null,
  });

  const invalidResponse = await request.get("/api/flows/templates?cursor=1.5", {
    headers: user1Headers,
  });
  expect(invalidResponse.status()).toBe(400);
});