import assert from "node:assert/strict"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, it } from "node:test"; import { alertSearchCursorsDb, smsAlertSearchCursorsDb, } from "../../src/db/alertSearchCursorsSqlite.js"; import { closeDatabase } from "../../src/db/sqlite.js"; import { advanceAlertSearchCursors, loadAlertSearchCursors, } from "../../src/alertSearch/cursors.js"; import type { AlertSearchSourceResult, RetrieveAlertSearchResult } from "../../src/alertSearch/retrieve.js"; import type { EndorserAlertSearchData, PartnerAlertSearchData } from "../../src/alertSearch/types.js"; const USER = "did:ethr:0xchanneluser"; let dir: string; let savedDataDir: string | undefined; beforeEach(async () => { savedDataDir = process.env.NOTIFY_DATA_DIR; dir = await mkdtemp(path.join(tmpdir(), "sms-channel-")); process.env.NOTIFY_DATA_DIR = dir; closeDatabase(); }); afterEach(async () => { closeDatabase(); if (savedDataDir === undefined) delete process.env.NOTIFY_DATA_DIR; else process.env.NOTIFY_DATA_DIR = savedDataDir; await rm(dir, { recursive: true, force: true }); }); function ulid(n: number): string { return `01H${String(n).padStart(23, "0")}`; } function retrieved(endorserUlid: string, partnerAt: string): RetrieveAlertSearchResult { const endorser: AlertSearchSourceResult = { outcome: "success", pageCount: 1, data: { claims: [{ id: endorserUlid, issuedAt: "2026-09-05T00:00:00Z", issuer: USER }], personalPlanContributions: [], trackedPlanUpdates: [], trackedPlanClaims: [], plansNearby: [], }, }; const partner: AlertSearchSourceResult = { outcome: "success", pageCount: 1, data: { profilesNearby: [{ rowId: "r1", updatedAt: partnerAt } as never] }, }; return { data: { ...endorser.data, ...partner.data }, empty: false, endorser, partner, } as RetrieveAlertSearchResult; } describe("per-channel alertSearch cursors", () => { it("advances only the SMS table for an SMS run", async () => { await advanceAlertSearchCursors( USER, retrieved(ulid(5), "2026-09-05T00:00:00Z"), "sms" ); assert.equal( (await smsAlertSearchCursorsDb.get(USER))?.endorserAfterId, ulid(5) ); assert.equal(await alertSearchCursorsDb.get(USER), undefined); }); it("defaults to the FCM table when no channel is named", async () => { await advanceAlertSearchCursors( USER, retrieved(ulid(9), "2026-09-06T00:00:00Z") ); assert.equal( (await alertSearchCursorsDb.get(USER))?.endorserAfterId, ulid(9) ); assert.equal(await smsAlertSearchCursorsDb.get(USER), undefined); }); it("lets the two channels sit at different positions", async () => { await advanceAlertSearchCursors( USER, retrieved(ulid(1), "2026-09-01T00:00:00Z"), "fcm" ); await advanceAlertSearchCursors( USER, retrieved(ulid(2), "2026-09-02T00:00:00Z"), "sms" ); assert.deepEqual(await loadAlertSearchCursors(USER, "fcm"), { endorserAfterId: ulid(1), partnerAfterDate: "2026-09-01T00:00:00Z", }); assert.deepEqual(await loadAlertSearchCursors(USER, "sms"), { endorserAfterId: ulid(2), partnerAfterDate: "2026-09-02T00:00:00Z", }); }); it("reports an empty position for a channel that has never run", async () => { assert.deepEqual(await loadAlertSearchCursors(USER, "sms"), {}); }); });