Ctrl K
ComunidadGuías…

admin-web/tests/routes.spec.ts

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

Archivo original ↗Preguntar a KiBot
import { expect, test } from "@playwright/test";

test.beforeEach(async ({ page }) => {
  // Every admin API call returns 200, so the probe resolves to disabled mode
  // and the dashboard renders without a credential.
  await page.route("**/api/admin/v1/**", async (route) => {
    await route.fulfill({ contentType: "application/json", body: "[]" });
  });
});

for (const [path, heading] of [
  ["/reports", "Open reports"],
  ["/feedback", "Feedback"],
]) {
  test(`${path} supports a deep link and empty state`, async ({ page }) => {
    await page.goto(path);
    await expect(page.getByRole("heading", { name: heading })).toBeVisible();
    await expect(page.getByText("No records.")).toBeVisible();
  });
}

test("forbidden reads have an explicit state", async ({ page }) => {
  await page.route("**/api/admin/v1/reports?**", (route) =>
    route.fulfill({
      status: 403,
      contentType: "application/json",
      body: JSON.stringify({
        error: { code: "forbidden", message: "request is not authorized" },
      }),
    }),
  );
  await page.goto("/reports");
  await expect(
    page.getByRole("heading", { name: "Access denied" }),
  ).toBeVisible();
});

test("report rows render the relay response contract", async ({ page }) => {
  await page.route("**/api/admin/v1/reports?**", (route) =>
    route.fulfill({
      contentType: "application/json",
      body: JSON.stringify([
        {
          id: "0e6caad8-1e18-4cd7-84fa-7264103f0a08",
          communityId: "6d474feb-c50a-44e4-a0b5-f30532df49bc",
          communityHost: "design.buzz.xyz",
          reporterPubkey: "21".repeat(32),
          targetKind: "event",
          target: "12".repeat(32),
          reportType: "spam",
          status: "open",
          createdAt: "2026-07-17T17:30:00Z",
        },
      ]),
    }),
  );
  await page.goto("/reports");
  await expect(page.getByText("design.buzz.xyz")).toBeVisible();
  await expect(page.getByText("spam")).toBeVisible();
  await expect(page.getByText("Unknown date")).toHaveCount(0);
});

test("event report detail renders the reported message content", async ({
  page,
}) => {
  const id = "0e6caad8-1e18-4cd7-84fa-7264103f0a08";
  await page.route(`**/api/admin/v1/reports/${id}`, (route) =>
    route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        id,
        communityId: "6d474feb-c50a-44e4-a0b5-f30532df49bc",
        communityHost: "design.buzz.xyz",
        reporterPubkey: "21".repeat(32),
        targetKind: "event",
        target: "12".repeat(32),
        reportType: "spam",
        status: "open",
        createdAt: "2026-07-17T17:30:00Z",
        message: {
          authorPubkey: "31".repeat(32),
          content:
            "This is the complete reported message.\nIt preserves lines.",
          createdAt: "2026-07-17T17:25:00Z",
          deletedAt: null,
        },
      }),
    }),
  );

  await page.goto(`/reports/${id}`);
  await expect(
    page.getByText("This is the complete reported message.", { exact: false }),
  ).toBeVisible();
  await expect(page.getByText("31".repeat(32))).toBeVisible();
  await expect(
    page.getByText("Message content is unavailable", { exact: false }),
  ).toHaveCount(0);
});

test("event report detail explains when message content is unavailable", async ({
  page,
}) => {
  const id = "0e6caad8-1e18-4cd7-84fa-7264103f0a09";
  await page.route(`**/api/admin/v1/reports/${id}`, (route) =>
    route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        id,
        communityId: "6d474feb-c50a-44e4-a0b5-f30532df49bc",
        communityHost: "design.buzz.xyz",
        reporterPubkey: "21".repeat(32),
        targetKind: "event",
        target: "12".repeat(32),
        reportType: "spam",
        status: "open",
        createdAt: "2026-07-17T17:30:00Z",
        message: null,
      }),
    }),
  );

  await page.goto(`/reports/${id}`);
  await expect(
    page.getByText("Message content is unavailable", { exact: false }),
  ).toBeVisible();
});

