Match no-reply@flip.id transfer success mail (direct or forwarded), parse stacked Nominal/ID Transaksi fields, and map spendings with source=flip. Co-authored-by: Cursor <cursoragent@cursor.com>
267 lines
7.8 KiB
JavaScript
267 lines
7.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import {
|
|
extractEmail,
|
|
parseForwardedHeader,
|
|
resolveOriginal,
|
|
subjectMatches,
|
|
} from "../src/lib/email.js";
|
|
import { matchRule } from "../src/handlers/match-rule.js";
|
|
import {
|
|
briQrisDirectBody,
|
|
briQrisForwardedBody,
|
|
briTransferDirectBody,
|
|
briTransferForwardedBody,
|
|
flipDirectBody,
|
|
flipForwardedBody,
|
|
grabBody,
|
|
grabForwardedBody,
|
|
livinDirectBody,
|
|
livinForwardedBody,
|
|
tokopediaMultiForwardedBody,
|
|
tokopediaSingleForwardedBody,
|
|
} from "./fixtures.js";
|
|
|
|
const silentLog = {
|
|
info() {},
|
|
warn() {},
|
|
error() {},
|
|
};
|
|
|
|
describe("extractEmail", () => {
|
|
it("parses angle-bracket addresses", () => {
|
|
assert.equal(
|
|
extractEmail(`"Livin'" <noreply.livin@bankmandiri.co.id>`),
|
|
"noreply.livin@bankmandiri.co.id",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("parseForwardedHeader", () => {
|
|
it("reads original From/Subject from a Fwd block", () => {
|
|
const h = parseForwardedHeader(livinForwardedBody);
|
|
assert.ok(h);
|
|
assert.match(h.from, /noreply\.livin@bankmandiri\.co\.id/);
|
|
assert.equal(h.subject, "Pembayaran Berhasil!");
|
|
});
|
|
|
|
it("joins wrapped Subject lines before To", () => {
|
|
const h = parseForwardedHeader(tokopediaMultiForwardedBody);
|
|
assert.ok(h);
|
|
assert.match(h.from, /noreply@tokopedia\.com/);
|
|
assert.equal(
|
|
h.subject,
|
|
"Pesanan Selesai: “sandal jepit ANDA pria casual...” dan 1 Item Lainnya",
|
|
);
|
|
});
|
|
|
|
it("returns null when there is no Fwd block", () => {
|
|
assert.equal(parseForwardedHeader(livinDirectBody), null);
|
|
});
|
|
});
|
|
|
|
describe("resolveOriginal", () => {
|
|
it("uses envelope for direct mail", () => {
|
|
const o = resolveOriginal({
|
|
from: `"Livin'" <noreply.livin@bankmandiri.co.id>`,
|
|
subject: "Pembayaran Berhasil!",
|
|
date: "Sun, 13 Sep 2026 14:48:36 +0700 (WIB)",
|
|
body: livinDirectBody,
|
|
});
|
|
assert.equal(o.viaForward, false);
|
|
assert.equal(extractEmail(o.from), "noreply.livin@bankmandiri.co.id");
|
|
});
|
|
|
|
it("uses Fwd header for forwarded mail", () => {
|
|
const o = resolveOriginal({
|
|
from: "NSRB <nasyarobby@gmail.com>",
|
|
subject: "Fwd: Your Grab E-Receipt",
|
|
body: grabForwardedBody,
|
|
});
|
|
assert.equal(o.viaForward, true);
|
|
assert.equal(extractEmail(o.from), "no-reply@grab.com");
|
|
assert.equal(o.subject, "Your Grab E-Receipt");
|
|
});
|
|
});
|
|
|
|
describe("subjectMatches", () => {
|
|
it("matches exact and Fwd-prefixed subjects", () => {
|
|
assert.equal(subjectMatches("Pembayaran Berhasil!", "Pembayaran Berhasil!"), true);
|
|
assert.equal(subjectMatches("Fwd: Pembayaran Berhasil!", "Pembayaran Berhasil!"), true);
|
|
assert.equal(subjectMatches("Hello", "Pembayaran Berhasil!"), false);
|
|
});
|
|
});
|
|
|
|
describe("matchRule", () => {
|
|
it("matches direct Livin", async () => {
|
|
const result = await matchRule({
|
|
messageId: "1a099bd61efd234d",
|
|
log: silentLog,
|
|
message: {
|
|
from: `"Livin'" <noreply.livin@bankmandiri.co.id>`,
|
|
subject: "Pembayaran Berhasil!",
|
|
date: "Sun, 13 Sep 2026 14:48:36 +0700 (WIB)",
|
|
body: livinDirectBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "livin.qr_payment.success");
|
|
});
|
|
|
|
it("matches forwarded Livin via original header", async () => {
|
|
const result = await matchRule({
|
|
messageId: "1a099b0c04c4397a",
|
|
log: silentLog,
|
|
message: {
|
|
from: "NSRB <nasyarobby@gmail.com>",
|
|
subject: "Fwd: Pembayaran Berhasil!",
|
|
body: livinForwardedBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "livin.qr_payment.success");
|
|
});
|
|
|
|
it("matches direct Grab", async () => {
|
|
const result = await matchRule({
|
|
messageId: "grab-direct",
|
|
log: silentLog,
|
|
message: {
|
|
from: "Grab <no-reply@grab.com>",
|
|
subject: "Your Grab E-Receipt",
|
|
body: grabBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "grab.ereceipt.ride");
|
|
});
|
|
|
|
it("matches forwarded Grab via original header", async () => {
|
|
const result = await matchRule({
|
|
messageId: "1a099b8b9d239e5e",
|
|
log: silentLog,
|
|
message: {
|
|
from: "NSRB <nasyarobby@gmail.com>",
|
|
subject: "Fwd: Your Grab E-Receipt",
|
|
body: grabForwardedBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "grab.ereceipt.ride");
|
|
});
|
|
|
|
it("matches direct BRI transfer", async () => {
|
|
const result = await matchRule({
|
|
messageId: "bri-direct",
|
|
log: silentLog,
|
|
message: {
|
|
from: "Bank BRI <BankBRI@bri.co.id>",
|
|
subject: "Pemindahan Dana Sesama Rekening BRI",
|
|
body: briTransferDirectBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "bri.transfer.success");
|
|
});
|
|
|
|
it("matches forwarded BRI transfer via original header", async () => {
|
|
const result = await matchRule({
|
|
messageId: "1a09b6cd9a3bc6bd",
|
|
log: silentLog,
|
|
message: {
|
|
from: "Nasyarobby Putra <nsrb@bu.edu>",
|
|
subject: "Fwd: Pemindahan Dana Sesama Rekening BRI",
|
|
body: briTransferForwardedBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "bri.transfer.success");
|
|
});
|
|
|
|
it("matches direct BRI QRIS", async () => {
|
|
const result = await matchRule({
|
|
messageId: "bri-qris-direct",
|
|
log: silentLog,
|
|
message: {
|
|
from: "Bank BRI <BankBRI@bri.co.id>",
|
|
subject: "Pembelian QRIS Berhasil",
|
|
body: briQrisDirectBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "bri.qris.success");
|
|
});
|
|
|
|
it("matches forwarded BRI QRIS via original header", async () => {
|
|
const result = await matchRule({
|
|
messageId: "1a09b9031c233407",
|
|
log: silentLog,
|
|
message: {
|
|
from: "Nasyarobby Putra <nsrb@bu.edu>",
|
|
subject: "Fwd: Pembelian QRIS Berhasil",
|
|
body: briQrisForwardedBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "bri.qris.success");
|
|
});
|
|
|
|
it("matches forwarded Tokopedia Pesanan Selesai", async () => {
|
|
const result = await matchRule({
|
|
messageId: "1a09ba1248c7d2e8",
|
|
log: silentLog,
|
|
message: {
|
|
from: "NSRB <nasyarobby@gmail.com>",
|
|
subject: "Fwd: Pesanan Selesai: “Mouse Pad Besar Anime Style...”",
|
|
body: tokopediaSingleForwardedBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "tokopedia.order.completed");
|
|
});
|
|
|
|
it("matches multi-item Tokopedia subject via original header", async () => {
|
|
const result = await matchRule({
|
|
messageId: "1a09ba315b65f2a5",
|
|
log: silentLog,
|
|
message: {
|
|
from: "NSRB <nasyarobby@gmail.com>",
|
|
subject:
|
|
"Fwd: Pesanan Selesai: “sandal jepit ANDA pria casual...” dan 1 Item Lainnya",
|
|
body: tokopediaMultiForwardedBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "tokopedia.order.completed");
|
|
});
|
|
|
|
it("matches direct Flip transfer", async () => {
|
|
const result = await matchRule({
|
|
messageId: "flip-direct",
|
|
log: silentLog,
|
|
message: {
|
|
from: "Flip <no-reply@flip.id>",
|
|
subject: "Transfer ke Rwen Ibrahim berhasil",
|
|
body: flipDirectBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "flip.transfer.success");
|
|
});
|
|
|
|
it("matches forwarded Flip transfer via original header", async () => {
|
|
const result = await matchRule({
|
|
messageId: "1a0c704164e0cd54",
|
|
log: silentLog,
|
|
message: {
|
|
from: "NSRB <nasyarobby@gmail.com>",
|
|
subject: "Fwd: Transfer ke Rwen Ibrahim berhasil",
|
|
body: flipForwardedBody,
|
|
},
|
|
});
|
|
assert.equal(result.pass.rule.kind, "flip.transfer.success");
|
|
});
|
|
|
|
it("breaks on unknown from/subject", async () => {
|
|
const result = await matchRule({
|
|
messageId: "x",
|
|
log: silentLog,
|
|
message: {
|
|
from: "Someone <other@example.com>",
|
|
subject: "Hello",
|
|
body: "hi",
|
|
},
|
|
});
|
|
assert.equal(result.break, true);
|
|
});
|
|
});
|