9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
335 lines
11 KiB
TypeScript
335 lines
11 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.addInitScript(() => {
|
|
localStorage.setItem("buzz-admin-locale", "en");
|
|
});
|
|
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("defaults to Simplified Chinese and can switch to English", async ({
|
|
page,
|
|
}) => {
|
|
await page.addInitScript(() => localStorage.clear());
|
|
await page.goto("/reports");
|
|
await expect(page.getByRole("heading", { name: "待处理举报" })).toBeVisible();
|
|
await page.getByRole("button", { name: "English" }).click();
|
|
await expect(
|
|
page.getByRole("heading", { name: "Open reports" }),
|
|
).toBeVisible();
|
|
await expect(page.locator("html")).toHaveAttribute("lang", "en");
|
|
});
|
|
|
|
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 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\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",
|
|
`/api/admin/v1/feedback/${id}/attachments/${"a".repeat(64)}`,
|
|
);
|
|
await expect(
|
|
page.getByRole("link", { name: /diagnostics.txt/ }),
|
|
).toHaveAttribute(
|
|
"href",
|
|
`/api/admin/v1/feedback/${id}/attachments/${"b".repeat(64)}`,
|
|
);
|
|
const fileHeight = await page
|
|
.locator(".file-attachment")
|
|
.evaluate((element) => element.getBoundingClientRect().height);
|
|
expect(fileHeight).toBeLessThan(100);
|
|
});
|