test: align remaining action tests with Drizzle mocks

EOF

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-08-01 13:17:35 +02:00
1 parent ed9c23c702
commit 096f55b394
4 files changed
+156 -112

No files matched your search

+49 -30
View File
@@ -2,64 +2,83 @@
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";
const { selectLimit, insertValues } = vi.hoisted(() => {
const selectLimit = vi.fn();
const insertValues = vi.fn().mockResolvedValue([{ insertId: 1 }]);
return { selectLimit, insertValues };
});
vi.mock("@/lib/auth", () => ({ auth: vi.fn() }));
vi.mock("@/lib/prisma", () => ({
prisma: { websiteStaffApplications: { findFirst: vi.fn(), create: vi.fn() } },
vi.mock("@/lib/db", () => ({
db: {
select: vi.fn(() => ({
from: vi.fn(() => ({
where: vi.fn(() => ({
limit: selectLimit,
})),
})),
})),
insert: vi.fn(() => ({ values: insertValues })),
},
WebsiteStaffApplications: {
id: "id",
userId: "userId",
rankId: "rankId",
},
}));
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,
const fakeForm = (data) => ({
get: (key) => data[key] ?? null,
});
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(auth).mockResolvedValue({ user: { id: "42" } } as never);
vi.mocked(auth).mockResolvedValue({ user: { id: "42" } });
vi.mocked(clientIp).mockResolvedValue("127.0.0.1");
vi.mocked(rateLimit).mockResolvedValue({ ok: true });
selectLimit.mockResolvedValue([]);
insertValues.mockResolvedValue([{ insertId: 1 }]);
});
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(insertValues).toHaveBeenCalledWith(
expect.objectContaining({
userId: 42,
rankId: 3,
content: "I want to help!",
}),
);
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);
await applyStaff(fakeForm({}));
expect(redirect).toHaveBeenCalledWith("/login");
});
it("returns duplicate status when application exists", async () => {
vi.mocked(prisma.websiteStaffApplications.findFirst).mockResolvedValue({
id: 1,
} as never);
selectLimit.mockResolvedValue([{ id: 1 }]);
await applyStaff(
fakeForm({
rankId: "3",
content: "I want to help!",
}) as unknown as FormData,
}),
);
expect(prisma.websiteStaffApplications.create).not.toHaveBeenCalled();
expect(insertValues).not.toHaveBeenCalled();
expect(redirect).toHaveBeenCalledWith("/apply/staff?error=duplicate");
});
@@ -69,7 +88,7 @@ describe("applyStaff", () => {
fakeForm({
rankId: "3",
content: "I want to help!",
}) as unknown as FormData,
}),
);
expect(redirect).toHaveBeenCalledWith("/apply/staff?error=ratelimit");
});
@@ -77,25 +96,25 @@ describe("applyStaff", () => {
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(insertValues).toHaveBeenCalledWith(
expect.objectContaining({
userId: 42,
rankId: 2,
content: "I want to join team!",
}),
);
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);
await applyTeam(fakeForm({}));
expect(redirect).toHaveBeenCalledWith("/login");
});
});
+57 -27
View File
@@ -1,15 +1,36 @@
// @ts-nocheck
import { beforeEach, describe, expect, it, vi } from "vitest";
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";
const { selectLimit } = vi.hoisted(() => {
const selectLimit = vi.fn().mockResolvedValue([]);
return { selectLimit };
});
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/db", () => ({
db: {
select: vi.fn(() => ({
from: vi.fn(() => ({
where: vi.fn(() => ({
limit: selectLimit,
})),
})),
})),
},
User: {
password: "password",
twoFactorConfirmedAt: "twoFactorConfirmedAt",
mail: "mail",
mailVerified: "mailVerified",
username: "username",
},
}));
vi.mock("@/lib/rate-limit", () => ({ clientIp: vi.fn(), rateLimit: vi.fn() }));
vi.mock("@/lib/services/captcha", () => ({
captchaConfig: vi.fn(),
@@ -25,26 +46,31 @@ beforeEach(() => {
vi.mocked(rateLimit).mockResolvedValue({ ok: true });
vi.mocked(checkLogin).mockResolvedValue({ valid: true } as never);
vi.mocked(captchaConfig).mockResolvedValue({ provider: "none" } as never);
selectLimit.mockResolvedValue([]);
});
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);
selectLimit.mockResolvedValue([
{
password: "hash",
twoFactorConfirmedAt: null,
mail: null,
mailVerified: "0",
},
]);
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);
selectLimit.mockResolvedValue([
{
password: "hash",
twoFactorConfirmedAt: new Date(),
mail: null,
mailVerified: "0",
},
]);
expect(await precheckLogin("user", "pass")).toBe("twofactor");
});
@@ -57,29 +83,33 @@ describe("precheckLogin", () => {
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);
selectLimit.mockResolvedValue([
{
password: "hash",
twoFactorConfirmedAt: null,
mail: null,
mailVerified: "0",
},
]);
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);
selectLimit.mockResolvedValue([]);
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);
selectLimit.mockResolvedValue([
{
password: "hash",
twoFactorConfirmedAt: null,
mail: "[email protected]",
mailVerified: "0",
},
]);
vi.mocked(siteSettings.getBool).mockResolvedValue(true);
expect(await precheckLogin("user", "pass")).toBe("unverified");
});
+31 -34
View File
@@ -1,39 +1,38 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const {
mockFindFirst,
mockUpsert,
mockFindUnique,
mockUpdate,
mockDelete,
mockSendMail,
mockRedirect,
} = vi.hoisted(() => ({
mockFindFirst: vi.fn(),
mockUpsert: vi.fn(),
mockFindUnique: vi.fn(),
mockUpdate: vi.fn(),
mockDelete: vi.fn(),
mockSendMail: vi.fn(),
mockRedirect: vi.fn(),
}));
const { selectLimit, insertOnDup, mockSendMail, mockRedirect } = vi.hoisted(
() => ({
selectLimit: vi.fn(),
insertOnDup: vi.fn().mockResolvedValue({}),
mockSendMail: vi.fn(),
mockRedirect: vi.fn(),
}),
);
vi.mock("next/navigation", () => ({
redirect: (...args: unknown[]) => {
redirect: (...args) => {
mockRedirect(...args);
throw new Error("redirect");
},
}));
vi.mock("@/lib/prisma", () => ({
prisma: {
user: { findFirst: mockFindFirst, update: mockUpdate },
passwordReset: {
upsert: mockUpsert,
findUnique: mockFindUnique,
delete: mockDelete,
},
vi.mock("@/lib/db", () => ({
db: {
select: vi.fn(() => ({
from: vi.fn(() => ({
where: vi.fn(() => ({
limit: selectLimit,
})),
})),
})),
insert: vi.fn(() => ({
values: vi.fn(() => ({
onDuplicateKeyUpdate: insertOnDup,
})),
})),
},
User: { id: "id", mail: "mail" },
PasswordReset: { email: "email", token: "token", createdAt: "createdAt" },
}));
vi.mock("@/lib/services/email", () => ({
@@ -62,22 +61,20 @@ import { requestReset } from "./password-reset";
beforeEach(() => {
vi.clearAllMocks();
insertOnDup.mockResolvedValue({});
});
describe("requestReset", () => {
it("sends a reset email when the user exists", async () => {
mockFindFirst.mockResolvedValue({ id: 1 });
mockUpsert.mockResolvedValue({});
selectLimit.mockResolvedValue([{ id: 1 }]);
const fd = new FormData();
fd.set("email", "[email protected]");
await expect(requestReset(fd)).rejects.toThrow("redirect");
expect(mockFindFirst).toHaveBeenCalledWith(
expect.objectContaining({ where: { mail: "[email protected]" } }),
);
expect(mockUpsert).toHaveBeenCalled();
expect(selectLimit).toHaveBeenCalled();
expect(insertOnDup).toHaveBeenCalled();
expect(mockSendMail).toHaveBeenCalledWith(
"[email protected]",
expect.stringContaining("password reset"),
@@ -86,7 +83,7 @@ describe("requestReset", () => {
});
it("does not send email when user is not found", async () => {
mockFindFirst.mockResolvedValue(null);
selectLimit.mockResolvedValue([]);
const fd = new FormData();
fd.set("email", "[email protected]");
@@ -100,6 +97,6 @@ describe("requestReset", () => {
fd.set("email", "not-an-email");
await expect(requestReset(fd)).rejects.toThrow("redirect");
expect(mockFindFirst).not.toHaveBeenCalled();
expect(selectLimit).not.toHaveBeenCalled();
});
});
+19 -21
View File
@@ -1,9 +1,12 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const create = vi.hoisted(() => vi.fn());
const insertValues = vi.hoisted(() => vi.fn());
vi.mock("@/lib/prisma", () => ({
prisma: { staffActivities: { create } },
vi.mock("@/lib/db", () => ({
db: {
insert: vi.fn(() => ({ values: insertValues })),
},
StaffActivities: {},
}));
vi.mock("@/env", () => ({ env: {} }));
@@ -12,8 +15,7 @@ vi.mock("next/headers", () => ({
headers: () =>
new Promise((resolve) =>
resolve({
get: (key: string) =>
key === "x-real-client-ip" ? "192.168.1.1" : null,
get: (key) => (key === "x-real-client-ip" ? "192.168.1.1" : null),
}),
),
}));
@@ -22,43 +24,39 @@ import { logStaffActivity } from "./staff-activity";
beforeEach(() => {
vi.clearAllMocks();
insertValues.mockResolvedValue([{ insertId: 1 }]);
});
describe("logStaffActivity", () => {
it("creates a staff activity entry with ip", async () => {
create.mockResolvedValue({ id: 1 });
await logStaffActivity({
staffId: 1,
action: "test_action",
description: "Test action performed",
});
expect(create).toHaveBeenCalledWith(
expect(insertValues).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
userId: BigInt(1),
action: "test_action",
description: "Test action performed",
ipAddress: "192.168.1.1",
}),
userId: BigInt(1),
action: "test_action",
description: "Test action performed",
ipAddress: "192.168.1.1",
}),
);
});
it("truncates action to 50 chars", async () => {
create.mockResolvedValue({ id: 1 });
const longAction = "a".repeat(100);
await logStaffActivity({
staffId: 1,
action: longAction,
description: "test",
});
const call = create.mock.calls[0][0];
expect(call.data.action.length).toBe(50);
expect(call.data.action).toBe("a".repeat(50));
const call = insertValues.mock.calls[0][0];
expect(call.action.length).toBe(50);
expect(call.action).toBe("a".repeat(50));
});
it("includes target type and target id when provided", async () => {
create.mockResolvedValue({ id: 1 });
await logStaffActivity({
staffId: 1,
action: "ban",
@@ -66,13 +64,13 @@ describe("logStaffActivity", () => {
targetType: "user",
targetId: 42,
});
const data = create.mock.calls[0][0].data;
const data = insertValues.mock.calls[0][0];
expect(data.targetType).toBe("user");
expect(data.targetId).toBe(BigInt(42));
});
it("does not throw on prisma error", async () => {
create.mockRejectedValue(new Error("DB down"));
it("does not throw on db error", async () => {
insertValues.mockRejectedValue(new Error("DB down"));
await expect(
logStaffActivity({
staffId: 1,