import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { resetEnvCache } from "@/lib/env"; import { createImapClientFromConfig, ImapClient, type ImapClientConfig, } from "@/services/imap/client"; import { getMockMailServer, resetMockMailServer, } from "@/services/imap/mock-mail-server"; const BASE_CONFIG: ImapClientConfig = { host: "mock.local", port: 993, user: "test@mock.local", pass: "test", connectTimeoutMs: 1000, readTimeoutMs: 1000, folder: "INBOX", }; function enableTestMode() { process.env.DATABASE_URL = process.env.DATABASE_URL || "mysql://app:app@localhost:7023/email_forecast"; process.env.TEST_MODE = "1"; resetEnvCache(); resetMockMailServer(); } function disableTestMode() { delete process.env.TEST_MODE; resetEnvCache(); resetMockMailServer(); } describe("MockMailServer", () => { beforeEach(() => { process.env.DATABASE_URL = process.env.DATABASE_URL || "mysql://app:app@localhost:7023/email_forecast"; disableTestMode(); }); afterEach(() => { disableTestMode(); }); it("injects eml with attachment and fetches by uid", async () => { const server = getMockMailServer(); const injected = server.inject({ from: "ops@example.com", subject: "预报 WHSU8127240", text: "请查收新增预报", attachments: [ { filename: "数据模版.xlsx", contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", content: Buffer.from("xlsx-bytes"), }, ], }); const client = server.createClient(BASE_CONFIG); await client.connect(); const uids = await client.searchSince(new Date(0)); expect(uids).toEqual([injected.uid]); const fetched = await client.fetchByUids(uids); expect(fetched).toHaveLength(1); expect(fetched[0].source.includes("数据模版.xlsx")).toBe(true); expect(fetched[0].envelope?.subject).toBe("预报 WHSU8127240"); await client.disconnect(); }); it("simulates auth failure", async () => { const server = getMockMailServer(); server.setFault("auth_fail"); const client = server.createClient(BASE_CONFIG); await expect(client.connect()).rejects.toThrow(/AUTHENTICATIONFAILED/); }); it("simulates connect timeout", async () => { const server = getMockMailServer(); server.setFault("timeout"); const client = server.createClient(BASE_CONFIG); await expect(client.connect()).rejects.toMatchObject({ code: "ETIMEOUT" }); }); it("simulates empty mailbox", async () => { const server = getMockMailServer(); server.inject({ subject: "hidden", text: "x" }); server.setFault("empty"); const client = server.createClient(BASE_CONFIG); await client.connect(); expect(await client.searchSince(new Date(0))).toEqual([]); expect(await client.countUnseen()).toBe(0); }); }); describe("IMAP factory TEST_MODE", () => { afterEach(() => { disableTestMode(); }); it("returns real ImapClient when TEST_MODE is off", () => { process.env.DATABASE_URL = process.env.DATABASE_URL || "mysql://app:app@localhost:7023/email_forecast"; delete process.env.TEST_MODE; resetEnvCache(); const client = createImapClientFromConfig(BASE_CONFIG); expect(client).toBeInstanceOf(ImapClient); }); it("returns MockMailServer client when TEST_MODE=1", async () => { enableTestMode(); getMockMailServer().inject({ subject: "fixture-a", text: "body" }); const client = createImapClientFromConfig(BASE_CONFIG); expect(client).not.toBeInstanceOf(ImapClient); await client.connect(); const uids = await client.searchSince(new Date(0)); expect(uids.length).toBe(1); }); });