120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
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<EndorserAlertSearchData> = {
|
|
outcome: "success",
|
|
pageCount: 1,
|
|
data: {
|
|
claims: [{ id: endorserUlid, issuedAt: "2026-09-05T00:00:00Z", issuer: USER }],
|
|
personalPlanContributions: [],
|
|
trackedPlanUpdates: [],
|
|
trackedPlanClaims: [],
|
|
plansNearby: [],
|
|
},
|
|
};
|
|
const partner: AlertSearchSourceResult<PartnerAlertSearchData> = {
|
|
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"), {});
|
|
});
|
|
});
|