diff --git a/src/lib/google/gmail.test.ts b/src/lib/google/gmail.test.ts new file mode 100644 index 00000000..8bb85a5c --- /dev/null +++ b/src/lib/google/gmail.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("./account", () => ({ googleFetch: vi.fn() })); + +import { readMessage } from "./gmail"; + +const message = (overrides: { internalDate?: string; date?: string } = {}) => ({ + id: "message-id", + threadId: "thread-id", + ...(overrides.internalDate === undefined ? {} : { internalDate: overrides.internalDate }), + payload: { + mimeType: "text/plain", + headers: overrides.date === undefined ? [] : [{ name: "Date", value: overrides.date }], + body: { data: Buffer.from("Message body").toString("base64url") }, + }, +}); + +describe("readMessage", () => { + it("uses the current time when internalDate and the Date header are missing", () => { + const before = Date.now(); + const result = readMessage(message()); + const after = Date.now(); + + expect(result.date.getTime()).toBeGreaterThanOrEqual(before); + expect(result.date.getTime()).toBeLessThanOrEqual(after); + }); + + it("uses the Date header when internalDate is malformed", () => { + const result = readMessage({ ...message({ internalDate: " 1793253600000 ", date: "Thu, 29 Oct 2026 06:00:00 GMT" }) }); + + expect(result.date.toISOString()).toBe("2026-10-29T06:00:00.000Z"); + }); + + it("uses the internal millisecond timestamp when it is valid", () => { + const result = readMessage(message({ internalDate: "1793253600000", date: "Thu, 29 Oct 2026 06:00:00 GMT" })); + + expect(result.date.toISOString()).toBe("2026-10-29T06:00:00.000Z"); + }); +}); diff --git a/src/lib/google/gmail.ts b/src/lib/google/gmail.ts index 39fc891d..c659eb5c 100644 --- a/src/lib/google/gmail.ts +++ b/src/lib/google/gmail.ts @@ -85,7 +85,10 @@ export function readMessage(json: unknown): ReadMail { const html = plain ? null : findPart(payload, "text/html"); const text = plain ? decodeBody(plain.body!.data!) : html ? htmlToText(decodeBody(html.body!.data!)) : message.snippet; const dateHeader = header(payload, "Date"); - const date = message.internalDate ? new Date(Number(message.internalDate)) : new Date(dateHeader); + const internalDate = message.internalDate; + const parsedDate = + internalDate && /^\d+$/.test(internalDate) ? new Date(Number(internalDate)) : new Date(dateHeader); + const date = Number.isNaN(parsedDate.getTime()) ? new Date() : parsedDate; return { id: message.id, threadId: message.threadId, @@ -95,7 +98,7 @@ export function readMessage(json: unknown): ReadMail { subject: decodeHeader(header(payload, "Subject")), messageId: header(payload, "Message-ID") || header(payload, "Message-Id"), references: header(payload, "References"), - date: Number.isNaN(date.getTime()) ? new Date() : date, + date, text, }; }