test("feedback cards open the complete submission", async ({ page }) => {
  const id = "feed0000-0000-4000-8000-000000000001";
  const fullBody = `${"Long feedback ".repeat(30)}end of feedback`;
  await page.route(`**/api/admin/v1/feedback/${id}`, (route) =>
    route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        id,
        communityId: "6d474feb-c50a-44e4-a0b5-f30532df49bc",
        communityHost: "design.buzz.xyz",
        eventId: "31".repeat(32),
        submitterPubkey: "21".repeat(32),
        category: "needs-work",
        body: fullBody,
        tags: [],
        eventCreatedAt: "2026-07-17T17:25:00Z",
        receivedAt: "2026-07-17T17:30:00Z",
      }),
    }),
  );
  await page.route("**/api/admin/v1/feedback", (route) =>
    route.fulfill({
      contentType: "application/json",
      body: JSON.stringify([
        {
          id,
          communityId: "6d474feb-c50a-44e4-a0b5-f30532df49bc",
          communityHost: "design.buzz.xyz",
          submitterPubkey: "21".repeat(32),
          category: "needs-work",
          bodySummary: `${fullBody.slice(0, 240)}…`,
          receivedAt: "2026-07-17T17:30:00Z",
        },
      ]),
    }),
  );

  await page.goto("/feedback");
  const card = page.locator(".feedback-record");
  await expect(card.locator(".record-provenance")).toContainText(
    "design.buzz.xyz",
  );
  await card.locator(".feedback-main-link").click();
  await expect(page).toHaveURL(`/feedback/${id}`);
  await expect(
    page.getByRole("heading", { name: "Feedback detail" }),
  ).toBeVisible();
  await expect(
    page.getByText("end of feedback", { exact: false }),
  ).toBeVisible();
});

test("feedback can be searched and filtered by community and time", async ({
  page,
}) => {
  const recent = new Date(Date.now() - 60 * 60 * 1000).toISOString();
  const old = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString();
  await page.route("**/api/admin/v1/feedback", (route) =>
    route.fulfill({
      contentType: "application/json",
      body: JSON.stringify([
        {
          id: "recent",
          communityId: "one",
          communityHost: "design.buzz.xyz",
          submitterPubkey: "21".repeat(32),
          category: "bug",
          bodySummary: "Composer freezes after sleep",
          receivedAt: recent,
        },
        {
          id: "old",
          communityId: "two",
          communityHost: "engineering.buzz.xyz",
          submitterPubkey: "22".repeat(32),
          category: "praise",
          bodySummary: "Calls are much more reliable",
          receivedAt: old,
        },
      ]),
    }),
  );

  await page.goto("/feedback");
  await expect(page.getByText("2 of 2 submissions")).toBeVisible();
  await page.getByRole("searchbox", { name: "Search feedback" }).fill("calls");
  await expect(page.getByText("Calls are much more reliable")).toBeVisible();
  await expect(page.getByText("Composer freezes after sleep")).toHaveCount(0);

  await page.getByRole("searchbox", { name: "Search feedback" }).fill("");
  await page.getByLabel("Community").selectOption("design.buzz.xyz");
  await expect(page.getByText("Composer freezes after sleep")).toBeVisible();
  await expect(page.getByText("Calls are much more reliable")).toHaveCount(0);

  await page.getByLabel("Community").selectOption("all");
  await page.getByLabel("Received").selectOption("day");
  await expect(page.getByText("Composer freezes after sleep")).toBeVisible();
  await expect(page.getByText("Calls are much more reliable")).toHaveCount(0);
});

