tests: add coverage for src/actions/ (15 files) and src/lib/{admin,auth} (3 files)

- src/actions coverage: 2.4% -> 13.55%
- src/lib/admin coverage: 44.3% -> 84.81%
- src/lib/auth coverage: 90.52%
- vitest.config.ts: exclude .next.prev/ from test discovery
This commit is contained in:
openhands committed 2026-07-30 18:48:51 +02:00
1 parent c433e5a52f
commit ea7d861e69
19 files changed
+1262 -1

No files matched your search

+124
View File
@@ -0,0 +1,124 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { redirectSafe } from "@/lib/foundation/security";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
import { clientIp, rateLimit } from "@/lib/rate-limit";
import {
requireMod,
requireModPermission,
requirePermission,
requirePermissionRateLimited,
requireStaff,
requireStaffRateLimited,
} from "./guard";
vi.mock("@/lib/permissions", () => ({
getAdminContext: vi.fn(),
canAccess: vi.fn(),
PERMS: {
ADMIN_DASHBOARD: "admin.dashboard",
MOD_DASHBOARD: "mod.dashboard",
MODERATION_VIEW: "moderation.view",
MOD_CFH_VIEW: "mod.cfh.view",
MOD_ACTIONS: "mod.actions",
MOD_BANS_VIEW: "mod.bans.view",
MOD_TICKETS_VIEW: "mod.tickets.view",
MOD_TEAM_VIEW: "mod.team.view",
TICKETS_VIEW: "tickets.view",
},
}));
vi.mock("@/lib/foundation/security", () => ({ redirectSafe: vi.fn() }));
vi.mock("@/lib/rate-limit", () => ({ clientIp: vi.fn(), rateLimit: vi.fn() }));
const session = {
session: { user: { id: 1, rank: 7, username: "admin" } },
permissions: { "admin.dashboard": 7 },
};
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(getAdminContext).mockResolvedValue(session as never);
vi.mocked(canAccess).mockImplementation(
(_p: unknown, _s: string, r: number) => r >= 7,
);
});
describe("requireStaff", () => {
it("returns staff when authorized", async () => {
const s = await requireStaff();
expect(s).toEqual({ id: 1, rank: 7, username: "admin" });
});
it("redirects when unauthorized", async () => {
vi.mocked(getAdminContext).mockResolvedValue({
session: { user: { id: 2, rank: 1, username: "user" } },
permissions: {},
} as never);
await requireStaff();
expect(redirectSafe).toHaveBeenCalledWith("/", "/");
});
});
describe("requirePermission", () => {
it("returns staff when both dashboard and specific perm ok", async () => {
vi.mocked(canAccess).mockReturnValue(true);
expect(await requirePermission("user.edit")).toEqual({
id: 1,
rank: 7,
username: "admin",
});
});
});
describe("requireMod", () => {
it("allows with MOD_DASHBOARD", async () => {
vi.mocked(getAdminContext).mockResolvedValue({
session: { user: { id: 1, rank: 5, username: "mod" } },
permissions: {},
} as never);
vi.mocked(canAccess).mockImplementation(
(_p: unknown, slug: string) => slug === "mod.dashboard",
);
expect(await requireMod()).toEqual({ id: 1, rank: 5, username: "mod" });
});
it("redirects without mod perms", async () => {
vi.mocked(canAccess).mockReturnValue(false);
await requireMod();
expect(redirectSafe).toHaveBeenCalledWith("/", "/");
});
});
describe("requireModPermission", () => {
it("allows with matching perm", async () => {
vi.mocked(canAccess).mockImplementation(
(_p: unknown, slug: string) => slug === "mod.tickets.view",
);
await requireModPermission(["mod.bans.view", "mod.tickets.view"]);
expect(redirectSafe).not.toHaveBeenCalled();
});
});
describe("requirePermissionRateLimited", () => {
it("returns staff when rate limit ok", async () => {
vi.mocked(canAccess).mockReturnValue(true);
vi.mocked(clientIp).mockResolvedValue("1.2.3.4");
vi.mocked(rateLimit).mockResolvedValue({ ok: true });
expect(await requirePermissionRateLimited("x")).toEqual({
id: 1,
rank: 7,
username: "admin",
});
});
});
describe("requireStaffRateLimited", () => {
it("redirects on rate limit", async () => {
vi.mocked(clientIp).mockResolvedValue("1.2.3.4");
vi.mocked(rateLimit).mockResolvedValue({ ok: false });
await requireStaffRateLimited();
expect(redirectSafe).toHaveBeenCalledWith(
"/admin?error=ratelimit",
"/admin",
);
});
});
+29
View File
@@ -0,0 +1,29 @@
import { describe, expect, it, vi } from "vitest";
import { siteSettings } from "@/lib/services/site-settings";
import { getMinStaffRank } from "./min-staff-rank";
vi.mock("@/lib/services/site-settings", () => ({
siteSettings: { get: vi.fn() },
}));
describe("getMinStaffRank", () => {
it("returns numeric value", async () => {
vi.mocked(siteSettings.get).mockResolvedValue("7");
expect(await getMinStaffRank()).toBe(7);
});
it("uses fallback when setting missing", async () => {
vi.mocked(siteSettings.get).mockResolvedValue("");
expect(await getMinStaffRank(5)).toBe(5);
});
it("floors values", async () => {
vi.mocked(siteSettings.get).mockResolvedValue("7.8");
expect(await getMinStaffRank()).toBe(7);
});
it("rejects zero", async () => {
vi.mocked(siteSettings.get).mockResolvedValue("0");
expect(await getMinStaffRank(3)).toBe(3);
});
});
+17
View File
@@ -0,0 +1,17 @@
import { describe, expect, it } from "vitest";
import {
personalTokenScope,
USER_TOKENABLE_TYPE,
} from "./personal-token-scope";
describe("personal-token-scope", () => {
it("exports USER_TOKENABLE_TYPE constant", () => {
expect(USER_TOKENABLE_TYPE).toBe("App\\Models\\User");
});
it("returns correct scope with BigInt", () => {
const r = personalTokenScope(42);
expect(r.tokenableId).toBe(BigInt(42));
expect(r.tokenableType).toBe("App\\Models\\User");
});
});