Files
bitvid/tests/storage-tombstone-service.test.mjs
T

86 lines
4.7 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { tombstoneFixture, videoEvent, ids, author, filteredStorageKey } from './test-helpers/storage-tombstone-fixture.mjs';
const cachedVideo = (id, extra = {}) => ({ id, pubkey: author, videoRootId: id, created_at: 100, title: id, url: 'https://example.invalid/video.mp4', ...extra });
const assertSnapshot = (f, expected) => {
assert.deepEqual(ids([...f.service.getVideosMap().values()]), expected);
assert.deepEqual(ids(f.notifications.at(-1).videos), expected);
assert.deepEqual(ids(f.updates.at(-1).videos), expected);
assert.deepEqual(ids(f.persisted()), expected);
};
test('different-ID tombstone removes every obsolete cached version and preserves unrelated live content', async t => {
const f = tombstoneFixture(t); await f.load();
await f.ingest(videoEvent('first', 100));
await f.ingest(videoEvent('edit', 150), videoEvent('other', 100, { root: 'other', pubkey: 'b'.repeat(64) }));
await f.ingest(videoEvent('delete', 200, { deleted: true }));
assertSnapshot(f, ['other']);
assert.deepEqual(ids(f.updates.at(-1).deleted), ['delete']);
await f.ingest(videoEvent('late', 120));
assertSnapshot(f, ['other']);
});
test('deletion-only empty result persists and second consumer receives no optimistic resurrection', async t => {
const f = tombstoneFixture(t); await f.load(); await f.ingest(videoEvent('first', 100));
await f.ingest(videoEvent('delete', 200, { deleted: true }));
assertSnapshot(f, []);
const count = f.notifications.length;
await f.load();
assert.deepEqual(f.notifications.slice(count).map(entry => ids(entry.videos)), [[]]);
assert.deepEqual(f.persisted(), []);
});
for (const viewer of ['public', 'author']) {
test(`persistent deleted and invalid entries are rejected before ${viewer} access bypass`, async t => {
const f = tombstoneFixture(t, { persisted: [cachedVideo('deleted', { deleted: true }), cachedVideo('invalid', { invalid: true }), cachedVideo('live')] });
if (viewer === 'author') f.client.pubkey = author;
const initial = await f.load();
assert.deepEqual(ids(initial), ['live']);
assert.equal(f.notifications.some(entry => entry.videos.some(video => video.deleted || video.invalid)), false);
assert.deepEqual(ids([...f.service.getVideosMap().values()]), ['live']);
assert.deepEqual(ids(f.persisted()), ['live']);
});
}
test('all-ineligible persistent cache is cleared even when active snapshot is empty', async t => {
const f = tombstoneFixture(t, { persisted: [cachedVideo('deleted', { deleted: true }), cachedVideo('invalid', { invalid: true })] });
assert.deepEqual(await f.load(), []);
assert.deepEqual(f.persisted(), []);
assert.deepEqual(f.notifications.map(entry => ids(entry.videos)), [[]]);
});
test('stale nondeleted persistent copies honor known tombstone while strictly newer and unrelated entries survive', async t => {
const f = tombstoneFixture(t); await f.load();
await f.ingest(videoEvent('old', 100));
const stale = f.persisted()[0];
await f.ingest(videoEvent('delete', 200, { deleted: true }));
await f.ingest(videoEvent('newer', 300), videoEvent('other', 150, { root: 'other' }));
// A stale storage snapshot contains an independent, still-nondeleted copy.
localStorage.setItem(filteredStorageKey, JSON.stringify([stale, ...f.persisted()]));
const start = f.notifications.length;
await f.load();
assert.equal(f.notifications.slice(start).some(entry => entry.videos.some(video => video.id === 'old')), false);
assertSnapshot(f, ['newer', 'other']);
});
test('direct cache API never reintroduces deleted invalid or tombstone-covered copies', async t => {
const f = tombstoneFixture(t); await f.load(); await f.ingest(videoEvent('old', 100));
const stale = f.persisted()[0];
await f.ingest(videoEvent('delete', 200, { deleted: true }));
f.service.cacheVideos([stale, cachedVideo('deleted', { deleted: true }), cachedVideo('invalid', { invalid: true }), cachedVideo('live')]);
assert.deepEqual(ids([...f.service.getVideosMap().values()]), ['live']);
});
test('legacy tombstone cleanup retains other author with same d-tag and strictly newer content', async t => {
const f = tombstoneFixture(t); await f.load();
const legacy = { root: `LEGACY:${author}:same`, dTag: 'same' };
const other = { root: `LEGACY:${'b'.repeat(64)}:same`, dTag: 'same', pubkey: 'b'.repeat(64) };
await f.ingest(videoEvent('old', 100, legacy), videoEvent('other-author', 100, other));
await f.ingest(videoEvent('delete', 200, { ...legacy, deleted: true }));
assertSnapshot(f, ['other-author']);
await f.ingest(videoEvent('newer', 300, legacy));
await f.ingest(videoEvent('delete-replayed', 200, { ...legacy, deleted: true }));
assertSnapshot(f, ['newer', 'other-author']);
});