SPB Git

spb/phi-conseil

Public
3.6 KB · 109 lines typescript
1import { beforeEach, describe, expect, it, vi } from "vitest";2import { POST } from "@/app/api/contact/route";3import { setMailerForTests, type Mailer } from "@/lib/mail";4import { resetEnvCache } from "@/lib/env";56const sent: unknown[] = [];7const fakeMailer: Mailer = {8  async send(data) {9    sent.push(data);10    return { delivered: true, id: "fake-id" };11  },12};1314function req(body: unknown, headers: Record<string, string> = {}) {15  return new Request("http://localhost/api/contact", {16    method: "POST",17    headers: {18      "content-type": "application/json",19      "x-forwarded-for": headers.ip ?? "198.51.100.1",20      ...headers,21    },22    body: typeof body === "string" ? body : JSON.stringify(body),23  });24}2526const valid = {27  name: "Jean Roy",28  email: "jean@exemple.com",29  organization: "OBNL",30  subject: "Reddition de comptes",31  message: "Nous devons produire un rapport final pour une subvention reçue cette année.",32  consent: true,33  website: "",34  startedAt: Date.now() - 10_000,35};3637describe("POST /api/contact", () => {38  beforeEach(() => {39    sent.length = 0;40    process.env.CONTACT_TRANSPORT = "log";41    process.env.CONTACT_RATE_LIMIT = "100";42    resetEnvCache();43    setMailerForTests(fakeMailer);44    vi.spyOn(console, "info").mockImplementation(() => {});45  });4647  it("accepte une soumission valide et appelle le mailer", async () => {48    const res = await POST(req({ ...valid, message: valid.message + " A" }));49    expect(res.status).toBe(200);50    expect(await res.json()).toMatchObject({ ok: true, delivered: true });51    expect(sent).toHaveLength(1);52  });5354  it("renvoie 400 avec les champs fautifs", async () => {55    const res = await POST(req({ ...valid, email: "pas-un-courriel", message: "B" }));56    expect(res.status).toBe(400);57    const body = await res.json();58    expect(body.ok).toBe(false);59    expect(Object.keys(body.fields)).toEqual(expect.arrayContaining(["email", "message"]));60    expect(sent).toHaveLength(0);61  });6263  it("répond succès silencieux au honeypot sans envoyer", async () => {64    const res = await POST(req({ ...valid, website: "http://bot", message: valid.message + " C" }));65    expect(res.status).toBe(200);66    expect(await res.json()).toMatchObject({ ok: true, delivered: false });67    expect(sent).toHaveLength(0);68  });6970  it("ignore une soumission instantanée (robot)", async () => {71    const res = await POST(req({ ...valid, startedAt: Date.now(), message: valid.message + " D" }));72    expect(await res.json()).toMatchObject({ ok: true, delivered: false });73    expect(sent).toHaveLength(0);74  });7576  it("refuse un JSON invalide et un mauvais content-type", async () => {77    expect((await POST(req("{oops"))).status).toBe(400);78    const r = await POST(79      new Request("http://localhost/api/contact", {80        method: "POST",81        headers: { "content-type": "text/plain" },82        body: "x",83      }),84    );85    expect(r.status).toBe(415);86  });8788  it("dédoublonne un même message", async () => {89    const body = { ...valid, message: valid.message + " E" };90    await POST(req(body));91    const res = await POST(req(body));92    expect(await res.json()).toMatchObject({ ok: true, duplicate: true });93    expect(sent).toHaveLength(1);94  });9596  it("renvoie 502 si le mailer échoue, sans fuite de détail", async () => {97    vi.spyOn(console, "error").mockImplementation(() => {});98    setMailerForTests({99      async send() {100        throw new Error("SMTP down: password=secret");101      },102    });103    const res = await POST(req({ ...valid, message: valid.message + " F" }));104    expect(res.status).toBe(502);105    const txt = JSON.stringify(await res.json());106    expect(txt).not.toContain("secret");107  });108});109
Files · 18a9e1379a7e4662b184e2c070c92578c5de3ea8
Menu

Theme