mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-01 16:47:47 -05:00
151 lines
4.7 KiB
TypeScript
151 lines
4.7 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
import { NZAP_TEST_ID } from "~/db/seed/constants";
|
|
import { ADMIN_ID } from "~/features/admin/admin-constants";
|
|
import { ADMIN_PAGE, SUSPENDED_PAGE } from "~/utils/urls";
|
|
import {
|
|
expect,
|
|
impersonate,
|
|
navigate,
|
|
seed,
|
|
test,
|
|
waitForPOSTResponse,
|
|
} from "./helpers/playwright";
|
|
|
|
async function banUser(
|
|
page: Page,
|
|
options: { expiresAt?: Date; reason?: string },
|
|
) {
|
|
const banForm = page
|
|
.locator("form")
|
|
.filter({ has: page.locator("h2", { hasText: /^Ban user$/ }) });
|
|
|
|
const comboboxButton = banForm.getByLabel(/^User/);
|
|
await expect(comboboxButton).not.toBeDisabled();
|
|
await comboboxButton.click();
|
|
|
|
const searchInput = page.getByTestId("user-search-input");
|
|
await searchInput.fill("N-ZAP");
|
|
|
|
const option = page.getByTestId("user-search-item").first();
|
|
await option.click();
|
|
|
|
if (options.expiresAt) {
|
|
const expiresAt = options.expiresAt;
|
|
const fillSegment = (segment: string, value: string) =>
|
|
banForm
|
|
.getByRole("spinbutton", {
|
|
name: new RegExp(`^${segment}, Ban expiration date`),
|
|
})
|
|
.fill(value);
|
|
|
|
const hours = expiresAt.getHours();
|
|
await fillSegment("year", String(expiresAt.getFullYear()));
|
|
await fillSegment("month", String(expiresAt.getMonth() + 1));
|
|
await fillSegment("day", String(expiresAt.getDate()));
|
|
await fillSegment("hour", String(hours % 12 || 12));
|
|
await fillSegment(
|
|
"minute",
|
|
String(expiresAt.getMinutes()).padStart(2, "0"),
|
|
);
|
|
await fillSegment("AM/PM", hours >= 12 ? "PM" : "AM");
|
|
}
|
|
if (options.reason) {
|
|
await banForm.getByLabel("Reason").fill(options.reason);
|
|
}
|
|
|
|
await waitForPOSTResponse(page, () =>
|
|
banForm.getByRole("button", { name: "Save" }).click(),
|
|
);
|
|
}
|
|
|
|
async function unbanUser(page: Page) {
|
|
const unbanForm = page
|
|
.locator("form")
|
|
.filter({ has: page.locator("h2", { hasText: /^Unban user$/ }) });
|
|
|
|
const comboboxButton = unbanForm.getByLabel(/^User/);
|
|
await expect(comboboxButton).not.toBeDisabled();
|
|
await comboboxButton.click();
|
|
|
|
const searchInput = page.getByTestId("user-search-input");
|
|
await searchInput.fill("N-ZAP");
|
|
|
|
const option = page.getByTestId("user-search-item").first();
|
|
await option.click();
|
|
|
|
await waitForPOSTResponse(page, () =>
|
|
unbanForm.getByRole("button", { name: "Save" }).click(),
|
|
);
|
|
}
|
|
|
|
test.describe("User banning", () => {
|
|
test("banned user is redirected to suspended page and cannot access site", async ({
|
|
page,
|
|
}) => {
|
|
await seed(page);
|
|
|
|
// 1. As admin, ban NZAP user
|
|
await impersonate(page, ADMIN_ID);
|
|
await navigate({ page, url: ADMIN_PAGE });
|
|
await banUser(page, { reason: "Test ban reason" });
|
|
|
|
// 2. As the banned user, try to access the site
|
|
await impersonate(page, NZAP_TEST_ID);
|
|
await navigate({ page, url: "/" });
|
|
|
|
// Should be redirected to suspended page
|
|
await expect(page).toHaveURL(SUSPENDED_PAGE);
|
|
await expect(page.getByText("Account suspended")).toBeVisible();
|
|
await expect(page.getByText("Reason: Test ban reason")).toBeVisible();
|
|
await expect(page.getByText("no end time set")).toBeVisible();
|
|
|
|
// 3. Verify user cannot navigate to other pages
|
|
await navigate({ page, url: "/builds" });
|
|
await expect(page).toHaveURL(SUSPENDED_PAGE);
|
|
|
|
await navigate({ page, url: "/calendar" });
|
|
await expect(page).toHaveURL(SUSPENDED_PAGE);
|
|
|
|
// 4. As admin, unban the user
|
|
await impersonate(page, ADMIN_ID);
|
|
await navigate({ page, url: ADMIN_PAGE });
|
|
await unbanUser(page);
|
|
|
|
// 5. As the unbanned user, verify they can access the site
|
|
await impersonate(page, NZAP_TEST_ID);
|
|
await navigate({ page, url: "/" });
|
|
|
|
// Should not be redirected to suspended page
|
|
await expect(page).not.toHaveURL(SUSPENDED_PAGE);
|
|
});
|
|
|
|
test("timed ban shows expiration date on suspended page", async ({
|
|
page,
|
|
}) => {
|
|
await seed(page);
|
|
|
|
// 1. As admin, ban NZAP user with a future duration
|
|
await impersonate(page, ADMIN_ID);
|
|
await navigate({ page, url: ADMIN_PAGE });
|
|
|
|
// Set ban to expire tomorrow (well in the future)
|
|
const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
|
tomorrow.setHours(12, 0, 0, 0);
|
|
await banUser(page, { expiresAt: tomorrow, reason: "Temporary ban" });
|
|
|
|
// 2. As the banned user, verify redirected to suspended page
|
|
await impersonate(page, NZAP_TEST_ID);
|
|
await navigate({ page, url: "/" });
|
|
|
|
await expect(page).toHaveURL(SUSPENDED_PAGE);
|
|
await expect(page.getByText("Account suspended")).toBeVisible();
|
|
await expect(page.getByText("Reason: Temporary ban")).toBeVisible();
|
|
// Should show expiration time, not "no end time set"
|
|
await expect(page.getByText("no end time set")).not.toBeVisible();
|
|
await expect(page.getByText("Ends:")).toBeVisible();
|
|
|
|
// Note: The actual time-based expiration logic is tested in unit tests
|
|
// (see app/features/ban/core/banned.test.ts)
|
|
});
|
|
});
|