Files
community-rule/tests/unit/organizerInquiryPostRoute.test.ts
T
2026-05-13 23:08:36 -06:00

136 lines
3.9 KiB
TypeScript

import { NextRequest } from "next/server";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const sendOrganizerInquiryNotificationMock = vi.fn();
vi.mock("../../lib/server/mail", () => ({
sendOrganizerInquiryNotification: (...args: unknown[]) =>
sendOrganizerInquiryNotificationMock(...args),
}));
const rateLimitKeyMock = vi.hoisted(() =>
vi.fn(
(_key: string, _minIntervalMs: number): { ok: true } | { ok: false; retryAfterMs: number } => ({
ok: true,
}),
),
);
vi.mock("../../lib/server/rateLimit", () => ({
rateLimitKey: (key: string, minIntervalMs: number) =>
rateLimitKeyMock(key, minIntervalMs),
}));
import { POST } from "../../app/api/organizer-inquiry/route";
describe("POST /api/organizer-inquiry", () => {
beforeEach(() => {
sendOrganizerInquiryNotificationMock.mockReset();
sendOrganizerInquiryNotificationMock.mockResolvedValue(undefined);
rateLimitKeyMock.mockReset();
rateLimitKeyMock.mockImplementation(() => ({ ok: true as const }));
process.env.ORGANIZER_INQUIRY_TO = "organizers@example.com";
});
afterEach(() => {
delete process.env.ORGANIZER_INQUIRY_TO;
});
it("returns 200 and sends mail for a valid payload", async () => {
const res = await POST(
new NextRequest("https://x.test/api/organizer-inquiry", {
method: "POST",
body: JSON.stringify({
email: "Visitor@Example.com",
message: "How do we run consensus meetings?",
company: "",
}),
}),
undefined,
);
expect(res.status).toBe(200);
const body = await res.json();
expect(body).toEqual({ ok: true });
expect(sendOrganizerInquiryNotificationMock).toHaveBeenCalledTimes(1);
const arg = sendOrganizerInquiryNotificationMock.mock.calls[0][0];
expect(arg.visitorEmail).toBe("visitor@example.com");
expect(arg.message).toBe("How do we run consensus meetings?");
});
it("returns 200 without sending mail when honeypot is filled", async () => {
const res = await POST(
new NextRequest("https://x.test/api/organizer-inquiry", {
method: "POST",
body: JSON.stringify({
email: "spam@example.com",
message: "How do we run consensus meetings?",
company: "Evil Corp",
}),
}),
undefined,
);
expect(res.status).toBe(200);
expect(sendOrganizerInquiryNotificationMock).not.toHaveBeenCalled();
});
it("returns 400 for invalid email", async () => {
const res = await POST(
new NextRequest("https://x.test/api/organizer-inquiry", {
method: "POST",
body: JSON.stringify({
email: "not-an-email",
message: "How do we run consensus meetings?",
company: "",
}),
}),
undefined,
);
expect(res.status).toBe(400);
expect(sendOrganizerInquiryNotificationMock).not.toHaveBeenCalled();
});
it("returns 429 when rate limited", async () => {
rateLimitKeyMock.mockReturnValue({
ok: false as const,
retryAfterMs: 1000,
});
const res = await POST(
new NextRequest("https://x.test/api/organizer-inquiry", {
method: "POST",
body: JSON.stringify({
email: "a@b.co",
message: "How do we run consensus meetings?",
company: "",
}),
}),
undefined,
);
expect(res.status).toBe(429);
expect(sendOrganizerInquiryNotificationMock).not.toHaveBeenCalled();
});
it("returns 500 when ORGANIZER_INQUIRY_TO is unset", async () => {
delete process.env.ORGANIZER_INQUIRY_TO;
const res = await POST(
new NextRequest("https://x.test/api/organizer-inquiry", {
method: "POST",
body: JSON.stringify({
email: "a@b.co",
message: "How do we run consensus meetings?",
company: "",
}),
}),
undefined,
);
expect(res.status).toBe(500);
expect(sendOrganizerInquiryNotificationMock).not.toHaveBeenCalled();
});
});