PRA-only-demo/packages/rpa-core/src/capabilities/message-recv.test.ts
yu caa8e19332 阶段1-rpa-core建包: engine+31个纯Node能力迁入packages/rpa-core, 领域类型单一源化, createEngine工厂收拢模块级状态
- engine/(registry/types/runtime/expression)物理迁入包, 解耦shared/types(域类型归包, 宿主type-only re-export)
- 31个纯Node能力+27个测试迁入包; 宿主留守Electron/浏览器/AI依赖能力, index.ts经@rpa/core/capabilities拉起
- 新增createEngine()/defaultEngine: excel缓存/Excel句柄/db连接/FTP客户端/OCR实例/拟真设置挂引擎实例
- remaining-core子流程依赖改为host-bridge注入, 包内不再import宿主flows
- 宿主main/index.ts启动时注入子流程源; electron-vite/vitest/tsconfig配@rpa/core源码别名
- 验证: typecheck绿, 快照909个id diff为零, 332测试过(2个桌面冒烟因会话无交互SetForegroundWindow被拒, 环境阻塞)
2026-11-04 12:36:03 +08:00

68 lines
2.8 KiB
TypeScript

import net from "node:net";
import { afterEach, describe, expect, it } from "vitest";
import type { CatalogComponent, FlowNode } from "../types";
import type { RunContext } from "../engine/types";
import { getHandler, hasCapability } from "../engine/registry";
import "./message";
const context = {} as RunContext;
const component = {} as CatalogComponent;
const node = (id: string): FlowNode => ({ id, componentId: id, position: { x: 0, y: 0 }, values: {}, options: {} });
const ids = [
"Mail_FindMail", "Mail_FindMail_v1", "Mail_FindMail_v2",
"Mail_ReplyMail", "Mail_ReplyMail_v1", "Mail_ReplyMail_v2",
"Mail_DoGetMailMessage", "Mail_DoGetMailMessage_v1", "Mail_DoGetMailMessage_v2"
];
afterEach(async () => {});
describe("mail receive and reply", () => {
it("registers find, reply and Outlook message capabilities", () => {
for (const id of ids) expect(hasCapability(id), id).toBe(true);
});
it("replies through SMTP with the selected mail sender", async () => {
let received = "";
const server = net.createServer((socket) => {
socket.setEncoding("utf8");
socket.write("220 localhost ESMTP\r\n");
let buffer = "";
let dataMode = false;
socket.on("data", (chunk) => {
buffer += chunk;
const lines = buffer.split("\r\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (dataMode) {
if (line === ".") {
dataMode = false;
socket.write("250 queued\r\n");
} else received += line + "\n";
} else if (/^EHLO/i.test(line)) socket.write("250-localhost\r\n250 OK\r\n");
else if (/^MAIL FROM/i.test(line)) socket.write("250 ok\r\n");
else if (/^RCPT TO/i.test(line)) socket.write("250 ok\r\n");
else if (/^DATA/i.test(line)) { dataMode = true; socket.write("354 end with dot\r\n"); }
else if (/^QUIT/i.test(line)) { socket.write("221 bye\r\n"); socket.end(); }
else socket.write("250 OK\r\n");
}
});
});
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const port = (server.address() as net.AddressInfo).port;
try {
const handler = getHandler("Mail_ReplyMail_v2");
expect(handler).toBeTruthy();
const result = await handler!(context, node("Mail_ReplyMail_v2"), {
account_obj: { host: "127.0.0.1", port, account: "robot@example.com", secure: "False" },
mail_obj: { from: "owner@example.com", subject: "status", messageId: "<abc@example.com>" },
content: "reply delivered"
}, component);
expect(result).toMatchObject({ accepted: ["owner@example.com"] });
expect(received).toContain("reply delivered");
expect(received).toContain("Re: status");
} finally {
await new Promise<void>((resolve) => server.close(() => resolve()));
}
}, 30000);
});