import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { ALERT_SEARCH_DIGEST_BUCKETS, alertSearchDigestDebugSummary, buildAlertSearchDigest, } from "../../src/alertSearch/digest.js"; import { emptyEndorserData, emptyPartnerData } from "../../src/alertSearch/client.js"; import type { AlertSearchSourceResult, RetrieveAlertSearchResult } from "../../src/alertSearch/retrieve.js"; import type { AlertSearchClaimRecord, AlertSearchFailureReason, AlertSearchJwtWithClaimRecord, AlertSearchPlanRecord, AlertSearchProfileRecord, AlertSearchQueryOutcome, CombinedAlertSearchData, EndorserAlertSearchData, PartnerAlertSearchData, } from "../../src/alertSearch/types.js"; function ulid(n: number): string { return `01H${String(n).padStart(23, "0")}`; } function claim(n: number): AlertSearchClaimRecord { return { id: ulid(n), issuedAt: "2026-01-01T00:00:00Z", issuer: `did:ethr:0x${n}`, }; } function jwtClaim(n: number): AlertSearchJwtWithClaimRecord { return { ...claim(n), claim: `claim-${n}` }; } function plan(n: number): AlertSearchPlanRecord { return { handleId: `plan-${n}`, jwtId: ulid(n), name: `Plan ${n}` }; } function profile(n: number): AlertSearchProfileRecord { return { issuerDid: `did:ethr:0xp${n}`, description: `profile-${n}`, updatedAt: "2026-03-01T00:00:00.000Z", rowId: n, }; } function endorserResult( outcome: AlertSearchQueryOutcome, data: EndorserAlertSearchData = emptyEndorserData(), extra?: Partial> ): AlertSearchSourceResult { return { outcome, pageCount: 1, data, ...extra }; } function partnerResult( outcome: AlertSearchQueryOutcome, data: PartnerAlertSearchData = emptyPartnerData(), extra?: Partial> ): AlertSearchSourceResult { return { outcome, pageCount: 1, data, ...extra }; } function retrieved( endorser: AlertSearchSourceResult, partner: AlertSearchSourceResult ): RetrieveAlertSearchResult { const data: CombinedAlertSearchData = { ...endorser.data, ...partner.data, }; return { data, empty: false, endorser, partner }; } function populatedEndorser(): EndorserAlertSearchData { return { claims: [claim(1), claim(2), claim(3)], personalPlanContributions: [jwtClaim(10)], trackedPlanUpdates: [plan(20)], trackedPlanClaims: [jwtClaim(30), jwtClaim(31)], plansNearby: [plan(40)], }; } describe("buildAlertSearchDigest", () => { it("preserves all six buckets and their records when populated", () => { const endorserData = populatedEndorser(); const partnerData = { profilesNearby: [profile(1), profile(2)], }; const digest = buildAlertSearchDigest( retrieved( endorserResult("success", endorserData), partnerResult("success", partnerData) ) ); assert.equal(digest.completed, true); assert.equal(digest.hasUpdates, true); assert.equal(digest.counts.claims, 3); assert.equal(digest.counts.personalPlanContributions, 1); assert.equal(digest.counts.trackedPlanUpdates, 1); assert.equal(digest.counts.trackedPlanClaims, 2); assert.equal(digest.counts.plansNearby, 1); assert.equal(digest.counts.profilesNearby, 2); assert.equal(digest.totalCount, 10); assert.deepEqual(digest.records.claims, endorserData.claims); assert.deepEqual( digest.records.personalPlanContributions, endorserData.personalPlanContributions ); assert.deepEqual(digest.records.trackedPlanUpdates, endorserData.trackedPlanUpdates); assert.deepEqual(digest.records.trackedPlanClaims, endorserData.trackedPlanClaims); assert.deepEqual(digest.records.plansNearby, endorserData.plansNearby); assert.deepEqual(digest.records.profilesNearby, partnerData.profilesNearby); assert.equal(digest.records.claims[0], endorserData.claims[0]); assert.notEqual(digest.records.claims, endorserData.claims); }); it("supports a single populated bucket", () => { const claims = [claim(1), claim(2)]; const digest = buildAlertSearchDigest( retrieved( endorserResult("success", { ...emptyEndorserData(), claims }), partnerResult("empty") ) ); assert.equal(digest.completed, true); assert.equal(digest.hasUpdates, true); assert.equal(digest.totalCount, 2); assert.equal(digest.counts.claims, 2); for (const bucket of ALERT_SEARCH_DIGEST_BUCKETS) { if (bucket !== "claims") assert.equal(digest.counts[bucket], 0); } assert.equal(digest.records.claims.length, 2); assert.equal(digest.records.claims[1].id, ulid(2)); }); it("supports multiple populated buckets without dropping others", () => { const digest = buildAlertSearchDigest( retrieved( endorserResult("success", { ...emptyEndorserData(), claims: [claim(1), claim(2), claim(3)], trackedPlanUpdates: [plan(20)], }), partnerResult("success", { profilesNearby: [profile(1), profile(2)] }) ) ); assert.equal(digest.completed, true); assert.equal(digest.hasUpdates, true); assert.equal(digest.totalCount, 6); assert.equal(digest.counts.claims, 3); assert.equal(digest.counts.trackedPlanUpdates, 1); assert.equal(digest.counts.profilesNearby, 2); assert.equal(digest.records.claims.length, 3); assert.equal(digest.records.trackedPlanUpdates[0].handleId, "plan-20"); assert.equal(digest.records.profilesNearby[1].description, "profile-2"); }); it("treats a successful empty retrieve as complete with no updates", () => { const digest = buildAlertSearchDigest( retrieved(endorserResult("empty"), partnerResult("empty")) ); assert.equal(digest.completed, true); assert.equal(digest.hasUpdates, false); assert.equal(digest.totalCount, 0); for (const bucket of ALERT_SEARCH_DIGEST_BUCKETS) { assert.equal(digest.counts[bucket], 0); assert.equal(digest.records[bucket].length, 0); } assert.equal( alertSearchDigestDebugSummary(digest).includes("You have"), false ); }); it("does not mark a digest complete when Endorser succeeds and Partner fails", () => { const digest = buildAlertSearchDigest( retrieved( endorserResult("success", { ...emptyEndorserData(), claims: [claim(1)] }), partnerResult("auth", emptyPartnerData(), { reason: "auth", message: "HTTP 401", }) ) ); assert.equal(digest.completed, false); assert.equal(digest.hasUpdates, false); assert.equal(digest.endorser.completed, true); assert.equal(digest.endorser.outcome, "success"); assert.equal(digest.partner.completed, false); assert.equal(digest.partner.outcome, "auth"); assert.equal(digest.partner.reason, "auth"); assert.equal(digest.records.claims.length, 1); assert.equal(digest.totalCount, 1); }); it("does not mark a digest complete when Partner succeeds and Endorser fails", () => { const digest = buildAlertSearchDigest( retrieved( endorserResult("network", emptyEndorserData(), { reason: "network", message: "fetch failed", }), partnerResult("success", { profilesNearby: [profile(1)] }) ) ); assert.equal(digest.completed, false); assert.equal(digest.hasUpdates, false); assert.equal(digest.endorser.outcome, "network"); assert.equal(digest.partner.outcome, "success"); assert.equal(digest.records.profilesNearby.length, 1); }); it("does not treat pagination or other incomplete outcomes as a completed digest", () => { const incomplete: AlertSearchFailureReason[] = [ "pagination", "auth", "timeout", "network", "malformed", "http", ]; for (const outcome of incomplete) { const digest = buildAlertSearchDigest( retrieved( endorserResult(outcome, emptyEndorserData(), { reason: outcome }), partnerResult("empty") ) ); assert.equal(digest.completed, false, outcome); assert.equal(digest.hasUpdates, false, outcome); assert.equal(digest.endorser.outcome, outcome); } }); it("sums counts across all six buckets", () => { const digest = buildAlertSearchDigest( retrieved( endorserResult("success", populatedEndorser()), partnerResult("success", { profilesNearby: [profile(1), profile(2)] }) ) ); const sum = ALERT_SEARCH_DIGEST_BUCKETS.reduce( (n, bucket) => n + digest.counts[bucket], 0 ); assert.equal(digest.totalCount, sum); assert.equal(digest.totalCount, 10); }); it("preserves underlying records rather than reducing them to counts", () => { const extra = { claimType: "AgreeAction", subject: "did:ethr:0xabc" }; const row = { ...claim(7), ...extra }; const digest = buildAlertSearchDigest( retrieved( endorserResult("success", { ...emptyEndorserData(), claims: [row] }), partnerResult("empty") ) ); assert.equal(digest.counts.claims, 1); assert.equal(digest.records.claims[0].id, ulid(7)); assert.equal(digest.records.claims[0].claimType, "AgreeAction"); assert.equal(digest.records.claims[0].subject, "did:ethr:0xabc"); assert.deepEqual(digest.records.claims[0], row); }); it("is not notification-worthy when total count is zero", () => { const digest = buildAlertSearchDigest( retrieved(endorserResult("success"), partnerResult("success")) ); assert.equal(digest.completed, true); assert.equal(digest.totalCount, 0); assert.equal(digest.hasUpdates, false); }); });