Implement parsing for Tokopedia "Pesanan Selesai" messages, including support for single and multi-item orders. Update rules to recognize Tokopedia order completion and enhance spending record mapping. Add tests to validate new functionality.
229 lines
6.6 KiB
JavaScript
229 lines
6.6 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,
|
|
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("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("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);
|
|
});
|
|
});
|