import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { expandWorkItems, listRfc822Parts, messageFromRfc822Part, } from "../src/mime.js"; import { processFetchedMessage } from "../src/process-fetched.js"; import { matchRule } from "../src/handlers/match-rule.js"; import { parseMatchedMessage } from "../src/handlers/parse-matched-message.js"; const silentLog = { info() {}, warn() {}, error() {}, }; function textPart(text, partId = "0.0") { return { partId, mimeType: "text/plain", body: { data: Buffer.from(text, "utf8") .toString("base64") .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""), }, }; } function htmlPart(html, partId = "0.1") { return { partId, mimeType: "text/html", body: { data: Buffer.from(html, "utf8") .toString("base64") .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""), }, }; } function rfc822Part({ partId, filename, from, subject, date, bodyText, bodyHtml, }) { return { partId, mimeType: "message/rfc822", filename, parts: [ { partId: `${partId}.0`, mimeType: "multipart/alternative", headers: [ { name: "From", value: from }, { name: "Subject", value: subject }, { name: "Date", value: date }, ], parts: [ textPart(bodyText, `${partId}.0.0`), ...(bodyHtml ? [htmlPart(bodyHtml, `${partId}.0.1`)] : []), ], }, ], }; } const grabRideBody = `Bike Hemat Hope you enjoyed your ride! Picked up on 13 September 2026 Booking ID: A-TESTBOOKING1 Total Paid Rp16.600 Receipt issued by the driver Mahendra. Passenger Bintang `; const grabFoodBody = `Selamat menikmati makanan Anda! TOTAL IDR 49000 TANGGAL | WAKTU 11 Sep 26 18:24 +0700 Detail Pesanan Jenis Kendaraan: GrabFood Diterbitkan untuk Bintang Pesanan ID 00112712674-TESTFOOD1 Pesanan Dari: Doner Kebab - Kalibata City Profil: Personal Detail Tagihan Detail Pembayaran: OVO Cash TOTAL (INCL. TAX) IDR 49000 `; describe("listRfc822Parts", () => { it("finds message/rfc822 attachments and does not treat outer as eml", () => { const payload = { mimeType: "multipart/mixed", parts: [ { mimeType: "multipart/alternative", parts: [textPart("wrapper")], }, rfc822Part({ partId: "1", filename: "Your Grab E-Receipt.eml", from: "Grab ", subject: "Your Grab E-Receipt", date: "Wed, 9 Sep 2026 06:09:32 +0000", bodyText: grabRideBody, }), rfc822Part({ partId: "2", filename: "Your Grab E-Receipt.eml", from: "Grab ", subject: "Your Grab E-Receipt", date: "Thu, 10 Sep 2026 11:28:24 +0000", bodyText: grabFoodBody, }), ], }; const parts = listRfc822Parts(payload); assert.equal(parts.length, 2); assert.equal(parts[0].filename, "Your Grab E-Receipt.eml"); }); }); describe("messageFromRfc822Part", () => { it("builds synthetic message with nested From/Subject/body", () => { const part = rfc822Part({ partId: "1", filename: "Your Grab E-Receipt.eml", from: "Grab ", subject: "Your Grab E-Receipt", date: "Wed, 9 Sep 2026 06:09:32 +0000", bodyText: grabRideBody, }); const msg = messageFromRfc822Part(part, { parentMessageId: "outer123", index: 0, }); assert.equal(msg.id, "outer123#1"); assert.equal(msg.from, "Grab "); assert.equal(msg.subject, "Your Grab E-Receipt"); assert.match(msg.body, /Booking ID: A-TESTBOOKING1/); assert.equal(msg.parentMessageId, "outer123"); assert.equal(msg.attachmentFilename, "Your Grab E-Receipt.eml"); }); }); describe("expandWorkItems", () => { it("returns outer only when no eml", () => { const outer = { id: "outer", from: "me@x.com", subject: "hi", body: "x", html: "", }; const payload = { mimeType: "multipart/alternative", parts: [textPart("hi")], }; const items = expandWorkItems(outer, payload); assert.equal(items.length, 1); assert.equal(items[0], outer); }); it("returns eml messages when attachments exist", () => { const outer = { id: "outer123", from: "NSRB ", subject: "Test attachment", body: "Regards", html: "", }; const payload = { mimeType: "multipart/mixed", parts: [ { mimeType: "multipart/alternative", parts: [textPart("Regards")] }, rfc822Part({ partId: "1", filename: "a.eml", from: "Grab ", subject: "Your Grab E-Receipt", date: "Wed, 9 Sep 2026 06:09:32 +0000", bodyText: grabRideBody, }), ], }; const items = expandWorkItems(outer, payload); assert.equal(items.length, 1); assert.equal(items[0].from, "Grab "); assert.equal(items[0].id, "outer123#1"); }); }); describe("processFetchedMessage fan-out", () => { it("processes each eml attachment and skips outer wrapper", async () => { const fetched = { notification: { emailAddress: "eleven16th@gmail.com" }, log: silentLog, message: { id: "outer123", from: "NSRB ", subject: "Test attachment", body: "Regards", html: "", }, emlMessages: [ { id: "outer123#1", from: "Grab ", subject: "Your Grab E-Receipt", date: "Wed, 9 Sep 2026 06:09:32 +0000", body: grabRideBody, html: "", parentMessageId: "outer123", }, { id: "outer123#2", from: "Grab ", subject: "Your Grab E-Receipt", date: "Thu, 10 Sep 2026 11:28:24 +0000", body: grabFoodBody, html: "", parentMessageId: "outer123", }, ], }; const results = await processFetchedMessage(fetched, [ matchRule, parseMatchedMessage, ]); assert.equal(results.length, 2); assert.equal(results[0].parsed?.kind, "grab.ereceipt.ride"); assert.equal(results[0].parsed?.reference, "A-TESTBOOKING1"); assert.equal(results[1].parsed?.kind, "grab.ereceipt.food"); assert.equal(results[1].parsed?.reference, "00112712674-TESTFOOD1"); }); it("processes outer message when no eml attachments", async () => { const fetched = { notification: { emailAddress: "eleven16th@gmail.com" }, log: silentLog, message: { id: "direct1", from: `"Livin'" `, subject: "Pembayaran Berhasil!", date: "Sun, 13 Sep 2026 14:48:36 +0700", body: `Halo TEST, Penerima Shop JAKARTA SELATAN - ID Tanggal 13 Sep 2026 Jam 10:00:00 WIB Nominal Transaksi Rp 10.000,00 No. Referensi 9999999999999999999`, html: "", }, emlMessages: [], }; const results = await processFetchedMessage(fetched, [ matchRule, parseMatchedMessage, ]); assert.equal(results.length, 1); assert.equal(results[0].parsed?.kind, "livin.qr_payment.success"); }); }); describe("matchRule on synthetic eml", () => { it("matches Grab from nested headers without Fwd", async () => { const result = await matchRule({ messageId: "outer#1", log: silentLog, message: { id: "outer#1", from: "Grab ", subject: "Your Grab E-Receipt", body: grabRideBody, html: "", }, }); assert.equal(result.pass.rule.kind, "grab.ereceipt.ride"); assert.equal(result.pass.original.from, "Grab "); }); }); describe("extractMimePartsFromRawEml", () => { it("pulls text/html from a simple multipart eml", async () => { const { extractMimePartsFromRawEml } = await import("../src/mime.js"); const raw = [ 'Content-Type: multipart/alternative; boundary="bnd"', "", "--bnd", "Content-Type: text/plain; charset=UTF-8", "", "Hello plain", "--bnd", "Content-Type: text/html; charset=UTF-8", "", "Hello html", "--bnd--", "", ].join("\r\n"); const parts = extractMimePartsFromRawEml(raw); assert.match(parts.text, /Hello plain/); assert.match(parts.html, /Hello html/); }); });