import { describe, it, expect, vi, beforeEach } from "vitest"; import express from "express"; import request from "supertest"; let app: express.Express; beforeEach(async () => { vi.clearAllMocks(); vi.resetModules(); const { isEmailVerified, createPendingVerification, verifyCode } = await import("../services/verification.js"); const { sendVerificationEmail } = await import("../services/email.js"); const { createFreeKey } = await import("../services/keys.js"); vi.mocked(isEmailVerified).mockResolvedValue(false); vi.mocked(createPendingVerification).mockResolvedValue({ email: "test@test.com", code: "123456", createdAt: "", expiresAt: "", attempts: 0 }); vi.mocked(verifyCode).mockResolvedValue({ status: "ok" }); vi.mocked(createFreeKey).mockResolvedValue({ key: "free-key-123", tier: "free", email: "test@test.com", createdAt: "" }); vi.mocked(sendVerificationEmail).mockResolvedValue(true); const { signupRouter } = await import("../routes/signup.js"); app = express(); app.use(express.json()); app.use("/signup", signupRouter); }); describe("POST /signup/free", () => { it("returns 400 for missing email", async () => { const res = await request(app).post("/signup/free").send({}); expect(res.status).toBe(400); }); it("returns 400 for invalid email format", async () => { const res = await request(app).post("/signup/free").send({ email: "not-email" }); expect(res.status).toBe(400); }); it("returns 409 for already verified email", async () => { const { isEmailVerified } = await import("../services/verification.js"); vi.mocked(isEmailVerified).mockResolvedValue(true); const res = await request(app).post("/signup/free").send({ email: "dup@test.com" }); expect(res.status).toBe(409); }); it("returns 200 with verification_required for valid email", async () => { const res = await request(app).post("/signup/free").send({ email: "new@test.com" }); expect(res.status).toBe(200); expect(res.body.status).toBe("verification_required"); }); it("sends verification email asynchronously", async () => { const { sendVerificationEmail } = await import("../services/email.js"); await request(app).post("/signup/free").send({ email: "new@test.com" }); await new Promise(r => setTimeout(r, 50)); expect(sendVerificationEmail).toHaveBeenCalledWith("new@test.com", "123456"); }); }); describe("POST /signup/verify", () => { it("returns 400 for missing email/code", async () => { const res = await request(app).post("/signup/verify").send({ email: "a@b.com" }); expect(res.status).toBe(400); }); it("returns 409 for already verified email", async () => { const { isEmailVerified } = await import("../services/verification.js"); vi.mocked(isEmailVerified).mockResolvedValue(true); const res = await request(app).post("/signup/verify").send({ email: "dup@test.com", code: "123456" }); expect(res.status).toBe(409); }); it("returns 410 for expired code", async () => { const { verifyCode } = await import("../services/verification.js"); vi.mocked(verifyCode).mockResolvedValue({ status: "expired" }); const res = await request(app).post("/signup/verify").send({ email: "a@b.com", code: "123456" }); expect(res.status).toBe(410); }); it("returns 429 for max attempts", async () => { const { verifyCode } = await import("../services/verification.js"); vi.mocked(verifyCode).mockResolvedValue({ status: "max_attempts" }); const res = await request(app).post("/signup/verify").send({ email: "a@b.com", code: "123456" }); expect(res.status).toBe(429); }); it("returns 400 for invalid code", async () => { const { verifyCode } = await import("../services/verification.js"); vi.mocked(verifyCode).mockResolvedValue({ status: "invalid" }); const res = await request(app).post("/signup/verify").send({ email: "a@b.com", code: "999999" }); expect(res.status).toBe(400); }); it("returns 200 with apiKey for valid code", async () => { const res = await request(app).post("/signup/verify").send({ email: "a@b.com", code: "123456" }); expect(res.status).toBe(200); expect(res.body).toMatchObject({ status: "verified", apiKey: "free-key-123" }); }); });