From cc3ef2fb1feedc2f8fbb9d489091326dd0ca972d Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 20 Oct 2025 09:54:07 -0400 Subject: [PATCH] Ensure delete tombstones carry explicit flag --- js/nostr.js | 2 +- tests/nostr-delete-flow.test.mjs | 106 +++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 tests/nostr-delete-flow.test.mjs diff --git a/js/nostr.js b/js/nostr.js index 56e792ca..7c724952 100644 --- a/js/nostr.js +++ b/js/nostr.js @@ -7126,7 +7126,7 @@ export class NostrClient { const contentPayload = { version: Number.isFinite(vid.version) ? vid.version : 3, - deleted: vid.deleted === true, + deleted: true, isPrivate: vid.isPrivate === true, isNsfw: vid.isNsfw === true, isForKids: vid.isForKids === true && vid.isNsfw !== true, diff --git a/tests/nostr-delete-flow.test.mjs b/tests/nostr-delete-flow.test.mjs new file mode 100644 index 00000000..44126383 --- /dev/null +++ b/tests/nostr-delete-flow.test.mjs @@ -0,0 +1,106 @@ +import "./test-helpers/setup-localstorage.mjs"; +import assert from "node:assert/strict"; +import { NostrClient } from "../js/nostr.js"; + +await (async function testDeleteFlowPublishesDeletionFlag() { + const client = new NostrClient(); + client.hydrateVideoHistory = async () => {}; + + const pubkey = "PUBKEY123"; + const videoRootId = "root-abc"; + const baseVideo = { + id: "evt-original", + pubkey, + created_at: 1_700_000_000, + version: 3, + videoRootId, + title: "Sample", + url: "https://example.com/video.mp4", + magnet: "", + thumbnail: "https://example.com/thumb.jpg", + description: "example", + mode: "live", + tags: [ + ["t", "video"], + ["d", videoRootId], + ], + }; + + client.allEvents.set(baseVideo.id, { ...baseVideo }); + + const revertCalls = []; + client.revertVideo = async (event) => { + revertCalls.push(event); + return { id: `revert-${revertCalls.length}` }; + }; + + const result = await client.deleteAllVersions(videoRootId, pubkey, { + confirm: false, + video: baseVideo, + }); + + assert.equal(result, true, "deleteAllVersions should resolve true on success"); + assert.equal(revertCalls.length, 1, "deleteAllVersions should call revertVideo once"); + + const parsedPayload = JSON.parse(revertCalls[0].content); + assert.equal(parsedPayload.deleted, true, "revert payload should mark deleted true"); + assert.equal(parsedPayload.videoRootId, videoRootId); + + const cached = client.allEvents.get(baseVideo.id); + assert.equal(cached.deleted, true, "cached original video should be marked deleted"); + + const activeKey = `ROOT:${videoRootId}`; + client.activeMap.set(activeKey, { + ...baseVideo, + videoRootId, + created_at: baseVideo.created_at, + }); + + const seenUpdates = []; + const handlers = {}; + const fakeSub = { + on(type, handler) { + handlers[type] = handler; + }, + unsub() {}, + }; + + client.pool = { + sub() { + return fakeSub; + }, + }; + + client.subscribeVideos((videoUpdate) => { + seenUpdates.push(videoUpdate); + }); + + const deleteEvent = { + id: "delete-event-1", + pubkey, + created_at: baseVideo.created_at + 100, + content: revertCalls[0].content, + tags: baseVideo.tags, + }; + + handlers.event(deleteEvent); + if (typeof handlers.eose === "function") { + handlers.eose(); + } + + assert.equal( + seenUpdates.length, + 0, + "deleted events should not trigger subscription callbacks", + ); + assert.equal( + client.activeMap.has(activeKey), + false, + "deleted events should remove the active map entry", + ); + + const storedDelete = client.allEvents.get(deleteEvent.id); + assert.equal(storedDelete.deleted, true, "stored delete event should be marked deleted"); +})(); + +console.log("nostr delete flow tests passed");