Files
EpicNext-Cms/src/lib/staff-smoke-contract.test.ts
T

260 lines
9.1 KiB
TypeScript

import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
describe("staff smoke contract", () => {
it("gates photos delete with PAGES_EDIT", () => {
const src = readFileSync("src/actions/admin-photos.ts", "utf8");
expect(src).toContain("PERMS.PAGES_EDIT");
expect(src).toContain("deletePhoto");
expect(src).toContain("logStaffActivity");
expect(existsSync("src/app/admin/photos/page.tsx")).toBe(true);
expect(readFileSync("src/app/admin/photos/page.tsx", "utf8")).toContain(
"AdminPageShell",
);
});
it("gates bans create/lift", () => {
expect(existsSync("src/app/admin/bans/page.tsx")).toBe(true);
const bans = readFileSync("src/actions/admin-bans.ts", "utf8");
expect(bans).toContain("liftBan");
expect(bans).toContain("createBan");
expect(readFileSync("src/app/admin/bans/page.tsx", "utf8")).toContain(
"AdminPageShell",
);
});
it("wires ban appeal lift on help tickets", () => {
const actions = readFileSync("src/actions/admin-help-tickets.ts", "utf8");
expect(actions).toContain("liftBanFromHelpTicket");
expect(actions).toContain("PERMS.USERS_BAN");
const detail = readFileSync(
"src/app/admin/help-tickets/[id]/admin-help-ticket-detail.tsx",
"utf8",
);
expect(detail).toContain("liftBanFromHelpTicket");
expect(detail).toContain("canLiftBan");
});
it("exposes sanction timeline on user show", () => {
expect(
existsSync("src/app/admin/users/_components/user-sanction-timeline.tsx"),
).toBe(true);
expect(existsSync("src/app/admin/users/_lib/load-user-sanctions.ts")).toBe(
true,
);
expect(
readFileSync("src/app/admin/users/show/[id]/page.tsx", "utf8"),
).toContain("UserSanctionTimeline");
});
it("applies CMS SQL from drizzle/migrations (not Prisma)", () => {
expect(existsSync("drizzle/migrations")).toBe(true);
expect(existsSync("prisma")).toBe(false);
expect(readFileSync("scripts/apply-migrations.ts", "utf8")).toContain(
"drizzle/migrations",
);
const pkg = readFileSync("package.json", "utf8");
expect(pkg).not.toContain('"prisma"');
expect(pkg).not.toContain('"@prisma/client"');
expect(pkg).not.toContain("prisma:generate");
expect(pkg).toContain('"db:generate": "drizzle-kit generate"');
expect(pkg).toContain('"db:studio": "drizzle-kit studio"');
expect(pkg).toContain('"db:migrate": "tsx scripts/apply-migrations.ts"');
expect(readFileSync("drizzle.config.ts", "utf8")).toContain(
"./drizzle/drafts",
);
});
it("ships shared ops + ticket queue helpers", () => {
expect(existsSync("src/lib/admin/ops-health.ts")).toBe(true);
const ops = readFileSync("src/lib/admin/ops-health.ts", "utf8");
expect(ops).toContain("redisOk");
expect(ops).toContain("redis.ping");
expect(existsSync("src/lib/admin/ticket-queue-counts.ts")).toBe(true);
expect(existsSync("src/lib/admin/ops-online-users.ts")).toBe(true);
});
it("ships dynamic admin menu overlay", () => {
expect(existsSync("src/lib/admin-nav-config.ts")).toBe(true);
expect(existsSync("src/app/admin/menu/page.tsx")).toBe(true);
expect(readFileSync("src/lib/admin-nav.ts", "utf8")).toContain(
"/admin/menu",
);
expect(
readFileSync("src/components/admin/admin-sidebar-nav.tsx", "utf8"),
).toContain("applyAdminNavConfig");
expect(readFileSync("src/app/admin/layout.tsx", "utf8")).toContain(
"ADMIN_NAV_CONFIG_KEY",
);
});
it("opts admin/mod out of static caching via instant=false", () => {
// The Cache Components migration replaced `force-dynamic` with
// `export const instant = false` — the legacy export must not survive.
let legacyHits = 0;
function walk(dir: string) {
for (const name of readdirSync(dir)) {
const p = join(dir, name);
if (statSync(p).isDirectory()) walk(p);
else if (/\.(tsx?|jsx?)$/.test(name)) {
const src = readFileSync(p, "utf8");
if (
/export\s+const\s+dynamic\s*=\s*["']force-dynamic["']/.test(src)
) {
legacyHits++;
}
}
}
}
walk("src/app/admin");
walk("src/app/mod");
expect(legacyHits).toBe(0);
// The root layout carries the single Cache Components opt-out; staff
// pages inherit it (was force-dynamic, then per-layout instant=false).
expect(readFileSync("src/app/layout.tsx", "utf8")).toContain(
"export const instant = false",
);
// Staff layouts must not redeclare the opt-out — that is the root
// layout's job now, and redeclaring it is the legacy per-layout pattern.
expect(readFileSync("src/app/admin/layout.tsx", "utf8")).not.toContain(
"export const instant",
);
expect(readFileSync("src/app/mod/layout.tsx", "utf8")).not.toContain(
"export const instant",
);
});
it("caches analytics via redisCache", () => {
for (const path of [
"src/app/admin/analytics/page.tsx",
"src/app/admin/analytics/activity/page.tsx",
"src/app/admin/analytics/economy/page.tsx",
]) {
expect(readFileSync(path, "utf8"), path).toContain("redisCache");
}
});
it("exports bulk adjust currency and trade lock", () => {
const src = readFileSync("src/actions/bulk-users.ts", "utf8");
expect(src).toContain("bulkAdjustCurrency");
expect(src).toContain("setTradeLock");
expect(src).toContain("peopleMutationService.execute");
expect(src).toContain('"user.trade-lock"');
});
it("deletes photos via Drizzle with local file purge helper", () => {
const src = readFileSync("src/actions/admin-photos.ts", "utf8");
expect(src).toContain("CameraWeb");
expect(src).toContain("tryRemoveLocalPhotoFile");
expect(src).toContain("@/lib/admin/photo-files");
expect(src).toContain("@/lib/db");
});
it("guards dual ticket queues on admin and mod", () => {
for (const path of [
"src/app/admin/tickets/desk/page.tsx",
"src/app/admin/help-tickets/page.tsx",
"src/app/mod/tickets/desk/page.tsx",
"src/app/mod/help-tickets/page.tsx",
"src/app/admin/tickets/[id]/page.tsx",
"src/app/admin/help-tickets/[id]/page.tsx",
"src/app/mod/tickets/[id]/page.tsx",
"src/app/mod/help-tickets/[id]/page.tsx",
]) {
expect(existsSync(path), path).toBe(true);
expect(readFileSync(path, "utf8"), path).toContain("TicketQueueBanner");
expect(readFileSync(path, "utf8"), path).toContain(
"fetchTicketQueueOpenCounts",
);
}
});
it("ships unified ticket inbox over both queues", () => {
expect(existsSync("src/lib/admin/ticket-inbox.ts")).toBe(true);
expect(readFileSync("src/lib/admin/ticket-inbox.ts", "utf8")).toContain(
"fetchUnifiedTicketInbox",
);
expect(existsSync("src/components/admin/unified-tickets-table.tsx")).toBe(
true,
);
expect(readFileSync("src/app/admin/tickets/page.tsx", "utf8")).toContain(
"fetchUnifiedTicketInbox",
);
expect(readFileSync("src/app/mod/tickets/page.tsx", "utf8")).toContain(
"fetchUnifiedTicketInbox",
);
expect(readFileSync("src/lib/admin-nav.ts", "utf8")).toContain(
"ticketInbox",
);
});
it("keeps StatusCard server-safe without client boundary", () => {
expect(
readFileSync("src/components/admin/dashboard.tsx", "utf8"),
).not.toMatch(/^["']use client["']/m);
expect(existsSync("src/components/admin/online-users-widget.tsx")).toBe(
true,
);
expect(
readFileSync("src/components/admin/online-users-widget.tsx", "utf8"),
).toContain("use client");
expect(existsSync("src/app/admin/ads/edit-ad-delete-button.tsx")).toBe(
true,
);
});
it("ships ops health alerts, optional DB backup, and health rate limit", () => {
const worker = readFileSync("scripts/jobs-worker.ts", "utf8");
expect(worker).toContain("checkOpsHealth");
expect(worker).toContain("healthDegraded");
expect(worker).toContain("backupDatabase");
expect(readFileSync("src/lib/services/alert.ts", "utf8")).toContain(
"healthDegraded",
);
expect(readFileSync("src/app/api/health/route.ts", "utf8")).toContain(
"rateLimit",
);
expect(readFileSync("src/actions/admin-alerts.ts", "utf8")).toContain(
"markAllAlertsRead",
);
expect(readFileSync("src/env.ts", "utf8")).toContain("DB_BACKUP_DIR");
});
it("documents local-only photo file purge", () => {
expect(readFileSync("src/app/admin/photos/page.tsx", "utf8")).toContain(
"purgeHint",
);
expect(readFileSync("src/messages/en.json", "utf8")).toContain(
"External CDN URLs are not purged",
);
});
it("ships items base admin CRUD", () => {
expect(existsSync("src/app/admin/items/page.tsx")).toBe(true);
expect(existsSync("src/app/admin/items/[id]/page.tsx")).toBe(true);
expect(readFileSync("src/actions/items-base.ts", "utf8")).toContain(
"updateItemsBase",
);
expect(readFileSync("src/lib/admin-nav.ts", "utf8")).toContain(
"/admin/items",
);
});
it("ships studio hub with synced badge import path", () => {
const nav = readFileSync("src/lib/admin-nav.ts", "utf8");
expect(nav).toContain('href: "/admin/studio"');
expect(nav).toContain('labelKey: "studio"');
expect(nav).toContain('"/admin/studio"');
expect(existsSync("src/lib/services/import-badge.ts")).toBe(true);
const badgeService = readFileSync(
"src/lib/services/import-badge.ts",
"utf8",
);
expect(badgeService).toContain("ExternalTexts.json");
expect(badgeService).toContain("WebsiteBadges");
expect(badgeService).toContain("importBadgeSynced");
});
});