0ac4beaaa8
- Add requireAuth() to admin-permissions.ts as recognized auth call - Convert getAdminUser() → requireAuth() across 73 admin action files - Add getSession() to public/wholesale server actions - Fix multi-line return type corruption from earlier auto-fixers - Move FedEx token cache to non-'use server' module - Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD, EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS - Update Stripe API version 2026-05-27 → 2026-06-24 - Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient - Fix 51 TypeScript errors (return type corruption, missing imports)
135 lines
4.0 KiB
TypeScript
135 lines
4.0 KiB
TypeScript
/**
|
|
* Unit tests for the email service's structured `{ ok, error? }` return.
|
|
*
|
|
* The shared `sendEmail` helper is private, but `sendWelcomeEmail`
|
|
* calls it and is exported, so we exercise the happy path + the two
|
|
* new error paths (no API key, Resend 4xx/5xx with JSON body) through
|
|
* the public surface.
|
|
*/
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
|
|
const originalFetch = global.fetch;
|
|
const originalKey = process.env.RESEND_API_KEY;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
global.fetch = originalFetch;
|
|
if (originalKey === undefined) {
|
|
delete process.env.RESEND_API_KEY;
|
|
} else {
|
|
process.env.RESEND_API_KEY = originalKey;
|
|
}
|
|
});
|
|
|
|
async function importFresh() {
|
|
// Re-import the module after toggling RESEND_API_KEY / mocking fetch
|
|
// so the module-level constants pick up the new env.
|
|
vi.resetModules();
|
|
return await import("@/lib/email-service");
|
|
}
|
|
|
|
describe("sendWelcomeEmail — structured error reporting", () => {
|
|
it("returns ok=true when Resend accepts the message", async () => {
|
|
process.env.RESEND_API_KEY = "re_test_key";
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ id: "email-abc" }),
|
|
} as Response) as unknown as typeof fetch;
|
|
const { sendWelcomeEmail } = await importFresh();
|
|
const r = await sendWelcomeEmail({
|
|
to: "u@example.com",
|
|
name: "U",
|
|
role: "brand_admin",
|
|
brandName: "Tuxedo",
|
|
tempPassword: "TempPass123!",
|
|
});
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
it("returns a clear error when RESEND_API_KEY is missing", async () => {
|
|
delete process.env.RESEND_API_KEY;
|
|
const { sendWelcomeEmail } = await importFresh();
|
|
const r = await sendWelcomeEmail({
|
|
to: "u@example.com",
|
|
name: "U",
|
|
role: "brand_admin",
|
|
brandName: "Tuxedo",
|
|
tempPassword: "TempPass123!",
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
if (!r.ok) {
|
|
expect(r.error).toMatch(/RESEND_API_KEY is not set/i);
|
|
}
|
|
});
|
|
|
|
it("surfaces Resend's actual error message on 4xx/5xx", async () => {
|
|
process.env.RESEND_API_KEY = "re_test_key";
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 422,
|
|
json: async () => ({
|
|
name: "validation_error",
|
|
message: "The gmail.com domain is not verified",
|
|
}),
|
|
} as Response) as unknown as typeof fetch;
|
|
const { sendWelcomeEmail } = await importFresh();
|
|
const r = await sendWelcomeEmail({
|
|
to: "u@gmail.com",
|
|
name: "U",
|
|
role: "brand_admin",
|
|
brandName: "Tuxedo",
|
|
tempPassword: "TempPass123!",
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
if (!r.ok) {
|
|
expect(r.error).toContain("Resend 422");
|
|
expect(r.error).toContain("validation_error");
|
|
expect(r.error).toContain("gmail.com domain is not verified");
|
|
}
|
|
});
|
|
|
|
it("surfaces the Resend message even when the response body is not JSON", async () => {
|
|
process.env.RESEND_API_KEY = "re_test_key";
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 500,
|
|
json: async () => {
|
|
throw new Error("not json");
|
|
},
|
|
} as unknown as Response) as unknown as typeof fetch;
|
|
const { sendWelcomeEmail } = await importFresh();
|
|
const r = await sendWelcomeEmail({
|
|
to: "u@example.com",
|
|
name: "U",
|
|
role: "brand_admin",
|
|
brandName: "Tuxedo",
|
|
tempPassword: "TempPass123!",
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
if (!r.ok) {
|
|
expect(r.error).toBe("Resend 500");
|
|
}
|
|
});
|
|
|
|
it("surfaces network errors verbatim", async () => {
|
|
process.env.RESEND_API_KEY = "re_test_key";
|
|
global.fetch = vi.fn().mockRejectedValue(new Error("fetch failed: ECONNREFUSED")) as unknown as typeof fetch;
|
|
const { sendWelcomeEmail } = await importFresh();
|
|
const r = await sendWelcomeEmail({
|
|
to: "u@example.com",
|
|
name: "U",
|
|
role: "brand_admin",
|
|
brandName: "Tuxedo",
|
|
tempPassword: "TempPass123!",
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
if (!r.ok) {
|
|
expect(r.error).toBe("fetch failed: ECONNREFUSED");
|
|
}
|
|
});
|
|
});
|