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:
1 parent
c433e5a52f
commit
ea7d861e69
19 files changed
+1262
-1
No files matched your search
@@ -0,0 +1,83 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { ActionError } from "@/lib/safe-action-shared";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import { createAd, deleteAd, deleteAdForm, updateAd } from "./admin-ads";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({ PERMS: { PAGES_EDIT: "pages.edit" } }));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: { websiteAds: { create: vi.fn(), update: vi.fn(), delete: vi.fn() } },
|
||||
}));
|
||||
vi.mock("@/lib/logger", () => ({ logger: { error: vi.fn() } }));
|
||||
vi.mock("@/lib/safe-action", () => ({
|
||||
adminAction: vi.fn((_opts: unknown, fn: Function) => fn),
|
||||
}));
|
||||
vi.mock("@/lib/safe-action-shared", () => ({
|
||||
ActionError: class extends Error {},
|
||||
actionOk: vi.fn(() => "ok"),
|
||||
}));
|
||||
vi.mock("@/lib/services/staff-activity", () => ({ logStaffActivity: vi.fn() }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
vi.mock("next/navigation", () => ({ redirect: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
const fakeForm = (data: Record<string, string>) => ({
|
||||
get: (key: string) => data[key] ?? null,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
});
|
||||
|
||||
describe("createAd", () => {
|
||||
it("creates an ad and redirects", async () => {
|
||||
vi.mocked(prisma.websiteAds.create).mockResolvedValue({
|
||||
id: BigInt(1),
|
||||
} as never);
|
||||
await createAd(
|
||||
fakeForm({ image: "https://example.com/ad.png" }) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteAds.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ image: "https://example.com/ad.png" }),
|
||||
});
|
||||
expect(logStaffActivity).toHaveBeenCalled();
|
||||
expect(redirect).toHaveBeenCalledWith("/admin/ads");
|
||||
});
|
||||
|
||||
it("returns early when image is empty", async () => {
|
||||
await createAd(fakeForm({ image: "" }) as unknown as FormData);
|
||||
expect(prisma.websiteAds.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("logs error and revalidates on db failure", async () => {
|
||||
vi.mocked(prisma.websiteAds.create).mockRejectedValue(new Error("DB down"));
|
||||
await createAd(fakeForm({ image: "x" }) as unknown as FormData);
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/ads");
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteAd", () => {
|
||||
it("deletes an ad via adminAction", async () => {
|
||||
const handler = deleteAd as unknown as (ctx: {
|
||||
data: { id: bigint };
|
||||
session: { user: { id: string } };
|
||||
}) => Promise<string>;
|
||||
vi.mocked(prisma.websiteAds.delete).mockResolvedValue({} as never);
|
||||
const result = await handler({
|
||||
data: { id: BigInt(99) },
|
||||
session: { user: { id: "1" } },
|
||||
});
|
||||
expect(prisma.websiteAds.delete).toHaveBeenCalledWith({
|
||||
where: { id: BigInt(99) },
|
||||
});
|
||||
expect(result).toBe("ok");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
import { sendHotelAlert } from "./admin-alerts";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({
|
||||
PERMS: { NOTIFICATIONS_EDIT: "notifications.edit" },
|
||||
}));
|
||||
vi.mock("@/lib/services/rcon", () => ({ rcon: { send: vi.fn() } }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
|
||||
const fakeForm = (data: Record<string, string>) => ({
|
||||
get: (key: string) => data[key] ?? null,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue({
|
||||
id: 1,
|
||||
rank: 7,
|
||||
username: "admin",
|
||||
} as never);
|
||||
});
|
||||
|
||||
describe("sendHotelAlert", () => {
|
||||
it("sends hotel alert and revalidates", async () => {
|
||||
await sendHotelAlert(
|
||||
fakeForm({ message: "Hello!" }) as unknown as FormData,
|
||||
);
|
||||
expect(rcon.send).toHaveBeenCalledWith("hotelalert", { message: "Hello!" });
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/alerts");
|
||||
});
|
||||
|
||||
it("returns early when message is empty", async () => {
|
||||
await sendHotelAlert(fakeForm({ message: "" }) as unknown as FormData);
|
||||
expect(rcon.send).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermissionRateLimited } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import { createBan, liftBan } from "./admin-bans";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermissionRateLimited: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({ PERMS: { USERS_BAN: "users.ban" } }));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: {
|
||||
user: { findUnique: vi.fn() },
|
||||
ban: { create: vi.fn(), delete: vi.fn() },
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/services/rcon", () => ({ rcon: { disconnectUser: vi.fn() } }));
|
||||
vi.mock("@/lib/services/staff-activity", () => ({ logStaffActivity: vi.fn() }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
const fakeForm = (data: Record<string, string>) => ({
|
||||
get: (key: string) => data[key] ?? null,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermissionRateLimited).mockResolvedValue(staff as never);
|
||||
});
|
||||
|
||||
describe("createBan", () => {
|
||||
it("creates a ban for valid inputs", async () => {
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
username: "baduser",
|
||||
} as never);
|
||||
await createBan(
|
||||
fakeForm({
|
||||
userId: "42",
|
||||
reason: "Spam",
|
||||
hours: "24",
|
||||
type: "account",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.ban.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ userId: 42, type: "account" }),
|
||||
});
|
||||
expect(rcon.disconnectUser).toHaveBeenCalledWith(42, "baduser");
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/bans");
|
||||
});
|
||||
|
||||
it("returns early when userId is invalid", async () => {
|
||||
await createBan(
|
||||
fakeForm({
|
||||
userId: "0",
|
||||
hours: "1",
|
||||
type: "account",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.ban.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("liftBan", () => {
|
||||
it("deletes ban and revalidates", async () => {
|
||||
await liftBan(fakeForm({ id: "42" }) as unknown as FormData);
|
||||
expect(prisma.ban.delete).toHaveBeenCalledWith({ where: { id: 42 } });
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/bans");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermissionRateLimited } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import { disbandGuild } from "./admin-guilds";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermissionRateLimited: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({ PERMS: { USERS_EDIT: "users.edit" } }));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: {
|
||||
guilds: { findUnique: vi.fn(), delete: vi.fn() },
|
||||
guildsForumsThreads: { findMany: vi.fn(), deleteMany: vi.fn() },
|
||||
guildsForumsComments: { deleteMany: vi.fn() },
|
||||
guildForumViews: { deleteMany: vi.fn() },
|
||||
guildsMembers: { deleteMany: vi.fn() },
|
||||
rooms: { updateMany: vi.fn() },
|
||||
items: { updateMany: vi.fn() },
|
||||
$transaction: vi.fn(),
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/services/staff-activity", () => ({ logStaffActivity: vi.fn() }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
const fakeForm = (data: Record<string, string>) => ({
|
||||
get: (key: string) => data[key] ?? null,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermissionRateLimited).mockResolvedValue(staff as never);
|
||||
});
|
||||
|
||||
describe("disbandGuild", () => {
|
||||
it("disbands guild and cleans related data", async () => {
|
||||
vi.mocked(prisma.guilds.findUnique).mockResolvedValue({
|
||||
id: 1,
|
||||
name: "TestGuild",
|
||||
userId: 42,
|
||||
} as never);
|
||||
vi.mocked(prisma.guildsForumsThreads.findMany).mockResolvedValue([
|
||||
{ id: 10 },
|
||||
] as never);
|
||||
vi.mocked(prisma.$transaction).mockImplementation(async (fn: unknown) => {
|
||||
const tx = {
|
||||
guildsForumsComments: { deleteMany: vi.fn().mockResolvedValue({}) },
|
||||
guildsForumsThreads: {
|
||||
findMany: vi.fn().mockResolvedValue([{ id: 10 }]),
|
||||
deleteMany: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
guildForumViews: { deleteMany: vi.fn().mockResolvedValue({}) },
|
||||
guildsMembers: { deleteMany: vi.fn().mockResolvedValue({}) },
|
||||
rooms: { updateMany: vi.fn().mockResolvedValue({}) },
|
||||
items: { updateMany: vi.fn().mockResolvedValue({}) },
|
||||
guilds: { delete: vi.fn().mockResolvedValue({}) },
|
||||
} as never;
|
||||
await (fn as (tx: never) => Promise<void>)(tx);
|
||||
});
|
||||
await disbandGuild(fakeForm({ id: "1" }) as unknown as FormData);
|
||||
expect(logStaffActivity).toHaveBeenCalled();
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/guilds");
|
||||
});
|
||||
|
||||
it("returns early when id is not positive", async () => {
|
||||
await disbandGuild(fakeForm({ id: "0" }) as unknown as FormData);
|
||||
expect(prisma.guilds.findUnique).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import {
|
||||
createHelpQuestion,
|
||||
deleteHelpQuestion,
|
||||
updateHelpQuestion,
|
||||
} from "./admin-help";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({ PERMS: { PAGES_EDIT: "pages.edit" } }));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: {
|
||||
websiteHelpCenterCategories: {
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/services/staff-activity", () => ({ logStaffActivity: vi.fn() }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
vi.mock("next/navigation", () => ({ redirect: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
const fakeForm = (data: Record<string, string | null>) => ({
|
||||
get: (key: string) => (key in data ? data[key] : null),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
});
|
||||
|
||||
describe("createHelpQuestion", () => {
|
||||
it("creates a help question and redirects", async () => {
|
||||
vi.mocked(prisma.websiteHelpCenterCategories.create).mockResolvedValue({
|
||||
id: BigInt(5),
|
||||
} as never);
|
||||
await createHelpQuestion(
|
||||
fakeForm({
|
||||
name: "FAQ",
|
||||
content: "<p>Answer</p>",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteHelpCenterCategories.create).toHaveBeenCalled();
|
||||
expect(redirect).toHaveBeenCalledWith("/admin/help-questions");
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateHelpQuestion", () => {
|
||||
it("updates and redirects", async () => {
|
||||
vi.mocked(prisma.websiteHelpCenterCategories.update).mockResolvedValue(
|
||||
{} as never,
|
||||
);
|
||||
await updateHelpQuestion(
|
||||
fakeForm({
|
||||
id: "42",
|
||||
name: "Updated",
|
||||
content: "New",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteHelpCenterCategories.update).toHaveBeenCalled();
|
||||
expect(redirect).toHaveBeenCalledWith("/admin/help-questions");
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteHelpQuestion", () => {
|
||||
it("deletes and redirects", async () => {
|
||||
vi.mocked(prisma.websiteHelpCenterCategories.delete).mockResolvedValue(
|
||||
{} as never,
|
||||
);
|
||||
await deleteHelpQuestion(fakeForm({ id: "42" }) as unknown as FormData);
|
||||
expect(prisma.websiteHelpCenterCategories.delete).toHaveBeenCalled();
|
||||
expect(redirect).toHaveBeenCalledWith("/admin/help-questions");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import {
|
||||
addBlacklist,
|
||||
addWhitelist,
|
||||
deleteBlacklist,
|
||||
deleteWhitelist,
|
||||
} from "./admin-ip";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({
|
||||
PERMS: { SETTINGS_EDIT: "settings.edit" },
|
||||
}));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: {
|
||||
websiteIpWhitelist: { create: vi.fn(), delete: vi.fn() },
|
||||
websiteIpBlacklist: { create: vi.fn(), delete: vi.fn() },
|
||||
},
|
||||
}));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
const fakeForm = (data: Record<string, string>) => ({
|
||||
get: (key: string) => data[key] ?? null,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
});
|
||||
|
||||
describe("addWhitelist", () => {
|
||||
it("creates whitelist entry", async () => {
|
||||
await addWhitelist(
|
||||
fakeForm({ ipAddress: "192.168.1.1" }) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteIpWhitelist.create).toHaveBeenCalledWith({
|
||||
data: { ipAddress: "192.168.1.1", asn: null, whitelistAsn: false },
|
||||
});
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/ip");
|
||||
});
|
||||
|
||||
it("returns early when ip is empty", async () => {
|
||||
await addWhitelist(fakeForm({ ipAddress: "" }) as unknown as FormData);
|
||||
expect(prisma.websiteIpWhitelist.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteWhitelist", () => {
|
||||
it("deletes whitelist entry", async () => {
|
||||
await deleteWhitelist(fakeForm({ id: "42" }) as unknown as FormData);
|
||||
expect(prisma.websiteIpWhitelist.delete).toHaveBeenCalledWith({
|
||||
where: { id: BigInt(42) },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("addBlacklist", () => {
|
||||
it("creates blacklist entry", async () => {
|
||||
await addBlacklist(
|
||||
fakeForm({ ipAddress: "203.0.113.1" }) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteIpBlacklist.create).toHaveBeenCalledWith({
|
||||
data: { ipAddress: "203.0.113.1", asn: null, blacklistAsn: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteBlacklist", () => {
|
||||
it("deletes blacklist entry", async () => {
|
||||
await deleteBlacklist(fakeForm({ id: "99" }) as unknown as FormData);
|
||||
expect(prisma.websiteIpBlacklist.delete).toHaveBeenCalledWith({
|
||||
where: { id: BigInt(99) },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { MEDIA_ROOT, resolveMediaPath } from "@/lib/media-storage";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { deleteMedia, uploadMedia, uploadMediaAndReturn } from "./admin-media";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({ PERMS: { PAGES_EDIT: "pages.edit" } }));
|
||||
vi.mock("@/lib/media-storage", () => ({
|
||||
MEDIA_ROOT: "/tmp/nexst-test-media",
|
||||
resolveMediaPath: vi.fn((name: string) => `/tmp/nexst-test-media/${name}`),
|
||||
}));
|
||||
vi.mock("node:fs/promises", () => ({
|
||||
mkdir: vi.fn(),
|
||||
writeFile: vi.fn(),
|
||||
unlink: vi.fn(),
|
||||
}));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
});
|
||||
|
||||
describe("uploadMedia", () => {
|
||||
it("returns error when no file provided", async () => {
|
||||
const result = await uploadMedia(new FormData());
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toBe("No file provided");
|
||||
});
|
||||
});
|
||||
|
||||
describe("uploadMediaAndReturn", () => {
|
||||
it("returns empty string when no file", async () => {
|
||||
expect(await uploadMediaAndReturn(new FormData())).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteMedia", () => {
|
||||
it("deletes media file and revalidates", async () => {
|
||||
await deleteMedia("photo.png");
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/api/media");
|
||||
});
|
||||
|
||||
it("skips deletion when path is outside media root", async () => {
|
||||
vi.mocked(resolveMediaPath).mockReturnValue("/etc/passwd");
|
||||
await deleteMedia("../../../etc/passwd");
|
||||
const { unlink } = await import("node:fs/promises");
|
||||
expect(unlink).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { deletePhoto } from "./admin-photos";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({ PERMS: { PAGES_EDIT: "pages.edit" } }));
|
||||
vi.mock("@/lib/prisma", () => ({ prisma: { cameraWeb: { delete: vi.fn() } } }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
|
||||
const fakeForm = (data: Record<string, string>) => ({
|
||||
get: (key: string) => data[key] ?? null,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue({
|
||||
id: 1,
|
||||
rank: 7,
|
||||
username: "admin",
|
||||
} as never);
|
||||
});
|
||||
|
||||
describe("deletePhoto", () => {
|
||||
it("deletes a photo and revalidates", async () => {
|
||||
await deletePhoto(fakeForm({ id: "42" }) as unknown as FormData);
|
||||
expect(prisma.cameraWeb.delete).toHaveBeenCalledWith({ where: { id: 42 } });
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/photos");
|
||||
});
|
||||
|
||||
it("returns early when id is not positive", async () => {
|
||||
await deletePhoto(fakeForm({ id: "0" }) as unknown as FormData);
|
||||
expect(prisma.cameraWeb.delete).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import { createTag, deleteTag, updateTag } from "./admin-tags";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({ PERMS: { PAGES_EDIT: "pages.edit" } }));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: {
|
||||
tags: { create: vi.fn(), update: vi.fn(), delete: vi.fn() },
|
||||
taggables: { deleteMany: vi.fn() },
|
||||
$transaction: vi.fn(),
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/services/staff-activity", () => ({ logStaffActivity: vi.fn() }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
const fakeForm = (data: Record<string, string>) => ({
|
||||
get: (key: string) => data[key] ?? null,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
});
|
||||
|
||||
describe("createTag", () => {
|
||||
it("creates a tag and revalidates", async () => {
|
||||
vi.mocked(prisma.tags.create).mockResolvedValue({ id: BigInt(1) } as never);
|
||||
|
||||
await createTag(
|
||||
fakeForm({
|
||||
name: "News",
|
||||
backgroundColor: "#ff0000",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
|
||||
expect(prisma.tags.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({ name: "News" }),
|
||||
}),
|
||||
);
|
||||
expect(logStaffActivity).toHaveBeenCalled();
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/tags");
|
||||
});
|
||||
|
||||
it("returns early when name is empty", async () => {
|
||||
await createTag(fakeForm({ name: "" }) as unknown as FormData);
|
||||
expect(prisma.tags.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses default color when not provided", async () => {
|
||||
vi.mocked(prisma.tags.create).mockResolvedValue({ id: BigInt(1) } as never);
|
||||
|
||||
await createTag(fakeForm({ name: "Test" }) as unknown as FormData);
|
||||
|
||||
expect(prisma.tags.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({ backgroundColor: "#888888" }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("handles db error gracefully", async () => {
|
||||
vi.mocked(prisma.tags.create).mockRejectedValue(new Error("DB error"));
|
||||
|
||||
await expect(
|
||||
createTag(fakeForm({ name: "News" }) as unknown as FormData),
|
||||
).resolves.toBeUndefined();
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/tags");
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateTag", () => {
|
||||
it("updates a tag and revalidates", async () => {
|
||||
vi.mocked(prisma.tags.update).mockResolvedValue({} as never);
|
||||
|
||||
await updateTag(
|
||||
fakeForm({
|
||||
id: "42",
|
||||
name: "Updated",
|
||||
backgroundColor: "#00ff00",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
|
||||
expect(prisma.tags.update).toHaveBeenCalledWith({
|
||||
where: { id: BigInt(42) },
|
||||
data: expect.objectContaining({ name: "Updated" }),
|
||||
});
|
||||
expect(logStaffActivity).toHaveBeenCalled();
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/tags");
|
||||
});
|
||||
|
||||
it("returns early when id is invalid", async () => {
|
||||
await updateTag(fakeForm({ id: "", name: "Test" }) as unknown as FormData);
|
||||
expect(prisma.tags.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns early when name is empty after update", async () => {
|
||||
await updateTag(fakeForm({ id: "42", name: "" }) as unknown as FormData);
|
||||
expect(prisma.tags.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteTag", () => {
|
||||
it("deletes a tag and its taggables", async () => {
|
||||
vi.mocked(prisma.taggables.deleteMany).mockResolvedValue({
|
||||
count: 1,
|
||||
} as never);
|
||||
vi.mocked(prisma.tags.delete).mockResolvedValue({} as never);
|
||||
vi.mocked(prisma.$transaction).mockImplementation(async (ops: unknown) => {
|
||||
const arr = ops as [
|
||||
typeof prisma.taggables.deleteMany,
|
||||
typeof prisma.tags.delete,
|
||||
];
|
||||
await arr[0];
|
||||
await arr[1];
|
||||
});
|
||||
|
||||
await deleteTag(fakeForm({ id: "42" }) as unknown as FormData);
|
||||
|
||||
expect(prisma.taggables.deleteMany).toHaveBeenCalledWith({
|
||||
where: { tagId: BigInt(42) },
|
||||
});
|
||||
expect(prisma.tags.delete).toHaveBeenCalledWith({
|
||||
where: { id: BigInt(42) },
|
||||
});
|
||||
expect(logStaffActivity).toHaveBeenCalled();
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/tags");
|
||||
});
|
||||
|
||||
it("returns early when id is invalid", async () => {
|
||||
await deleteTag(fakeForm({ id: "" }) as unknown as FormData);
|
||||
expect(prisma.$transaction).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { createTeam, deleteTeam } from "./admin-teams";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({ PERMS: { USERS_EDIT: "users.edit" } }));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: { websiteTeams: { create: vi.fn(), delete: vi.fn() } },
|
||||
}));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
const fakeForm = (data: Record<string, string | null>) => ({
|
||||
get: (key: string) => (key in data ? data[key] : null),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
});
|
||||
|
||||
describe("createTeam", () => {
|
||||
it("creates a team entry", async () => {
|
||||
await createTeam(
|
||||
fakeForm({ rankName: "Moderator" }) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteTeams.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ rankName: "Moderator" }),
|
||||
});
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/teams");
|
||||
});
|
||||
|
||||
it("returns early when rankName is empty", async () => {
|
||||
await createTeam(fakeForm({ rankName: "" }) as unknown as FormData);
|
||||
expect(prisma.websiteTeams.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteTeam", () => {
|
||||
it("deletes a team entry", async () => {
|
||||
vi.mocked(prisma.websiteTeams.delete).mockResolvedValue({} as never);
|
||||
await deleteTeam(fakeForm({ id: "42" }) as unknown as FormData);
|
||||
expect(prisma.websiteTeams.delete).toHaveBeenCalledWith({
|
||||
where: { id: BigInt(42) },
|
||||
});
|
||||
expect(revalidatePath).toHaveBeenCalledWith("/admin/teams");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import { saveVpn } from "./admin-vpn";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({
|
||||
PERMS: { SETTINGS_EDIT: "settings.edit" },
|
||||
}));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: { websiteSetting: { upsert: vi.fn() } },
|
||||
}));
|
||||
vi.mock("@/lib/services/site-settings", () => ({
|
||||
siteSettings: { reload: vi.fn() },
|
||||
}));
|
||||
vi.mock("@/lib/services/staff-activity", () => ({ logStaffActivity: vi.fn() }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
vi.mock("next/navigation", () => ({ redirect: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
const fakeForm = (data: Record<string, string | null>) => ({
|
||||
get: (key: string) => (key in data ? data[key] : null),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
vi.mocked(prisma.websiteSetting.upsert).mockResolvedValue({} as never);
|
||||
});
|
||||
|
||||
describe("saveVpn", () => {
|
||||
it("saves VPN settings and redirects", async () => {
|
||||
await saveVpn(
|
||||
fakeForm({
|
||||
vpn_block_enabled: "1",
|
||||
vpn_provider: "proxycheck",
|
||||
vpn_api_key: "abc123",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteSetting.upsert).toHaveBeenCalledTimes(4);
|
||||
expect(siteSettings.reload).toHaveBeenCalled();
|
||||
expect(redirect).toHaveBeenCalledWith("/admin/vpn?saved=1");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||
import { applyStaff, applyTeam } from "./applications";
|
||||
|
||||
vi.mock("@/lib/auth", () => ({ auth: vi.fn() }));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: { websiteStaffApplications: { findFirst: vi.fn(), create: vi.fn() } },
|
||||
}));
|
||||
vi.mock("@/lib/rate-limit", () => ({ clientIp: vi.fn(), rateLimit: vi.fn() }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
vi.mock("next/navigation", () => ({ redirect: vi.fn() }));
|
||||
|
||||
const fakeForm = (data: Record<string, string>) => ({
|
||||
get: (key: string) => data[key] ?? null,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(auth).mockResolvedValue({ user: { id: "42" } } as never);
|
||||
vi.mocked(clientIp).mockResolvedValue("127.0.0.1");
|
||||
vi.mocked(rateLimit).mockResolvedValue({ ok: true });
|
||||
});
|
||||
|
||||
describe("applyStaff", () => {
|
||||
it("submits staff application", async () => {
|
||||
vi.mocked(prisma.websiteStaffApplications.findFirst).mockResolvedValue(
|
||||
null,
|
||||
);
|
||||
vi.mocked(prisma.websiteStaffApplications.create).mockResolvedValue(
|
||||
{} as never,
|
||||
);
|
||||
await applyStaff(
|
||||
fakeForm({
|
||||
rankId: "3",
|
||||
content: "I want to help!",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteStaffApplications.create).toHaveBeenCalled();
|
||||
expect(redirect).toHaveBeenCalledWith("/apply/staff?submitted=1");
|
||||
});
|
||||
|
||||
it("redirects to login when not authenticated", async () => {
|
||||
vi.mocked(auth).mockResolvedValue(null);
|
||||
await applyStaff(fakeForm({}) as unknown as FormData);
|
||||
expect(redirect).toHaveBeenCalledWith("/login");
|
||||
});
|
||||
|
||||
it("returns duplicate status when application exists", async () => {
|
||||
vi.mocked(prisma.websiteStaffApplications.findFirst).mockResolvedValue({
|
||||
id: 1,
|
||||
} as never);
|
||||
await applyStaff(
|
||||
fakeForm({
|
||||
rankId: "3",
|
||||
content: "I want to help!",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteStaffApplications.create).not.toHaveBeenCalled();
|
||||
expect(redirect).toHaveBeenCalledWith("/apply/staff?error=duplicate");
|
||||
});
|
||||
|
||||
it("handles rate limit", async () => {
|
||||
vi.mocked(rateLimit).mockResolvedValue({ ok: false });
|
||||
await applyStaff(
|
||||
fakeForm({
|
||||
rankId: "3",
|
||||
content: "I want to help!",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
expect(redirect).toHaveBeenCalledWith("/apply/staff?error=ratelimit");
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyTeam", () => {
|
||||
it("submits team application", async () => {
|
||||
vi.mocked(prisma.websiteStaffApplications.findFirst).mockResolvedValue(
|
||||
null,
|
||||
);
|
||||
vi.mocked(prisma.websiteStaffApplications.create).mockResolvedValue(
|
||||
{} as never,
|
||||
);
|
||||
await applyTeam(
|
||||
fakeForm({
|
||||
teamId: "2",
|
||||
content: "I want to join team!",
|
||||
}) as unknown as FormData,
|
||||
);
|
||||
expect(prisma.websiteStaffApplications.create).toHaveBeenCalled();
|
||||
expect(redirect).toHaveBeenCalledWith("/apply/team?submitted=1");
|
||||
});
|
||||
|
||||
it("redirects to login when not authenticated", async () => {
|
||||
vi.mocked(auth).mockResolvedValue(null);
|
||||
await applyTeam(fakeForm({}) as unknown as FormData);
|
||||
expect(redirect).toHaveBeenCalledWith("/login");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { env } from "@/env";
|
||||
import { checkLogin } from "@/lib/auth/password";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||
import { captchaConfig, verifyCaptcha } from "@/lib/services/captcha";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import { precheckLogin } from "./auth-precheck";
|
||||
|
||||
vi.mock("@/env", () => ({ env: { CONVERT_PASSWORDS: false } }));
|
||||
vi.mock("@/lib/auth/password", () => ({ checkLogin: vi.fn() }));
|
||||
vi.mock("@/lib/prisma", () => ({ prisma: { user: { findUnique: vi.fn() } } }));
|
||||
vi.mock("@/lib/rate-limit", () => ({ clientIp: vi.fn(), rateLimit: vi.fn() }));
|
||||
vi.mock("@/lib/services/captcha", () => ({
|
||||
captchaConfig: vi.fn(),
|
||||
verifyCaptcha: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/services/site-settings", () => ({
|
||||
siteSettings: { getBool: vi.fn() },
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(clientIp).mockResolvedValue("1.2.3.4");
|
||||
vi.mocked(rateLimit).mockResolvedValue({ ok: true });
|
||||
vi.mocked(checkLogin).mockResolvedValue({ valid: true } as never);
|
||||
vi.mocked(captchaConfig).mockResolvedValue({ provider: "none" } as never);
|
||||
});
|
||||
|
||||
describe("precheckLogin", () => {
|
||||
it("returns ok for valid login without 2FA", async () => {
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
password: "hash",
|
||||
twoFactorConfirmedAt: null,
|
||||
mail: null,
|
||||
mailVerified: "0",
|
||||
} as never);
|
||||
expect(await precheckLogin("user", "pass")).toBe("ok");
|
||||
});
|
||||
|
||||
it("returns twofactor when 2FA is set up", async () => {
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
password: "hash",
|
||||
twoFactorConfirmedAt: new Date(),
|
||||
mail: null,
|
||||
mailVerified: "0",
|
||||
} as never);
|
||||
expect(await precheckLogin("user", "pass")).toBe("twofactor");
|
||||
});
|
||||
|
||||
it("returns invalid for empty inputs", async () => {
|
||||
expect(await precheckLogin("", "")).toBe("invalid");
|
||||
});
|
||||
|
||||
it("returns captcha when captcha required", async () => {
|
||||
vi.mocked(captchaConfig).mockResolvedValue({
|
||||
provider: "hcaptcha",
|
||||
} as never);
|
||||
vi.mocked(verifyCaptcha).mockResolvedValue(false);
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
password: "hash",
|
||||
twoFactorConfirmedAt: null,
|
||||
mail: null,
|
||||
mailVerified: "0",
|
||||
} as never);
|
||||
expect(await precheckLogin("user", "pass", "bad-token")).toBe("captcha");
|
||||
});
|
||||
|
||||
it("returns invalid when user not found (dummy hash check)", async () => {
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue(null);
|
||||
const result = await precheckLogin("nonexistent", "pass");
|
||||
expect(result).toBe("invalid");
|
||||
expect(checkLogin).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns unverified when email verification required", async () => {
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
password: "hash",
|
||||
twoFactorConfirmedAt: null,
|
||||
mail: "[email protected]",
|
||||
mailVerified: "0",
|
||||
} as never);
|
||||
vi.mocked(siteSettings.getBool).mockResolvedValue(true);
|
||||
expect(await precheckLogin("user", "pass")).toBe("unverified");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import {
|
||||
bulkBan,
|
||||
bulkGiveBadge,
|
||||
bulkGiveCurrency,
|
||||
bulkUnban,
|
||||
} from "./bulk-users";
|
||||
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/permissions", () => ({ PERMS: { USERS_EDIT: "users.edit" } }));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: {
|
||||
ban: { deleteMany: vi.fn(), create: vi.fn() },
|
||||
user: { update: vi.fn() },
|
||||
usersCurrency: { upsert: vi.fn() },
|
||||
usersBadges: { findFirst: vi.fn(), aggregate: vi.fn(), create: vi.fn() },
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/services/rcon", () => ({
|
||||
rcon: {
|
||||
giveCredits: vi.fn(),
|
||||
giveDuckets: vi.fn(),
|
||||
givePointsGotw: vi.fn(),
|
||||
giveBadge: vi.fn(),
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/services/staff-activity", () => ({ logStaffActivity: vi.fn() }));
|
||||
|
||||
const staff = { id: 1, rank: 7, username: "admin" };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
});
|
||||
|
||||
describe("bulkUnban", () => {
|
||||
it("unbans users", async () => {
|
||||
vi.mocked(prisma.ban.deleteMany).mockResolvedValue({ count: 3 } as never);
|
||||
const r = await bulkUnban({ userIds: [1, 2, 3] });
|
||||
expect(r.ok).toBe(true);
|
||||
expect(r.data).toEqual({ unbanned: 3, total: 3 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("bulkBan", () => {
|
||||
it("bans users", async () => {
|
||||
vi.mocked(prisma.ban.create).mockResolvedValue({} as never);
|
||||
const r = await bulkBan({
|
||||
userIds: [1, 2],
|
||||
reason: "Spam",
|
||||
duration: 3600,
|
||||
});
|
||||
expect(r.ok).toBe(true);
|
||||
expect(r.data.banned).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bulkGiveCurrency", () => {
|
||||
it("gives credits", async () => {
|
||||
vi.mocked(prisma.user.update).mockResolvedValue({} as never);
|
||||
const r = await bulkGiveCurrency({
|
||||
userIds: [1],
|
||||
amount: 100,
|
||||
type: "credits",
|
||||
});
|
||||
expect(r.data.given).toBe(1);
|
||||
expect(rcon.giveCredits).toHaveBeenCalledWith(1, 100);
|
||||
});
|
||||
|
||||
it("gives pixels", async () => {
|
||||
vi.mocked(prisma.usersCurrency.upsert).mockResolvedValue({} as never);
|
||||
const r = await bulkGiveCurrency({
|
||||
userIds: [2],
|
||||
amount: 50,
|
||||
type: "pixels",
|
||||
});
|
||||
expect(r.data.given).toBe(1);
|
||||
expect(rcon.giveDuckets).toHaveBeenCalledWith(2, 50);
|
||||
});
|
||||
|
||||
it("gives points", async () => {
|
||||
vi.mocked(prisma.usersCurrency.upsert).mockResolvedValue({} as never);
|
||||
const r = await bulkGiveCurrency({
|
||||
userIds: [3],
|
||||
amount: 25,
|
||||
type: "points",
|
||||
});
|
||||
expect(r.data.given).toBe(1);
|
||||
expect(rcon.givePointsGotw).toHaveBeenCalledWith(3, 25);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bulkGiveBadge", () => {
|
||||
it("gives badge to user", async () => {
|
||||
vi.mocked(prisma.usersBadges.findFirst).mockResolvedValue(null);
|
||||
vi.mocked(prisma.usersBadges.aggregate).mockResolvedValue({
|
||||
_max: { slotId: 5 },
|
||||
} as never);
|
||||
vi.mocked(prisma.usersBadges.create).mockResolvedValue({} as never);
|
||||
const r = await bulkGiveBadge({ userIds: [1], badgeCode: "ADM" });
|
||||
expect(r.data.given).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { adminAction } from "@/lib/safe-action";
|
||||
import { actionOk } from "@/lib/safe-action-shared";
|
||||
import { logAudit } from "@/lib/services/audit";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
|
||||
vi.mock("@/lib/permissions", () => ({
|
||||
PERMS: { SETTINGS_EDIT: "settings.edit" },
|
||||
}));
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: { emulatorSettings: { upsert: vi.fn() } },
|
||||
}));
|
||||
vi.mock("@/lib/safe-action", () => ({
|
||||
adminAction: vi.fn((_opts: unknown, fn: Function) => fn),
|
||||
}));
|
||||
vi.mock("@/lib/safe-action-shared", () => ({ actionOk: vi.fn(() => "ok") }));
|
||||
vi.mock("@/lib/services/audit", () => ({ logAudit: vi.fn() }));
|
||||
vi.mock("@/lib/services/rcon", () => ({ rcon: { updateConfig: vi.fn() } }));
|
||||
|
||||
describe("saveEmulatorSettings", () => {
|
||||
it("saves settings and calls rcon update", async () => {
|
||||
vi.mocked(prisma.emulatorSettings.upsert).mockResolvedValue({} as never);
|
||||
const handler = (await import("./emulator").then(
|
||||
(m) => m.saveEmulatorSettings,
|
||||
)) as unknown as (ctx: {
|
||||
data: { settings: Record<string, string> };
|
||||
session: { user: { id: string } };
|
||||
}) => Promise<string>;
|
||||
|
||||
const result = await handler({
|
||||
data: { settings: { key1: "val1", key2: "val2" } },
|
||||
session: { user: { id: "1" } },
|
||||
});
|
||||
|
||||
expect(prisma.emulatorSettings.upsert).toHaveBeenCalledTimes(2);
|
||||
expect(rcon.updateConfig).toHaveBeenCalled();
|
||||
expect(result).toBe("ok");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user