Menu
Public repository / 054e5cda0171bd13facff3c16a38f2048a1052cc
AkurAI-Mail / tools/akurai-mail-mcp.test.ts
Latest change 7a284f40b2637792d0599c38c4cac0342da676d7 - Merge live EC2 ops copies into scripts/ and tools/ by Ólafur Búi Ólafsson
// Integration tests for akurai-mail-mcp.ts: spawn the MCP server over line-delimited
// stdio against a fake server CLI that records its argv and returns canned JSON.
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
const MCP = join(import.meta.dir, "akurai-mail-mcp.ts");
let dir = "";
let cli = "";
let callsLog = "";
const FAKE_CLI = `#!/usr/bin/env bash
printf '%s\\n' "$*" >> "$CALLS_LOG"
case "$1" in
webmail-read) echo '{"ok":true,"message":{"from":"Spammer <Bad@Example.com>"}}' ;;
status) echo '{"ok":true,"users":[{"email":"a@olibuijr.com"},{"email":"b@olibuijr.com"}]}' ;;
*) echo '{"ok":true}' ;;
esac
`;
beforeAll(async () => {
const base = join(import.meta.dir, ".test-tmp");
await mkdir(base, { recursive: true });
dir = await mkdtemp(join(base, "mcp-"));
cli = join(dir, "fake-cli");
callsLog = join(dir, "calls.log");
await writeFile(cli, FAKE_CLI);
await chmod(cli, 0o755);
});
afterAll(async () => {
await rm(join(import.meta.dir, ".test-tmp"), { recursive: true, force: true });
});
async function callTool(name: string, args: Record<string, unknown>, scope = "admin"): Promise<{ result?: unknown; error?: string; calls: string[] }> {
await writeFile(callsLog, "");
const proc = Bun.spawn(["bun", "run", MCP], {
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, AKURAI_MAIL_SERVER_CLI: cli, AKURAI_MAIL_MCP_AUDIT_LOG: join(dir, "audit.jsonl"), AKURAI_MAIL_MCP_SCOPE: scope, CALLS_LOG: callsLog },
});
proc.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name, arguments: args } }) + "\n");
proc.stdin.end();
const out = await new Response(proc.stdout).text();
await proc.exited;
const reply = out.split("\n").filter((line) => line.startsWith("{")).map((line) => JSON.parse(line)).find((msg) => msg.id === 1);
const calls = (await readFile(callsLog, "utf8")).split("\n").filter(Boolean);
if (reply?.error) return { error: String(reply.error.message), calls };
return { result: JSON.parse(reply.result.content[0].text), calls };
}
describe("merged live admin tier", () => {
test("mail_accounts lists users from status", async () => {
const { result, calls } = await callTool("mail_accounts", {});
expect(result).toEqual({ ok: true, accounts: ["a@olibuijr.com", "b@olibuijr.com"] });
expect(calls).toEqual(["status"]);
});
test("admin tools are blocked below admin scope", async () => {
const { error, calls } = await callTool("mail_user_add", { email: "x@olibuijr.com" }, "send");
expect(error).toContain('requires "admin" scope');
expect(calls).toEqual([]);
});
});
describe("anti-spam / quarantine tools", () => {
test("policy get uses the spam-summary subcommand", async () => {
const { calls } = await callTool("mail_spam_policy_get", {});
expect(calls).toEqual(["spam-summary"]);
});
test("quarantine list reads the Spam folder", async () => {
const { calls } = await callTool("mail_quarantine_list", { account: "a@olibuijr.com", limit: 5 });
expect(calls).toEqual(["webmail-state a@olibuijr.com --folder Spam --limit 5 --cursor "]);
});
test("mutations require an approvalProof", async () => {
const { error, calls } = await callTool("mail_spam_scan", { approvalProof: "nope" }, "draft");
expect(error).toContain("approvalProof");
expect(calls).toEqual([]);
});
test("ALLOW is an accepted approvalProof", async () => {
const { calls } = await callTool("mail_spam_scan", { account: "a@olibuijr.com", approvalProof: "ALLOW" }, "draft");
expect(calls).toEqual(["anti-spam-scan --email a@olibuijr.com"]);
});
test("release moves to Inbox and allowlists the sender", async () => {
const { result, calls } = await callTool("mail_quarantine_release", { account: "a@olibuijr.com", messageIds: ["m1"], allowSender: true, reason: "legit", approvalProof: "ALLOW" }, "draft");
expect(calls[0]).toBe("webmail-read a@olibuijr.com m1");
expect(calls[1]).toBe("webmail-action a@olibuijr.com m1 move --target Inbox");
expect(calls[2]).toStartWith("anti-spam-config ");
const payload = JSON.parse(calls[2]!.slice("anti-spam-config ".length));
expect(payload).toMatchObject({ field: "allowlist", mode: "add", values: ["bad@example.com"], disposition: "allow", reason: "legit" });
expect(result).toMatchObject({ ok: true, folder: "Inbox", senders: ["bad@example.com"] });
});
test("confirm with block blocklists the sender without moving", async () => {
const { calls } = await callTool("mail_quarantine_confirm", { account: "a@olibuijr.com", messageIds: ["m1"], block: true, reason: "spam", approvalProof: "ALLOW" }, "draft");
expect(calls.length).toBe(2);
expect(JSON.parse(calls[1]!.slice("anti-spam-config ".length))).toMatchObject({ field: "blocklist", values: ["bad@example.com"], disposition: "block" });
});
test("policy change maps domain kind to domainBlocklist with version guard", async () => {
const { calls } = await callTool("mail_spam_policy_change", { operation: "add", kind: "domain", value: "spam.example", disposition: "block", reason: "r", expectedVersion: 3, approvalProof: "ALLOW" }, "draft");
expect(JSON.parse(calls[0]!.slice("anti-spam-config ".length))).toMatchObject({ field: "domainBlocklist", mode: "add", values: ["spam.example"], expectedVersion: 3 });
});
});