alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
183 lines (154 loc) • 5.57 kB
text/typescript
import { Alepha } from "alepha";
import { describe, expect, it } from "vitest";
import {
type CloudflareEmailBinding,
CloudflareEmailProvider,
type CloudflareEmailSendMessage,
type CloudflareEmailSendResult,
SEND_EMAIL_DEFAULT_BINDING,
} from "../providers/CloudflareEmailProvider.ts";
// ---------------------------------------------------------------------------------------------------------------------
const EMAIL_FROM = "noreply@example.com";
class FakeBinding implements CloudflareEmailBinding {
public calls: CloudflareEmailSendMessage[] = [];
public response: CloudflareEmailSendResult = {
id: "msg-1",
status: "queued",
};
public error?: unknown;
public async send(
message: CloudflareEmailSendMessage,
): Promise<CloudflareEmailSendResult> {
this.calls.push(message);
if (this.error) {
throw this.error;
}
return this.response;
}
}
const setup = async (binding?: CloudflareEmailBinding | null) => {
const alepha = Alepha.create({ env: { EMAIL_FROM } });
if (binding !== null) {
alepha.set("cloudflare.env", {
[SEND_EMAIL_DEFAULT_BINDING]: binding ?? new FakeBinding(),
});
}
const provider = alepha.inject(CloudflareEmailProvider);
await alepha.start();
return { alepha, provider };
};
// ---------------------------------------------------------------------------------------------------------------------
describe("CloudflareEmailProvider", () => {
describe("send", () => {
it("should send email through the binding with mapped fields", async () => {
const binding = new FakeBinding();
const { provider } = await setup(binding);
await provider.send({
to: "user@example.com",
subject: "Welcome!",
body: "<p>Hello</p>",
});
expect(binding.calls).toHaveLength(1);
expect(binding.calls[0]).toEqual({
to: ["user@example.com"],
from: EMAIL_FROM,
subject: "Welcome!",
html: "<p>Hello</p>",
});
});
it("should split an `EMAIL_FROM` display name into { email, name }", async () => {
const binding = new FakeBinding();
const alepha = Alepha.create({
env: { EMAIL_FROM: "Lore <noreply@lore.alepha.dev>" },
});
alepha.set("cloudflare.env", {
[SEND_EMAIL_DEFAULT_BINDING]: binding,
});
const provider = alepha.inject(CloudflareEmailProvider);
await alepha.start();
await provider.send({
to: "user@example.com",
subject: "x",
body: "<p>x</p>",
});
expect(binding.calls[0].from).toEqual({
email: "noreply@lore.alepha.dev",
name: "Lore",
});
});
it("should pass a bare `EMAIL_FROM` address through as a string", async () => {
const binding = new FakeBinding();
const { provider } = await setup(binding);
await provider.send({
to: "user@example.com",
subject: "x",
body: "<p>x</p>",
});
expect(binding.calls[0].from).toBe(EMAIL_FROM);
});
it("should pass through multiple recipients", async () => {
const binding = new FakeBinding();
const { provider } = await setup(binding);
await provider.send({
to: ["a@example.com", "b@example.com"],
subject: "Broadcast",
body: "<p>hi</p>",
});
expect(binding.calls[0].to).toEqual(["a@example.com", "b@example.com"]);
});
it("should throw EmailError when the binding returns bounced", async () => {
const binding = new FakeBinding();
binding.response = { id: "msg-2", status: "bounced" };
const { provider } = await setup(binding);
await expect(
provider.send({
to: "user@example.com",
subject: "x",
body: "<p>x</p>",
}),
).rejects.toThrow("Cloudflare email bounced (id=msg-2)");
});
it("should wrap binding errors as EmailError", async () => {
const binding = new FakeBinding();
binding.error = new Error("boom");
const { provider } = await setup(binding);
await expect(
provider.send({
to: "user@example.com",
subject: "x",
body: "<p>x</p>",
}),
).rejects.toThrow("Failed to send email via Cloudflare: boom");
});
it("should surface 429 rate-limit errors with a dedicated message", async () => {
const binding = new FakeBinding();
const rateLimited = Object.assign(new Error("Too many requests"), {
status: 429,
});
binding.error = rateLimited;
const { provider } = await setup(binding);
await expect(
provider.send({
to: "user@example.com",
subject: "x",
body: "<p>x</p>",
}),
).rejects.toThrow("Cloudflare email rate limit hit (429)");
});
});
describe("start", () => {
it("should boot inert when no Cloudflare env is available (Node)", async () => {
const alepha = Alepha.create({ env: { EMAIL_FROM } });
alepha.inject(CloudflareEmailProvider);
// Must not throw — the provider has to be registerable off-Workers
// so the CF build task can see it and emit the wrangler binding.
await expect(alepha.start()).resolves.toBeDefined();
});
it("should boot inert when the SEND_EMAIL binding is missing", async () => {
const alepha = Alepha.create({ env: { EMAIL_FROM } });
alepha.set("cloudflare.env", {});
alepha.inject(CloudflareEmailProvider);
await expect(alepha.start()).resolves.toBeDefined();
});
});
});