import { test, describe, beforeEach, afterEach } from "node:test"; import assert from "node:assert"; import "../test-helpers/setup-localstorage.mjs"; import { SyncMetadataStore, __testExports } from "../../js/nostr/syncMetadataStore.js"; const { SYNC_METADATA_STORAGE_KEY } = __testExports; describe("SyncMetadataStore", () => { beforeEach(() => { localStorage.clear(); }); afterEach(() => { localStorage.clear(); }); test("should load from localStorage on initialization", () => { const data = [["key1", 1234567890]]; localStorage.setItem(SYNC_METADATA_STORAGE_KEY, JSON.stringify(data)); const store = new SyncMetadataStore(); // computeKey(kind, pubkey, dTag, relayUrl) // We don't know the components of "key1" but it should be in the map. // However, the class treats keys opaquely once computed. // Let's use internal map to verify if possible, or verify via public API if we know the key components. // Accessing private property? logic uses `this.metadata`. // We can't easily access `this.metadata` if it's not exposed. // But we can verify with `getLastSeen`. // Wait, "key1" isn't a valid key format generated by computeKey, but load() just loads the map. // getLastSeen computes the key again. // So if we pre-populate with a valid key, we can test it. const kind = 1; const pubkey = "pubkey1"; const dTag = "dtag1"; const relayUrl = "wss://relay1.com"; const key = `${kind}:${pubkey}:${dTag}:${relayUrl}`; const timestamp = 1000; localStorage.setItem(SYNC_METADATA_STORAGE_KEY, JSON.stringify([[key, timestamp]])); const store2 = new SyncMetadataStore(); assert.strictEqual(store2.getLastSeen(kind, pubkey, dTag, relayUrl), timestamp); }); test("should update last seen and save to localStorage", () => { const store = new SyncMetadataStore(); const kind = 1; const pubkey = "pubkey1"; const dTag = "dtag1"; const relayUrl = "wss://relay1.com"; const timestamp = 2000; store.updateLastSeen(kind, pubkey, dTag, relayUrl, timestamp); assert.strictEqual(store.getLastSeen(kind, pubkey, dTag, relayUrl), timestamp); const saved = JSON.parse(localStorage.getItem(SYNC_METADATA_STORAGE_KEY)); const key = `${kind}:${pubkey}:${dTag}:${relayUrl}`; const entry = saved.find(e => e[0] === key); assert.ok(entry); assert.strictEqual(entry[1], timestamp); }); test("should only update if timestamp is newer", () => { const store = new SyncMetadataStore(); const kind = 1; const pubkey = "pubkey1"; const dTag = "dtag1"; const relayUrl = "wss://relay1.com"; store.updateLastSeen(kind, pubkey, dTag, relayUrl, 2000); assert.strictEqual(store.getLastSeen(kind, pubkey, dTag, relayUrl), 2000); // Try older store.updateLastSeen(kind, pubkey, dTag, relayUrl, 1000); assert.strictEqual(store.getLastSeen(kind, pubkey, dTag, relayUrl), 2000); // Try newer store.updateLastSeen(kind, pubkey, dTag, relayUrl, 3000); assert.strictEqual(store.getLastSeen(kind, pubkey, dTag, relayUrl), 3000); }); test("should get per-relay last seen", () => { const store = new SyncMetadataStore(); const kind = 1; const pubkey = "pubkey1"; const dTag = "dtag1"; store.updateLastSeen(kind, pubkey, dTag, "wss://relay1.com", 1000); store.updateLastSeen(kind, pubkey, dTag, "wss://relay2.com", 2000); store.updateLastSeen(kind, pubkey, "other", "wss://relay1.com", 3000); // Should be ignored const result = store.getPerRelayLastSeen(kind, pubkey, dTag); assert.deepStrictEqual(result, { "wss://relay1.com": 1000, "wss://relay2.com": 2000 }); }); test("should handle missing localStorage gracefully", () => { const originalLocalStorage = globalThis.localStorage; delete globalThis.localStorage; // Simulate missing localStorage try { const store = new SyncMetadataStore(); store.updateLastSeen(1, "p", "d", "r", 100); // Should not throw // Can't check persistence, but ensure no crash } finally { globalThis.localStorage = originalLocalStorage; } }); });