mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 06:47:28 +00:00
Mount @logicsrc/plugin-agentmail in the CommandBoard API so members can list/read/search/send mail over the existing agentbbs Mailu server. - src/agentmail.ts: inject imapflow + nodemailer + mailparser drivers into the plugin's Mailu transport (kept out of the plugin by design); env-driven builder (AGENTMAIL_BACKEND=mailu → bbs Mailu, else in-memory singleton for dev/test). Handles STARTTLS cert-vs-loopback via AGENTMAIL_SMTP_TLS_SERVERNAME. - index.ts: register agentMailPlugin + 7 routes under /api/plugins/agentmail/* (mailboxes, list, read, search, send, PATCH flags, delete). Acting member from x-agentmail-member header (else AGENTMAIL_MEMBER), paid-gated; MailAccessError->402, DraftError->422. - 6 route tests (in-memory backend) + contract test plugin-id list updated. commandboard-api 20/20, builds clean. Note: run `npm install` to refresh the lockfile for the 3 new deps. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
79 lines
3 KiB
TypeScript
79 lines
3 KiB
TypeScript
import type { Server } from "node:http";
|
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
import { createCommandBoardServer } from "./index.js";
|
|
|
|
// These exercise the AgentMail routes against the default in-memory transport
|
|
// (no AGENTMAIL_BACKEND=mailu), so no live mail server is required.
|
|
let server: Server;
|
|
let baseUrl: string;
|
|
|
|
beforeAll(async () => {
|
|
server = createCommandBoardServer();
|
|
await new Promise<void>((resolve) => server.listen(0, resolve));
|
|
const address = server.address();
|
|
if (!address || typeof address === "string") {
|
|
throw new Error("Expected API server to bind to a local port");
|
|
}
|
|
baseUrl = `http://127.0.0.1:${address.port}`;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
|
});
|
|
});
|
|
|
|
describe("AgentMail API routes", () => {
|
|
it("lists mailboxes and exposes the member address", async () => {
|
|
const res = await fetch(`${baseUrl}/api/plugins/agentmail/mailboxes`);
|
|
expect(res.status).toBe(200);
|
|
const body = (await res.json()) as { address: string; mailboxes: unknown[] };
|
|
expect(body.address).toBe("chovy@bbs.profullstack.com");
|
|
expect(Array.isArray(body.mailboxes)).toBe(true);
|
|
});
|
|
|
|
it("registers agentmail in the plugin snapshot", async () => {
|
|
const res = await fetch(`${baseUrl}/api/plugins`);
|
|
const body = (await res.json()) as { plugins: { id: string }[] };
|
|
expect(body.plugins.some((p) => p.id === "agentmail")).toBe(true);
|
|
});
|
|
|
|
it("sends a draft and stores it in Sent", async () => {
|
|
const send = await fetch(`${baseUrl}/api/plugins/agentmail/messages`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ to: "qa@example.com", subject: "hi", text: "from a QA run" })
|
|
});
|
|
expect(send.status).toBe(201);
|
|
const result = (await send.json()) as { messageId: string };
|
|
expect(result.messageId).toMatch(/@/);
|
|
|
|
const sent = await fetch(`${baseUrl}/api/plugins/agentmail/mailboxes/Sent/messages`);
|
|
expect(sent.status).toBe(200);
|
|
const body = (await sent.json()) as { messages: { subject: string }[] };
|
|
expect(body.messages.some((m) => m.subject === "hi")).toBe(true);
|
|
});
|
|
|
|
it("accepts a 'Name <addr>' recipient string", async () => {
|
|
const send = await fetch(`${baseUrl}/api/plugins/agentmail/messages`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ to: "QA Bot <qa2@example.com>", subject: "named", text: "x" })
|
|
});
|
|
expect(send.status).toBe(201);
|
|
});
|
|
|
|
it("rejects a search without a query", async () => {
|
|
const res = await fetch(`${baseUrl}/api/plugins/agentmail/search`);
|
|
expect(res.status).toBe(422);
|
|
});
|
|
|
|
it("rejects a draft with no recipient", async () => {
|
|
const res = await fetch(`${baseUrl}/api/plugins/agentmail/messages`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ subject: "no recipients", text: "x" })
|
|
});
|
|
expect(res.status).toBe(422);
|
|
});
|
|
});
|