Files
bitvid/tests/app-batch-fetch-profiles.test.mjs
T
thePR0M3TH3ANandClaude Opus 4.8 d693bf55aa refactor(profiles): batch profile fetch through SubscriptionManager (P4)
performProfileFetch now issues ONE deduped, health-gated list() across all
relays via the L1 SubscriptionManager (the pool merges per-relay results),
replacing the manual per-relay fan-out. Newest-per-author selection unchanged;
legacy per-relay path retained as the manager-less fallback.

Spec correction: tests/app-batch-fetch-profiles.test.mjs asserted one query
PER relay (implementation detail); updated to assert one BATCHED query across
all relays. All behavioral assertions (cached hydration, newest profile
rendered, failing relay tolerated) unchanged; added a relays-targeting
assertion. Documented in TEST_INTEGRITY.md (SCN-profiles-batched).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 16:15:06 -04:00

225 lines
6.4 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { nostrClient } from "../js/nostrClientFacade.js";
import { batchFetchProfilesFromRelays } from "../js/utils/profileBatchFetcher.js";
const FAST_RELAY = "wss://fast.example";
const FAIL_RELAY = "wss://fail.example";
const VALID_PUBKEY = "a".repeat(64);
const CACHED_PUBKEY = "b".repeat(64);
function createProfileEvent({ pubkey, createdAt, content }) {
return {
id: `${pubkey}:${createdAt}`,
pubkey,
created_at: createdAt,
content: JSON.stringify(content),
};
}
test("batchFetchProfiles handles fast and failing relays", async () => {
const cache = new Map();
const updates = [];
const setCalls = [];
const authorSet = new Set([
VALID_PUBKEY,
CACHED_PUBKEY,
"",
null,
undefined,
"npubInvalid",
]);
const getProfileCacheEntry = (pubkey) => {
if (pubkey === CACHED_PUBKEY) {
return { profile: { name: "Cached", picture: "cached.png" } };
}
return cache.get(pubkey) ?? null;
};
const setProfileCacheEntry = (pubkey, profile) => {
cache.set(pubkey, { profile });
setCalls.push({ pubkey, profile });
};
const updateProfileInDOM = (pubkey, profile) => {
updates.push({ pubkey, profile });
};
const originalRelays = Array.isArray(nostrClient.relays)
? [...nostrClient.relays]
: [];
const originalReadRelays = Array.isArray(nostrClient.readRelays)
? [...nostrClient.readRelays]
: nostrClient.readRelays;
const originalWriteRelays = Array.isArray(nostrClient.writeRelays)
? [...nostrClient.writeRelays]
: nostrClient.writeRelays;
const originalPool = nostrClient.pool;
const poolListCalls = [];
nostrClient.relays = [FAST_RELAY, FAIL_RELAY];
nostrClient.readRelays = [FAST_RELAY, FAIL_RELAY];
nostrClient.writeRelays = [FAST_RELAY, FAIL_RELAY];
nostrClient.readRelays = [FAST_RELAY, FAIL_RELAY];
nostrClient.pool = {
list: (relays, filters) => {
// console.log("pool.list called with relays:", relays);
poolListCalls.push({ relays, filters });
const relayUrl = Array.isArray(relays) ? relays[0] : undefined;
if (relayUrl === FAST_RELAY) {
return Promise.resolve([
createProfileEvent({
pubkey: VALID_PUBKEY,
createdAt: 200,
content: { name: "Fast", picture: "fast.png" },
}),
createProfileEvent({
pubkey: VALID_PUBKEY,
createdAt: 150,
content: { name: "Old", picture: "old.png" },
}),
]);
}
if (relayUrl === FAIL_RELAY) {
return Promise.reject(new Error("relay timed out"));
}
return Promise.resolve([]);
},
};
try {
await batchFetchProfilesFromRelays({
authorSet,
getProfileCacheEntry,
setProfileCacheEntry,
updateProfileInDOM,
});
} finally {
nostrClient.relays = originalRelays;
nostrClient.readRelays = originalReadRelays;
nostrClient.writeRelays = originalWriteRelays;
nostrClient.readRelays = originalReadRelays;
nostrClient.pool = originalPool;
}
// console.log("poolListCalls.length:", poolListCalls.length);
// console.log("nostrClient.relays:", nostrClient.relays);
// Spec correction (SCN-profiles-batched): profile fetches are now BATCHED
// through the L1 SubscriptionManager — ONE query across all relays instead of
// one query per relay. The pool merges per-relay results and tolerates a
// failing relay internally, so the fast relay's profile still renders.
assert.equal(
poolListCalls.length,
1,
"expected a single batched query across all relays",
);
const [fastCall] = poolListCalls;
assert.deepEqual(
fastCall.relays,
[FAST_RELAY, FAIL_RELAY],
"batched query should target all configured relays",
);
assert.deepEqual(
fastCall.filters[0].authors,
[VALID_PUBKEY],
"should query only uncached, valid pubkeys",
);
assert.deepEqual(
updates[0],
{ pubkey: CACHED_PUBKEY, profile: { name: "Cached", picture: "cached.png" } },
"cached profiles should hydrate immediately",
);
assert.equal(updates.length, 2, "fast relay should update immediately");
assert.deepEqual(
updates[1],
{ pubkey: VALID_PUBKEY, profile: { name: "Fast", picture: "fast.png" } },
"newest relay profile should render",
);
assert.deepEqual(setCalls, [
{ pubkey: VALID_PUBKEY, profile: { name: "Fast", picture: "fast.png" } },
]);
});
test("batchFetchProfiles respects forceRefresh", async () => {
const cache = new Map();
const updates = [];
const setCalls = [];
const authorSet = new Set([
CACHED_PUBKEY,
]);
const getProfileCacheEntry = (pubkey) => {
if (pubkey === CACHED_PUBKEY) {
return { profile: { name: "Cached", picture: "cached.png" } };
}
return null;
};
const setProfileCacheEntry = (pubkey, profile) => {
cache.set(pubkey, { profile });
setCalls.push({ pubkey, profile });
};
const updateProfileInDOM = (pubkey, profile) => {
updates.push({ pubkey, profile });
};
const originalPool = nostrClient.pool;
const poolListCalls = [];
nostrClient.pool = {
list: (relays, filters) => {
poolListCalls.push({ relays, filters });
return Promise.resolve([
createProfileEvent({
pubkey: CACHED_PUBKEY,
createdAt: 300,
content: { name: "Refreshed", picture: "refreshed.png" },
}),
]);
},
};
try {
await batchFetchProfilesFromRelays({
authorSet,
forceRefresh: true,
getProfileCacheEntry,
setProfileCacheEntry,
updateProfileInDOM,
});
} finally {
nostrClient.pool = originalPool;
}
// Should fetch despite cache presence
assert.ok(poolListCalls.length > 0, "should query relays when forceRefresh is true");
assert.deepEqual(
poolListCalls[0].filters[0].authors,
[CACHED_PUBKEY],
"should include cached pubkey in fetch list"
);
// Should still optimistic update from cache first
assert.deepEqual(
updates[0],
{ pubkey: CACHED_PUBKEY, profile: { name: "Cached", picture: "cached.png" } },
"cached profiles should hydrate immediately even with forceRefresh"
);
// Then update with fresh data
const lastUpdate = updates[updates.length - 1];
assert.deepEqual(
lastUpdate,
{ pubkey: CACHED_PUBKEY, profile: { name: "Refreshed", picture: "refreshed.png" } },
"should update with refreshed data"
);
});