You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
3.7 KiB

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);
});
});