// Mock dependencies jest.mock("../../../../../services/email/core/EmailClient", () => { return jest.fn().mockImplementation(() => ({ initialize: jest.fn().mockResolvedValue(), sendEmail: jest .fn() .mockResolvedValue({ success: true, messageId: "msg-123" }), })); }); jest.mock("../../../../../services/email/core/TemplateManager", () => { return jest.fn().mockImplementation(() => ({ initialize: jest.fn().mockResolvedValue(), renderTemplate: jest.fn().mockResolvedValue("Test"), })); }); jest.mock("../../../../../utils/logger", () => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), })); const UserEngagementEmailService = require("../../../../../services/email/domain/UserEngagementEmailService"); describe("UserEngagementEmailService", () => { let service; const originalEnv = process.env; beforeEach(() => { jest.clearAllMocks(); process.env = { ...originalEnv, FRONTEND_URL: "http://localhost:3000", CUSTOMER_SUPPORT_EMAIL: "support@email.com", }; service = new UserEngagementEmailService(); }); afterEach(() => { process.env = originalEnv; }); describe("initialize", () => { it("should initialize only once", async () => { await service.initialize(); await service.initialize(); expect(service.emailClient.initialize).toHaveBeenCalledTimes(1); expect(service.templateManager.initialize).toHaveBeenCalledTimes(1); }); }); describe("sendFirstListingCelebrationEmail", () => { const owner = { firstName: "John", email: "john@example.com" }; const item = { id: 123, name: "Power Drill" }; it("should send first listing celebration email with correct variables", async () => { const result = await service.sendFirstListingCelebrationEmail( owner, item, ); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( "firstListingCelebrationToOwner", expect.objectContaining({ ownerName: "John", itemName: "Power Drill", itemId: 123, viewItemUrl: "http://localhost:3000/items/123", }), ); expect(service.emailClient.sendEmail).toHaveBeenCalledWith( "john@example.com", "Congratulations! Your first item is live on Village Share", expect.any(String), ); }); it("should use default name when firstName is missing", async () => { const ownerNoName = { email: "john@example.com" }; await service.sendFirstListingCelebrationEmail(ownerNoName, item); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( "firstListingCelebrationToOwner", expect.objectContaining({ ownerName: "there" }), ); }); it("should handle errors gracefully", async () => { service.templateManager.renderTemplate.mockRejectedValueOnce( new Error("Template error"), ); const result = await service.sendFirstListingCelebrationEmail( owner, item, ); expect(result.success).toBe(false); expect(result.error).toBe("Template error"); }); }); describe("sendItemDeletionNotificationToOwner", () => { const owner = { firstName: "John", email: "john@example.com" }; const item = { id: 123, name: "Power Drill" }; const deletionReason = "Violated community guidelines"; it("should send item deletion notification with correct variables", async () => { const result = await service.sendItemDeletionNotificationToOwner( owner, item, deletionReason, ); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( "itemDeletionToOwner", expect.objectContaining({ ownerName: "John", itemName: "Power Drill", deletionReason: "Violated community guidelines", supportEmail: "support@villageshare.com", dashboardUrl: "http://localhost:3000/owning", }), ); expect(service.emailClient.sendEmail).toHaveBeenCalledWith( "john@example.com", 'Important: Your listing "Power Drill" has been removed', expect.any(String), ); }); it("should use default name when firstName is missing", async () => { const ownerNoName = { email: "john@example.com" }; await service.sendItemDeletionNotificationToOwner( ownerNoName, item, deletionReason, ); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( "itemDeletionToOwner", expect.objectContaining({ ownerName: "there" }), ); }); it("should handle errors gracefully", async () => { service.emailClient.sendEmail.mockRejectedValueOnce( new Error("Send error"), ); const result = await service.sendItemDeletionNotificationToOwner( owner, item, deletionReason, ); expect(result.success).toBe(false); expect(result.error).toBe("Send error"); }); }); describe("sendUserBannedNotification", () => { const bannedUser = { firstName: "John", email: "john@example.com" }; const admin = { firstName: "Admin", lastName: "User" }; const banReason = "Multiple policy violations"; it("should send user banned notification with correct variables", async () => { const result = await service.sendUserBannedNotification( bannedUser, admin, banReason, ); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( "userBannedNotification", expect.objectContaining({ userName: "John", banReason: "Multiple policy violations", supportEmail: "support@villageshare.com", }), ); expect(service.emailClient.sendEmail).toHaveBeenCalledWith( "john@example.com", "Important: Your Village Share Account Has Been Suspended", expect.any(String), ); }); it("should use default name when firstName is missing", async () => { const bannedUserNoName = { email: "john@example.com" }; await service.sendUserBannedNotification( bannedUserNoName, admin, banReason, ); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( "userBannedNotification", expect.objectContaining({ userName: "there" }), ); }); it("should handle errors gracefully", async () => { service.templateManager.renderTemplate.mockRejectedValueOnce( new Error("Template error"), ); const result = await service.sendUserBannedNotification( bannedUser, admin, banReason, ); expect(result.success).toBe(false); expect(result.error).toBe("Template error"); }); }); });