import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { resolveUser } from "../src/cli/resolve-user.js"; import { parseArgv, parseIds, requireReason } from "../src/cli/parse-args.js"; import { archiveModifyBody, buildDraftRaw, buildForwardDraftRaw, labelModifyBody, rawToGmailMessage, } from "../src/gmail/draft.js"; function usersFixture() { return [ { id: 1, email: "a@example.com", is_default: 0 }, { id: 2, email: "b@example.com", is_default: 1 }, ]; } describe("resolveUser", () => { it("uses --user email", () => { const users = usersFixture(); const result = resolveUser("a@example.com", { listUsers: () => users, getByEmail: (e) => users.find((u) => u.email === e) || null, getById: (id) => users.find((u) => u.id === Number(id)) || null, getDefault: () => users.find((u) => u.is_default) || null, envDefault: "", }); assert.equal(result.ok, true); assert.equal(result.user.email, "a@example.com"); }); it("uses --user id", () => { const users = usersFixture(); const result = resolveUser("2", { listUsers: () => users, getByEmail: () => null, getById: (id) => users.find((u) => u.id === Number(id)) || null, getDefault: () => null, envDefault: "", }); assert.equal(result.ok, true); assert.equal(result.user.id, 2); }); it("uses GMAIL_DEFAULT_USER over is_default", () => { const users = usersFixture(); const result = resolveUser(null, { listUsers: () => users, getByEmail: (e) => users.find((u) => u.email === e) || null, getById: () => null, getDefault: () => users.find((u) => u.is_default) || null, envDefault: "a@example.com", }); assert.equal(result.ok, true); assert.equal(result.user.email, "a@example.com"); }); it("uses is_default when no flag/env", () => { const users = usersFixture(); const result = resolveUser(null, { listUsers: () => users, getByEmail: () => null, getById: () => null, getDefault: () => users.find((u) => u.is_default) || null, envDefault: "", }); assert.equal(result.ok, true); assert.equal(result.user.email, "b@example.com"); }); it("uses sole connected user", () => { const users = [{ id: 9, email: "only@example.com", is_default: 0 }]; const result = resolveUser(null, { listUsers: () => users, getByEmail: () => null, getById: () => null, getDefault: () => null, envDefault: "", }); assert.equal(result.ok, true); assert.equal(result.user.email, "only@example.com"); }); it("fails when multiple and no default", () => { const users = [ { id: 1, email: "a@example.com", is_default: 0 }, { id: 2, email: "b@example.com", is_default: 0 }, ]; const result = resolveUser(null, { listUsers: () => users, getByEmail: () => null, getById: () => null, getDefault: () => null, envDefault: "", }); assert.equal(result.ok, false); assert.match(result.error, /Multiple accounts/); }); it("fails when no users", () => { const result = resolveUser(null, { listUsers: () => [], getByEmail: () => null, getById: () => null, getDefault: () => null, envDefault: "", }); assert.equal(result.ok, false); assert.match(result.error, /No signed-in users/); }); }); describe("requireReason", () => { it("rejects missing reason", () => { const r = requireReason({ reason: " " }, "archive"); assert.equal(r.ok, false); assert.match(r.error, /--reason is required/); }); it("accepts non-empty reason", () => { const r = requireReason({ reason: "noise" }, "archive"); assert.equal(r.ok, true); assert.equal(r.reason, "noise"); }); }); describe("parseArgv / parseIds", () => { it("parses dry-run and multi ids", () => { const args = parseArgv( ["abc", "--dry-run", "--ids", "x,y", "--reason", "why"], { booleans: ["dryRun"], strings: ["reason"], multi: ["ids"], }, ); assert.equal(args.dryRun, true); assert.deepEqual(args.ids, ["x", "y"]); assert.equal(args.reason, "why"); assert.deepEqual(parseIds(args), ["x", "y", "abc"]); }); }); describe("draft MIME helpers", () => { it("buildDraftRaw includes headers and body", () => { const raw = buildDraftRaw({ to: "a@b.com", subject: "Hello", body: "Line1\nLine2", cc: "c@d.com", }); assert.match(raw, /^To: a@b.com\r\n/); assert.match(raw, /Cc: c@d.com/); assert.match(raw, /Subject: Hello/); assert.match(raw, /Line1\nLine2$/); }); it("buildForwardDraftRaw prefixes Fwd and quotes original", () => { const raw = buildForwardDraftRaw({ to: "acct@example.com", note: "Please book.", original: { from: "grab@example.com", to: "me@example.com", subject: "Your Grab E-Receipt", date: "Mon, 1 Jan 2026", body: "Total Rp10.000", }, }); assert.match(raw, /Subject: Fwd: Your Grab E-Receipt/); assert.match(raw, /Please book\./); assert.match(raw, /---------- Forwarded message ---------/); assert.match(raw, /From: grab@example.com/); assert.match(raw, /Total Rp10\.000/); }); it("does not double Fwd: prefix", () => { const raw = buildForwardDraftRaw({ to: "a@b.com", original: { subject: "Fwd: Already", body: "x" }, }); assert.match(raw, /Subject: Fwd: Already/); assert.doesNotMatch(raw, /Subject: Fwd: Fwd:/); }); it("rawToGmailMessage base64url-encodes", () => { const { raw } = rawToGmailMessage("hi+/=?"); assert.equal(raw.includes("+"), false); assert.equal(raw.includes("/"), false); assert.ok(raw.length > 0); }); it("archiveModifyBody removes INBOX only", () => { assert.deepEqual(archiveModifyBody(), { removeLabelIds: ["INBOX"] }); }); it("labelModifyBody adds and removes", () => { assert.deepEqual(labelModifyBody({ add: ["Label_1"], remove: ["INBOX"] }), { addLabelIds: ["Label_1"], removeLabelIds: ["INBOX"], }); assert.deepEqual(labelModifyBody({ add: ["A"] }), { addLabelIds: ["A"], }); }); });