Files
community-rule/tests/unit/ruleStakeholderInviteResendExpiredRoute.test.ts
T

113 lines
3.3 KiB
TypeScript

import { NextRequest } from "next/server";
import { beforeEach, describe, expect, it, vi } from "vitest";
const findUniqueMock = vi.fn();
const updateMock = vi.fn();
const sendInviteMock = vi.fn();
const getSessionPepperMock = vi.fn(() => "pepper");
const rateLimitKeyMock = vi.fn(
(
_key: string,
_minIntervalMs: number,
): { ok: true } | { ok: false; retryAfterMs: number } => ({ ok: true }),
);
vi.mock("../../lib/server/env", () => ({
isDatabaseConfigured: () => true,
getSessionPepper: () => getSessionPepperMock(),
}));
vi.mock("../../lib/server/db", () => ({
prisma: {
ruleStakeholder: {
findUnique: (...args: unknown[]) => findUniqueMock(...args),
update: (...args: unknown[]) => updateMock(...args),
},
},
}));
vi.mock("../../lib/server/hash", () => ({
hashSessionToken: (token: string) => `hash-${token}`,
newSessionToken: () => "new-token",
}));
vi.mock("../../lib/server/mail", () => ({
sendRuleStakeholderInviteEmail: (...args: unknown[]) =>
sendInviteMock(...args),
}));
vi.mock("../../lib/server/rateLimit", () => ({
rateLimitKey: (key: string, minIntervalMs: number) =>
rateLimitKeyMock(key, minIntervalMs),
}));
import { POST } from "../../app/api/invites/rule-stakeholder/resend/route";
beforeEach(() => {
findUniqueMock.mockReset();
updateMock.mockReset();
sendInviteMock.mockReset();
getSessionPepperMock.mockReset();
rateLimitKeyMock.mockReset();
getSessionPepperMock.mockReturnValue("pepper");
rateLimitKeyMock.mockReturnValue({ ok: true as const });
updateMock.mockResolvedValue({});
sendInviteMock.mockResolvedValue(undefined);
});
describe("POST /api/invites/rule-stakeholder/resend", () => {
it("returns ok without sending when the token does not match", async () => {
findUniqueMock.mockResolvedValueOnce(null);
const res = await POST(
new NextRequest("https://x.test/api/invites/rule-stakeholder/resend", {
method: "POST",
body: JSON.stringify({
token: "a".repeat(12),
email: "inv@example.com",
}),
headers: { "content-type": "application/json" },
}),
undefined,
);
expect(res.status).toBe(200);
expect(sendInviteMock).not.toHaveBeenCalled();
});
it("rotates the token and sends mail when email matches", async () => {
findUniqueMock.mockResolvedValueOnce({
id: "st-1",
email: "inv@example.com",
inviteTokenHash: "hash-old",
inviteExpiresAt: new Date(Date.now() - 60_000),
invitedBy: { email: "owner@example.com" },
rule: {
title: "Juniper Street Food Share",
summary: null,
document: {},
user: { email: "owner@example.com" },
},
});
const res = await POST(
new NextRequest("https://x.test/api/invites/rule-stakeholder/resend", {
method: "POST",
body: JSON.stringify({
token: "a".repeat(12),
email: "inv@example.com",
}),
headers: { "content-type": "application/json" },
}),
undefined,
);
expect(res.status).toBe(200);
expect(updateMock).toHaveBeenCalled();
expect(sendInviteMock).toHaveBeenCalledWith(
expect.objectContaining({
to: "inv@example.com",
communityName: "Juniper Street Food Share",
inviterEmail: "owner@example.com",
}),
);
});
});