Files
notification-wakeup-service/src/alertSearch/digest.test.ts
T

280 lines
9.5 KiB
TypeScript

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
ALERT_SEARCH_DIGEST_BUCKETS,
alertSearchDigestDebugSummary,
buildAlertSearchDigest,
} from "./digest.js";
import { emptyEndorserData, emptyPartnerData } from "./client.js";
import type { AlertSearchSourceResult, RetrieveAlertSearchResult } from "./retrieve.js";
import type {
AlertSearchClaimRecord,
AlertSearchFailureReason,
AlertSearchJwtWithClaimRecord,
AlertSearchPlanRecord,
AlertSearchProfileRecord,
AlertSearchQueryOutcome,
CombinedAlertSearchData,
EndorserAlertSearchData,
PartnerAlertSearchData,
} from "./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<EndorserAlertSearchData>>
): AlertSearchSourceResult<EndorserAlertSearchData> {
return { outcome, pageCount: 1, data, ...extra };
}
function partnerResult(
outcome: AlertSearchQueryOutcome,
data: PartnerAlertSearchData = emptyPartnerData(),
extra?: Partial<AlertSearchSourceResult<PartnerAlertSearchData>>
): AlertSearchSourceResult<PartnerAlertSearchData> {
return { outcome, pageCount: 1, data, ...extra };
}
function retrieved(
endorser: AlertSearchSourceResult<EndorserAlertSearchData>,
partner: AlertSearchSourceResult<PartnerAlertSearchData>
): 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);
});
});