test("feedback filters keep long community names usable", async ({ page }) => {
  await page.route("**/api/admin/v1/feedback", (route) =>
    route.fulfill({
      contentType: "application/json",
      body: JSON.stringify([
        {
          id: "long-community",
          communityId: "long-community",
          communityHost: `${"long-community-name.".repeat(4)}buzz.example.com`,
          submitterPubkey: "21".repeat(32),
          category: "bug",
          bodySummary: "The filter row stays within its container",
          receivedAt: new Date().toISOString(),
        },
      ]),
    }),
  );

  for (const viewport of [
    { name: "desktop", width: 1200 },
    { name: "mobile", width: 720 },
  ]) {
    await test.step(viewport.name, async () => {
      await page.setViewportSize({ width: viewport.width, height: 720 });
      await page.goto("/feedback");

      const filters = page.locator(".feedback-filters");
      const search = page.getByRole("searchbox", { name: "Search feedback" });
      const community = page.getByRole("combobox", { name: "Community" });
      const status = page.getByLabel("Status");
      await expect(filters).toBeVisible();
      await expect(community).toBeVisible();
      await expect(status).toBeVisible();

      const [filtersBox, searchBox, communityBox, statusBox] =
        await Promise.all([
          filters.boundingBox(),
          search.boundingBox(),
          community.boundingBox(),
          status.boundingBox(),
        ]);
      if (!filtersBox || !searchBox || !communityBox || !statusBox) {
        throw new Error("feedback filter bounds were unavailable");
      }
      expect(statusBox.x + statusBox.width).toBeLessThanOrEqual(
        filtersBox.x + filtersBox.width,
      );

      if (viewport.name === "desktop") {
        const rootFontSize = await page.evaluate(() =>
          Number.parseFloat(
            getComputedStyle(document.documentElement).fontSize,
          ),
        );
        expect(communityBox.width).toBeGreaterThanOrEqual(14 * rootFontSize);
      } else {
        expect(Math.abs(communityBox.width - searchBox.width)).toBeLessThan(1);
      }

      const pageWidths = await page.evaluate(() => ({
        client: document.documentElement.clientWidth,
        scroll: document.documentElement.scrollWidth,
      }));
      expect(pageWidths.scroll).toBe(pageWidths.client);
    });
  }
});

test("feedback status is stored locally by feedback id", async ({ page }) => {
  await page.route("**/api/admin/v1/feedback", (route) =>
    route.fulfill({
      contentType: "application/json",
      body: JSON.stringify([
        {
          id: "feedback-one",
          communityId: "one",
          communityHost: "design.buzz.xyz",
          submitterPubkey: "21".repeat(32),
          category: "bug",
          bodySummary: "Composer freezes after sleep",
          receivedAt: new Date().toISOString(),
        },
      ]),
    }),
  );

  await page.goto("/feedback");
  await page.getByRole("checkbox", { name: "Acted on" }).check();
  await page.reload();
  await expect(page.getByRole("checkbox", { name: "Acted on" })).toBeChecked();
  await page.getByLabel("Status").selectOption("acted-on");
  await expect(page.getByText("Composer freezes after sleep")).toBeVisible();
  await page.getByLabel("Status").selectOption("pending");
  await expect(page.getByText("No matching feedback.")).toBeVisible();
});

test("feedback attachments render from imeta without raw markdown", async ({
  page,
}) => {
  const id = "feedback-with-attachments";
  const imageUrl = `https://design.buzz.xyz/media/${"a".repeat(64)}.png`;
  const fileUrl = `https://design.buzz.xyz/media/${"b".repeat(64)}.txt`;
  await page.route(`**/api/admin/v1/feedback/${id}`, (route) =>
    route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        id,
        communityId: "one",
        communityHost: "design.buzz.xyz",
        eventId: "31".repeat(32),
        submitterPubkey: "21".repeat(32),
        category: "bug",
        body: `Composer froze.\n![image](${imageUrl})\n[diagnostics.txt](${fileUrl})`,
        tags: [
          [
            "imeta",
            `url ${imageUrl}`,
            "m image/png",
            `x ${"a".repeat(64)}`,
            "size 48213",
            "dim 1280x720",
            "filename screenshot.png",
          ],
          [
            "imeta",
            `url ${fileUrl}`,
            "m text/plain",
            `x ${"b".repeat(64)}`,
            "size 391",
            "filename diagnostics.txt",
          ],
        ],
        eventCreatedAt: "2026-07-17T17:25:00Z",
        receivedAt: "2026-07-17T17:30:00Z",
      }),
    }),
  );

  await page.goto(`/feedback/${id}`);
  await expect(
    page.getByText("Composer froze.", { exact: true }),
  ).toBeVisible();
  await expect(page.getByText("![image]", { exact: false })).toHaveCount(0);
  await expect(
    page.getByRole("img", { name: "screenshot.png" }),
  ).toHaveAttribute("src", /^blob:/);
  await expect(
    page.getByRole("link", { name: /diagnostics.txt/ }),
  ).toHaveAttribute("href", /^blob:/);
  const fileHeight = await page
    .locator(".file-attachment")
    .evaluate((element) => element.getBoundingClientRect().height);
  expect(fileHeight).toBeLessThan(100);
});