mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Harden profile data loading
This commit is contained in:
1 parent
5f439d6863
commit
b774de6d76
7 files changed
+237
-59
No files matched your search
@@ -0,0 +1,45 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { ProfileDirectMessageActions } from "../js/ui/profileModal/ProfileDirectMessageActions.js";
|
||||
|
||||
test("DM pane renders a persisted snapshot while the relay load is still active", () => {
|
||||
const scheduled = [];
|
||||
const actor = "a".repeat(64);
|
||||
const controller = {
|
||||
activeMessagesRequest: Symbol("relay-load"),
|
||||
directMessagesCache: [],
|
||||
directMessagesLastActor: null,
|
||||
helper: { resolveActiveDmActor: () => actor },
|
||||
renderer: {
|
||||
scheduleDirectMessagesRender: (detail) => scheduled.push(detail),
|
||||
setMessagesLoadingState: () => {},
|
||||
},
|
||||
};
|
||||
const actions = new ProfileDirectMessageActions({}, controller);
|
||||
const messages = [{ event: { id: "cached-message" } }];
|
||||
|
||||
actions.handleDirectMessagesUpdated({ messages, reason: "snapshot" });
|
||||
|
||||
assert.deepEqual(controller.directMessagesCache, messages);
|
||||
assert.equal(controller.directMessagesLastActor, actor);
|
||||
assert.deepEqual(scheduled, [{ messages, actorPubkey: actor, reason: "snapshot" }]);
|
||||
});
|
||||
|
||||
test("DM pane waits for relay increments but ignores unrelated updates during a load", () => {
|
||||
const controller = {
|
||||
activeMessagesRequest: Symbol("relay-load"),
|
||||
directMessagesCache: [],
|
||||
directMessagesLastActor: null,
|
||||
helper: { resolveActiveDmActor: () => "a".repeat(64) },
|
||||
renderer: {
|
||||
scheduleDirectMessagesRender: () => assert.fail("unrelated update must not render"),
|
||||
setMessagesLoadingState: () => {},
|
||||
},
|
||||
};
|
||||
const actions = new ProfileDirectMessageActions({}, controller);
|
||||
|
||||
actions.handleDirectMessagesUpdated({ messages: [{ event: { id: "other" } }], reason: "load" });
|
||||
|
||||
assert.deepEqual(controller.directMessagesCache, []);
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { ProfilePlaylistsController } from "../js/ui/profileModal/ProfilePlaylistsController.js";
|
||||
|
||||
test("playlist refresh renders the list before deferred video metadata completes", async () => {
|
||||
const controller = new ProfilePlaylistsController({
|
||||
getActivePubkey: () => "a".repeat(64),
|
||||
services: {
|
||||
nostrService: {
|
||||
fetchVideosByAuthors: () => new Promise(() => {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
const renders = [];
|
||||
let resolveMetadata;
|
||||
controller.listEl = {};
|
||||
controller.setLoading = () => {};
|
||||
controller.render = () => renders.push(controller.playlists.map((playlist) => playlist.id));
|
||||
controller.fetchCreatorPlaylists = async () => [{
|
||||
id: "existing",
|
||||
items: [{ type: "a", value: `30078:${"b".repeat(64)}:video` }],
|
||||
}];
|
||||
controller.mainController.services.nostrService.fetchVideosByAuthors = () =>
|
||||
new Promise((resolve) => {
|
||||
resolveMetadata = resolve;
|
||||
});
|
||||
|
||||
await controller.refresh();
|
||||
assert.deepEqual(renders, [["existing"]], "list renders before metadata request settles");
|
||||
|
||||
resolveMetadata();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.deepEqual(renders, [["existing"], ["existing"]], "metadata completion refreshes labels later");
|
||||
});
|
||||
@@ -1324,6 +1324,7 @@ test("loadSubscriptions uses overlap window and deterministic latest-event tie-b
|
||||
subscribedPubkeys: [oldAuthor],
|
||||
createdAt: 1000,
|
||||
eventId: "cached-event",
|
||||
eventPubkey: pubkey,
|
||||
});
|
||||
|
||||
nostrClient.fetchListIncrementally = async (params = {}) => {
|
||||
@@ -1378,6 +1379,104 @@ test("loadSubscriptions uses overlap window and deterministic latest-event tie-b
|
||||
}
|
||||
});
|
||||
|
||||
test("loadSubscriptions never applies a different BitLogin session actor's list", async () => {
|
||||
const SubscriptionsManager = subscriptions.constructor;
|
||||
const manager = new SubscriptionsManager();
|
||||
const activePubkey = "a".repeat(64);
|
||||
const sessionPubkey = "b".repeat(64);
|
||||
const activeAuthor = "c".repeat(64);
|
||||
const sessionAuthor = "d".repeat(64);
|
||||
const originalFetchIncremental = nostrClient.fetchListIncrementally;
|
||||
const originalSessionActor = nostrClient.sessionActor;
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalDecryptSubscriptionEvent = manager.decryptSubscriptionEvent;
|
||||
|
||||
nostrClient.sessionActor = { pubkey: sessionPubkey };
|
||||
nostrClient.pool = { list: async () => [] };
|
||||
nostrClient.fetchListIncrementally = async ({ pubkey }) => {
|
||||
assert.equal(pubkey, activePubkey, "only the active account may be fetched");
|
||||
return [
|
||||
{
|
||||
id: "active-list",
|
||||
pubkey: activePubkey,
|
||||
created_at: 100,
|
||||
content: "active-ciphertext",
|
||||
tags: [["encrypted", "nip04"]],
|
||||
},
|
||||
{
|
||||
id: "foreign-list",
|
||||
pubkey: sessionPubkey,
|
||||
created_at: 200,
|
||||
content: "foreign-ciphertext",
|
||||
tags: [["encrypted", "nip04"]],
|
||||
},
|
||||
];
|
||||
};
|
||||
manager.decryptSubscriptionEvent = async (event) => ({
|
||||
ok: true,
|
||||
plaintext: JSON.stringify([["p", event.id === "active-list" ? activeAuthor : sessionAuthor]]),
|
||||
});
|
||||
|
||||
try {
|
||||
await manager.updateFromRelays(activePubkey, { allowPermissionPrompt: false });
|
||||
assert.equal(manager.subsEventId, "active-list");
|
||||
assert.deepEqual(manager.getSubscribedAuthors(), [activeAuthor]);
|
||||
} finally {
|
||||
nostrClient.fetchListIncrementally = originalFetchIncremental;
|
||||
nostrClient.sessionActor = originalSessionActor;
|
||||
nostrClient.pool = originalPool;
|
||||
manager.decryptSubscriptionEvent = originalDecryptSubscriptionEvent;
|
||||
localStorage.clear();
|
||||
profileCache.memoryCache.clear();
|
||||
profileCache.activePubkey = null;
|
||||
}
|
||||
});
|
||||
|
||||
test("loadSubscriptions repairs a legacy cached list with a full active-account fetch", async () => {
|
||||
const SubscriptionsManager = subscriptions.constructor;
|
||||
const manager = new SubscriptionsManager();
|
||||
const activePubkey = "e".repeat(64);
|
||||
const staleAuthor = "f".repeat(64);
|
||||
const correctAuthor = "1".repeat(64);
|
||||
const originalFetchIncremental = nostrClient.fetchListIncrementally;
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalDecryptSubscriptionEvent = manager.decryptSubscriptionEvent;
|
||||
|
||||
profileCache.setProfileData(activePubkey, "subscriptions", {
|
||||
subscribedPubkeys: [staleAuthor],
|
||||
eventId: "legacy-foreign-event",
|
||||
createdAt: 9999,
|
||||
});
|
||||
nostrClient.pool = { list: async () => [] };
|
||||
nostrClient.fetchListIncrementally = async (params) => {
|
||||
assert.equal(params.since, 0, "legacy source-less cache must not suppress a full fetch");
|
||||
return [{
|
||||
id: "correct-active-event",
|
||||
pubkey: activePubkey,
|
||||
created_at: 100,
|
||||
content: "correct-ciphertext",
|
||||
tags: [["encrypted", "nip04"]],
|
||||
}];
|
||||
};
|
||||
manager.decryptSubscriptionEvent = async () => ({
|
||||
ok: true,
|
||||
plaintext: JSON.stringify([["p", correctAuthor]]),
|
||||
});
|
||||
|
||||
try {
|
||||
await manager.updateFromRelays(activePubkey, { allowPermissionPrompt: false });
|
||||
assert.deepEqual(manager.getSubscribedAuthors(), [correctAuthor]);
|
||||
assert.equal(manager.subsEventPubkey, activePubkey);
|
||||
} finally {
|
||||
nostrClient.fetchListIncrementally = originalFetchIncremental;
|
||||
nostrClient.pool = originalPool;
|
||||
manager.decryptSubscriptionEvent = originalDecryptSubscriptionEvent;
|
||||
localStorage.clear();
|
||||
profileCache.memoryCache.clear();
|
||||
profileCache.activePubkey = null;
|
||||
}
|
||||
});
|
||||
|
||||
test("handleSubscriptionListEvent does not re-trigger on the live-sub echo (loop guard)", () => {
|
||||
const SubscriptionsManager = subscriptions.constructor;
|
||||
const manager = new SubscriptionsManager();
|
||||
|
||||
Reference in new issue
Block a user