Files
publisher-dashboard/packages/emails/src/emails/org-invite.test.ts
2026-01-12 17:12:06 +08:00

194 lines
5.8 KiB
TypeScript

import type { EmailClient } from "../types.js";
import { beforeEach, describe, expect, it, mock } from "bun:test";
import {
buildOrgInviteEmailHtml,
buildOrgInviteEmailText,
sendOrgInviteEmail,
} from "./org-invite.js";
describe("buildOrgInviteEmailHtml", () => {
const params = {
email: "user@example.com",
orgName: "Acme Corp",
inviterName: "John Doe",
role: "admin" as const,
inviteUrl: "https://example.com/invite/accept?token=invite123",
expiresIn: "7 days",
};
it("should include the invite URL", () => {
const html = buildOrgInviteEmailHtml(params);
expect(html).toContain(
'href="https://example.com/invite/accept?token=invite123"',
);
});
it("should include the organization name", () => {
const html = buildOrgInviteEmailHtml(params);
expect(html).toContain("Acme Corp");
});
it("should include the inviter name", () => {
const html = buildOrgInviteEmailHtml(params);
expect(html).toContain("John Doe");
});
it("should include the role with correct article", () => {
const html = buildOrgInviteEmailHtml(params);
expect(html).toContain("an <strong>Admin</strong>");
});
it("should include the expiry time", () => {
const html = buildOrgInviteEmailHtml(params);
expect(html).toContain("This invitation expires in 7 days.");
});
it("should include the recipient email", () => {
const html = buildOrgInviteEmailHtml(params);
expect(html).toContain("user@example.com");
});
it("should escape HTML in organization name", () => {
const paramsWithXss = {
...params,
orgName: '<script>alert("xss")</script>',
};
const html = buildOrgInviteEmailHtml(paramsWithXss);
expect(html).not.toContain("<script>");
expect(html).toContain("&lt;script&gt;");
});
it("should escape HTML in inviter name", () => {
const paramsWithXss = {
...params,
inviterName: '<img src="x" onerror="alert(1)">',
};
const html = buildOrgInviteEmailHtml(paramsWithXss);
expect(html).not.toContain("<img");
expect(html).toContain("&lt;img");
});
it("should use 'a' article for member role", () => {
const memberParams = { ...params, role: "member" as const };
const html = buildOrgInviteEmailHtml(memberParams);
expect(html).toContain("a <strong>Member</strong>");
});
it("should use 'an' article for owner role", () => {
const ownerParams = { ...params, role: "owner" as const };
const html = buildOrgInviteEmailHtml(ownerParams);
expect(html).toContain("an <strong>Owner</strong>");
});
});
describe("buildOrgInviteEmailText", () => {
const params = {
email: "user@example.com",
orgName: "Acme Corp",
inviterName: "John Doe",
role: "admin" as const,
inviteUrl: "https://example.com/invite/accept?token=invite123",
expiresIn: "7 days",
};
it("should include the invite URL", () => {
const text = buildOrgInviteEmailText(params);
expect(text).toContain("https://example.com/invite/accept?token=invite123");
});
it("should include the organization name", () => {
const text = buildOrgInviteEmailText(params);
expect(text).toContain("Acme Corp");
});
it("should include the inviter name", () => {
const text = buildOrgInviteEmailText(params);
expect(text).toContain("John Doe");
});
it("should include the role with correct article", () => {
const text = buildOrgInviteEmailText(params);
expect(text).toContain("an Admin");
});
it("should include the expiry time", () => {
const text = buildOrgInviteEmailText(params);
expect(text).toContain("This invitation expires in 7 days.");
});
});
describe("sendOrgInviteEmail", () => {
const createMockClient = () => {
const sendEmailMock = mock(() =>
Promise.resolve({ messageId: "test-message-id" }),
);
return {
sendEmail: sendEmailMock,
} as EmailClient & { sendEmail: ReturnType<typeof mock> };
};
let mockClient: ReturnType<typeof createMockClient>;
beforeEach(() => {
mockClient = createMockClient();
});
it("should send org invite email with correct details", async () => {
const result = await sendOrgInviteEmail({
client: mockClient,
fromAddress: "noreply@example.com",
baseUrl: "https://app.example.com",
email: "newuser@example.com",
token: "invite-token-abc",
orgName: "Acme Corp",
inviterName: "Jane Doe",
role: "admin",
expiryDays: 7,
});
expect(result.success).toBe(true);
expect(mockClient.sendEmail).toHaveBeenCalledTimes(1);
const call = mockClient.sendEmail.mock.calls[0];
const params = call?.[0] as {
from: string;
to: string;
subject: string;
htmlBody: string;
textBody: string;
};
expect(params.to).toBe("newuser@example.com");
expect(params.subject).toBe("You've been invited to join Acme Corp");
expect(params.htmlBody).toContain(
"https://app.example.com/invite/accept?token=invite-token-abc",
);
expect(params.htmlBody).toContain("Acme Corp");
expect(params.htmlBody).toContain("Jane Doe");
expect(params.htmlBody).toContain("Admin");
expect(params.htmlBody).toContain("7 days");
expect(params.textBody).toContain("Acme Corp");
expect(params.textBody).toContain("Jane Doe");
expect(params.textBody).toContain("an Admin");
});
it("should use correct article for member role", async () => {
await sendOrgInviteEmail({
client: mockClient,
fromAddress: "noreply@example.com",
baseUrl: "https://app.example.com",
email: "newuser@example.com",
token: "invite-token-abc",
orgName: "Acme Corp",
inviterName: "Jane Doe",
role: "member",
expiryDays: 7,
});
const call = mockClient.sendEmail.mock.calls[0];
const params = call?.[0] as { textBody: string };
expect(params.textBody).toContain("a Member");
});
});