CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
Gitea Actions Runner Test / test-job (push) Canceled after 0s
- P1: new src/lib/admin/rank-guard.ts with tryGuardRank/guardRank + RankGuardTarget/Result; extracted from users.ts; applied to admin-bans (create/lift + rank redirect) and admin-badges (caps + rank guard + error surfacing); import-url-safety.ts SSRF guard wired into clone save+icon fetch; logAudit await in help-tickets; antiddos mutations SETTINGS_EDIT; markAllAlertsRead NOTIFICATIONS_EDIT; rankError/saveError in all 25 message files; tests rew/new for bans/badges/rank-guard/import-url-safety - P2: ticket-inbox merged pagination reworked to single UNION ALL SQL with LIMIT/OFFSET (avoids O(offset) materialization); getCachedAdminCount with cacheFiltered option applied to devops/errors/photos/items/guilds/help-tickets/moderation pages; admin-theme catches→?error=1+banner+25 locales; logger in silent catches (rare-values/writeable-boxes); catalog-items-loader CAST→plain equality; count caching helper extended with opt-in cacheFiltered - P3: removed dead modules is-staff/notice/recent-furni-resync/log-filters plus load-user-sanctions/user-sanction-timeline; removed orphaned CATALOG_PAGE_COLUMNS re-export; removed getFurniIconUrl from studio-furni-icons Build: lint 0, typecheck 0, 3480 tests pass, i18n:check exit 0, colors exit 0. Circular chunk warning (9452/10535/66463) is webpack code-splitting artifact; build succeeds.
83 lines
2.5 KiB
TypeScript
83 lines
2.5 KiB
TypeScript
// @ts-nocheck
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const state = vi.hoisted(() => ({
|
|
users: [] as Array<{ username: string; rank: number; mail: string | null }>,
|
|
highestRank: 8,
|
|
}));
|
|
|
|
vi.mock("@/lib/db", async () => {
|
|
const schema = await import("@/db/schema");
|
|
const { createFakeDb } = await import("@/test/fake-db");
|
|
const fake = createFakeDb((_table, projection) =>
|
|
"username" in projection ? state.users : [],
|
|
);
|
|
return { ...schema, db: fake };
|
|
});
|
|
|
|
vi.mock("@/lib/permissions", () => ({
|
|
getHighestRank: vi.fn(async () => state.highestRank),
|
|
}));
|
|
|
|
vi.mock("@/lib/safe-action-shared", () => ({
|
|
ActionError: class ActionError extends Error {},
|
|
}));
|
|
|
|
import { getHighestRank } from "@/lib/permissions";
|
|
import { guardRank, tryGuardRank } from "./rank-guard";
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
state.users = [{ username: "victim", rank: 5, mail: "[email protected]" }];
|
|
state.highestRank = 8;
|
|
});
|
|
|
|
describe("tryGuardRank", () => {
|
|
it("allows an actor at a strictly higher rank", async () => {
|
|
const result = await tryGuardRank(1, 7);
|
|
expect(result).toEqual({ ok: true, target: state.users[0] });
|
|
});
|
|
|
|
it("blocks an actor at the same rank", async () => {
|
|
state.users = [{ username: "victim", rank: 7, mail: null }];
|
|
const result = await tryGuardRank(1, 7);
|
|
expect(result).toEqual({ ok: false, reason: "rank" });
|
|
});
|
|
|
|
it("blocks an actor at a lower rank", async () => {
|
|
const result = await tryGuardRank(1, 3);
|
|
expect(result).toEqual({ ok: false, reason: "rank" });
|
|
});
|
|
|
|
it("lets the dynamic super admin act on anyone", async () => {
|
|
state.users = [{ username: "victim", rank: 8, mail: "[email protected]" }];
|
|
state.highestRank = 8;
|
|
const result = await tryGuardRank(1, 8);
|
|
expect(result).toEqual({
|
|
ok: true,
|
|
target: { username: "victim", rank: 8, mail: "[email protected]" },
|
|
});
|
|
expect(await getHighestRank()).toBe(8);
|
|
});
|
|
|
|
it("reports a missing target as not_found", async () => {
|
|
state.users = [];
|
|
const result = await tryGuardRank(999, 7);
|
|
expect(result).toEqual({ ok: false, reason: "not_found" });
|
|
});
|
|
});
|
|
|
|
describe("guardRank", () => {
|
|
it("throws a readable ActionError for a target at equal or higher rank", async () => {
|
|
state.users = [{ username: "victim", rank: 7, mail: null }];
|
|
await expect(guardRank(1, 7)).rejects.toThrow(
|
|
"Cannot modify user with equal or higher rank",
|
|
);
|
|
});
|
|
|
|
it("throws a readable ActionError for a missing target", async () => {
|
|
state.users = [];
|
|
await expect(guardRank(999, 7)).rejects.toThrow("User not found");
|
|
});
|
|
});
|