import { describe, expect, test } from "bun:test"; import { notifyReplyCommentCreated, type NotifyDeps, type ReceiverTarget } from "./index"; function createDeps() { const state = { sendMailCalled: false, }; const deps: NotifyDeps = { getParentReceiver: async (): Promise => ({ kind: "user", userId: 2 }), getGlobalConfig: async () => ({ enabled: true, fromEmail: "noreply@example.com", smtpHost: "smtp.example.com", smtpPort: 465, smtpSecure: true, smtpUser: "smtp-user", smtpPass: "smtp-pass", }), getReceiverNotifyEnabled: async () => true, getReceiverProfile: async () => ({ email: "receiver@example.com", username: "receiver", nickname: "Receiver", }), sendMail: async () => { state.sendMailCalled = true; }, }; return { state, deps, }; } describe("notifyReplyCommentCreated", () => { test("全局开关关闭 -> 不发送", async () => { const { deps, state } = createDeps(); deps.getGlobalConfig = async () => ({ enabled: false, fromEmail: "noreply@example.com", smtpHost: "smtp.example.com", smtpPort: 465, smtpSecure: true, smtpUser: "smtp-user", smtpPass: "smtp-pass", }); await notifyReplyCommentCreated( { postId: 10, postOwnerUserId: 2, commentId: 200, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps, ); expect(state.sendMailCalled).toBe(false); }); test("用户偏好关闭 -> 不发送", async () => { const { deps, state } = createDeps(); deps.getReceiverNotifyEnabled = async () => false; await notifyReplyCommentCreated( { postId: 10, postOwnerUserId: 2, commentId: 201, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps, ); expect(state.sendMailCalled).toBe(false); }); test("无 parentId -> 通知文章作者", async () => { const { deps, state } = createDeps(); await notifyReplyCommentCreated( { postId: 10, postOwnerUserId: 2, commentId: 202, parentId: null, actorUserId: 1, replyBody: "hello" }, deps, ); expect(state.sendMailCalled).toBe(true); }); test("自通知 -> 不发送", async () => { const { deps, state } = createDeps(); deps.getParentReceiver = async () => ({ kind: "user", userId: 1 }); await notifyReplyCommentCreated( { postId: 10, postOwnerUserId: 2, commentId: 203, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps, ); expect(state.sendMailCalled).toBe(false); }); test("发送异常 -> 不抛出(best-effort)", async () => { const { deps } = createDeps(); deps.sendMail = async () => { throw new Error("smtp send failed"); }; await expect( notifyReplyCommentCreated( { postId: 10, postOwnerUserId: 2, commentId: 204, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps, ), ).resolves.toBeUndefined(); }); test("接收方邮箱非法 -> 不发送", async () => { const { deps, state } = createDeps(); deps.getReceiverProfile = async () => ({ email: "not-an-email", username: "receiver", nickname: "Receiver", }); await notifyReplyCommentCreated( { postId: 10, postOwnerUserId: 2, commentId: 205, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps, ); expect(state.sendMailCalled).toBe(false); }); test("回复游客(有邮箱且非匿名)-> 发送邮件到游客邮箱", async () => { const { deps, state } = createDeps(); deps.getParentReceiver = async () => ({ kind: "guest", email: "guest@example.com" }); await notifyReplyCommentCreated( { postId: 10, postOwnerUserId: 2, commentId: 206, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps, ); expect(state.sendMailCalled).toBe(true); }); test("游客回复自己(同邮箱)-> 不发送", async () => { const { deps, state } = createDeps(); deps.getParentReceiver = async () => ({ kind: "guest", email: "guest@example.com" }); await notifyReplyCommentCreated( { postId: 10, postOwnerUserId: 2, commentId: 207, parentId: 100, actorUserId: null, actorGuestEmail: "guest@example.com", replyBody: "hello", }, deps, ); expect(state.sendMailCalled).toBe(false); }); });