mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #1718 from PR0M3TH3AN/ai/tests-nostr-state-20250522-1996916389475353061
Test Suite Improvements: sessionActor and profileCache
This commit is contained in:
3 files changed
+51
-171
No files matched your search
@@ -95,7 +95,7 @@ test("clearStoredSessionActor removes persisted payload", async () => {
|
||||
persistSessionActor({
|
||||
pubkey: "npub1cleanup",
|
||||
privateKeyEncrypted: encrypted.ciphertext,
|
||||
encryption: encrypted
|
||||
encryption: encrypted,
|
||||
});
|
||||
assert.ok(globalThis.localStorage.getItem(SESSION_ACTOR_STORAGE_KEY));
|
||||
|
||||
|
||||
@@ -106,3 +106,53 @@ test("ProfileCache: setProfile normalizes and saves", () => {
|
||||
const stored = profileCache.getProfile(pubkey);
|
||||
assert.deepEqual(stored, entry.profile);
|
||||
});
|
||||
|
||||
test("ProfileCache: emits events on active profile change", () => {
|
||||
let eventDetail = null;
|
||||
const pubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
|
||||
const unsubscribe = profileCache.subscribe((event, detail) => {
|
||||
if (event === "profileChanged") {
|
||||
eventDetail = detail;
|
||||
}
|
||||
});
|
||||
|
||||
profileCache.setActiveProfile(pubkey);
|
||||
assert.deepEqual(eventDetail, { pubkey });
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
test("ProfileCache: respects TTL expiration", () => {
|
||||
const pubkey = "1111111111111111111111111111111111111111111111111111111111111111";
|
||||
const section = "watchHistory"; // 24h TTL
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
// Create expired data
|
||||
const data = { history: [], savedAt: Date.now() - (ONE_DAY_MS + 1000) };
|
||||
const key = profileCache.getStorageKey(pubkey, section);
|
||||
globalThis.localStorage.setItem(key, JSON.stringify(data));
|
||||
|
||||
// Ensure memory is empty
|
||||
profileCache.clearMemoryCache(pubkey);
|
||||
|
||||
const result = profileCache.getProfileData(pubkey, section);
|
||||
assert.equal(result, null);
|
||||
});
|
||||
|
||||
test("ProfileCache: setProfile sanitizes XSS in media URLs", () => {
|
||||
const pubkey = "2222222222222222222222222222222222222222222222222222222222222222";
|
||||
const rawProfile = {
|
||||
name: "Hacker",
|
||||
picture: "javascript:alert(1)",
|
||||
banner: "javascript:alert(2)",
|
||||
};
|
||||
|
||||
const entry = profileCache.setProfile(pubkey, rawProfile);
|
||||
|
||||
// sanitizeProfileMediaUrl returns null or fallback for invalid schemes
|
||||
// "assets/svg/default-profile.svg" is default for picture
|
||||
assert.equal(entry.profile.picture, "assets/svg/default-profile.svg");
|
||||
|
||||
// banner might be undefined if invalid
|
||||
assert.equal(entry.profile.banner, undefined);
|
||||
});
|
||||
@@ -1,170 +0,0 @@
|
||||
import { test, describe, beforeEach } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { profileCache } from "../../js/state/profileCache.js";
|
||||
import { NOTE_TYPES } from "../../js/nostrEventSchemas.js";
|
||||
|
||||
describe("ProfileCache", () => {
|
||||
const TEST_PUBKEY = "0000000000000000000000000000000000000000000000000000000000000001";
|
||||
const ANOTHER_PUBKEY = "0000000000000000000000000000000000000000000000000000000000000002";
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset internal state
|
||||
profileCache.activePubkey = null;
|
||||
profileCache.memoryCache.clear();
|
||||
profileCache.listeners.clear();
|
||||
// Clear mock localStorage
|
||||
if (globalThis.localStorage) {
|
||||
globalThis.localStorage.clear();
|
||||
}
|
||||
});
|
||||
|
||||
test("setActiveProfile normalizes and sets active pubkey", () => {
|
||||
profileCache.setActiveProfile(" " + TEST_PUBKEY.toUpperCase() + " ");
|
||||
assert.strictEqual(profileCache.getActiveProfile(), TEST_PUBKEY);
|
||||
});
|
||||
|
||||
test("setActiveProfile clears memory cache for old profile", () => {
|
||||
profileCache.setActiveProfile(TEST_PUBKEY);
|
||||
profileCache.setMemoryDataForPubkey(TEST_PUBKEY, "test_section", { foo: "bar" });
|
||||
assert.ok(profileCache.getProfileData(TEST_PUBKEY, "test_section"));
|
||||
|
||||
// Switch profile
|
||||
profileCache.setActiveProfile(ANOTHER_PUBKEY);
|
||||
|
||||
// Check old data is gone from memory
|
||||
// Note: profileCache.getProfileData falls back to localStorage, so we should ensure localStorage is empty
|
||||
// or specifically check memoryCache.
|
||||
const memKey = `${TEST_PUBKEY}:test_section`;
|
||||
assert.strictEqual(profileCache.memoryCache.has(memKey), false);
|
||||
});
|
||||
|
||||
test("resolveAddressKey generates correct keys based on policy", () => {
|
||||
profileCache.setActiveProfile(TEST_PUBKEY);
|
||||
|
||||
// Watch History: kind:pubkey:d (dTag default)
|
||||
// NOTE_TYPES.WATCH_HISTORY is usually 30078 or similar.
|
||||
// But CACHE_POLICIES keys are the noteType string from schemas.
|
||||
// Let's rely on what the code actually does.
|
||||
|
||||
const key = profileCache.resolveAddressKey("watchHistory", { pubkey: TEST_PUBKEY });
|
||||
// Expect: bitvid:profile:{pubkey}:{noteType}:{dTag}:v1
|
||||
// The code maps "watchHistory" to NOTE_TYPES.WATCH_HISTORY via SECTION_TO_NOTE_TYPE.
|
||||
const expectedPrefix = `bitvid:profile:${TEST_PUBKEY}:${NOTE_TYPES.WATCH_HISTORY}`;
|
||||
assert.ok(key.startsWith(expectedPrefix));
|
||||
assert.ok(key.includes(":v1"));
|
||||
});
|
||||
|
||||
test("setProfile normalizes and persists profile data", () => {
|
||||
profileCache.setActiveProfile(TEST_PUBKEY);
|
||||
|
||||
const rawProfile = {
|
||||
name: " Test User ",
|
||||
picture: "https://example.com/pic.jpg",
|
||||
about: " About me ",
|
||||
website: " https://example.com ",
|
||||
lud16: "[email protected]",
|
||||
};
|
||||
|
||||
profileCache.setProfile(TEST_PUBKEY, rawProfile);
|
||||
|
||||
const stored = profileCache.getProfile(TEST_PUBKEY);
|
||||
assert.ok(stored);
|
||||
assert.strictEqual(stored.name, "Test User"); // Trimmed
|
||||
assert.strictEqual(stored.picture, "https://example.com/pic.jpg");
|
||||
assert.strictEqual(stored.about, "About me"); // Trimmed
|
||||
assert.strictEqual(stored.website, "https://example.com"); // Trimmed
|
||||
assert.strictEqual(stored.lud16, "[email protected]");
|
||||
|
||||
// Check persistence
|
||||
// "profile" section maps to NOTE_TYPES.PROFILE_METADATA (kind 0)
|
||||
// Policy addressing is kind:pubkey
|
||||
const storageKey = profileCache.getStorageKey(TEST_PUBKEY, "profile");
|
||||
const storedJson = localStorage.getItem(storageKey);
|
||||
assert.ok(storedJson, "Should be saved to localStorage");
|
||||
const parsed = JSON.parse(storedJson);
|
||||
assert.deepStrictEqual(parsed.profile, stored);
|
||||
});
|
||||
|
||||
test("getProfileData checks TTL", (t) => {
|
||||
// Mock Date.now
|
||||
const originalDateNow = Date.now;
|
||||
let mockNow = 1000000;
|
||||
Date.now = () => mockNow;
|
||||
|
||||
try {
|
||||
// NOTE_TYPES.VIDEO_POST has 10 min TTL (600,000 ms)
|
||||
// We need to use a section that maps to a policy with TTL.
|
||||
// "profile" has Infinity TTL.
|
||||
// Let's look at CACHE_POLICIES.
|
||||
// VIDEO_POST is indexedDB, so ProfileCache might not handle it via localStorage path?
|
||||
// ProfileCache.getStorageTier checks policy.
|
||||
// CACHE_POLICIES[NOTE_TYPES.VIDEO_POST].storage is INDEXED_DB.
|
||||
|
||||
// We need a LOCAL_STORAGE policy with finite TTL.
|
||||
// WATCH_HISTORY is LOCAL_STORAGE and 24h TTL.
|
||||
|
||||
const section = "watchHistory";
|
||||
profileCache.setActiveProfile(TEST_PUBKEY);
|
||||
|
||||
const data = {
|
||||
items: [],
|
||||
timestamp: mockNow // Saved at 1000000
|
||||
};
|
||||
|
||||
profileCache.set(section, data);
|
||||
|
||||
// Verify immediate retrieval
|
||||
const retrieved = profileCache.get(section);
|
||||
assert.deepStrictEqual(retrieved, data);
|
||||
|
||||
// Fast forward 25 hours (25 * 60 * 60 * 1000 = 90,000,000)
|
||||
mockNow += 90000000;
|
||||
|
||||
// Clear memory cache to force load from storage
|
||||
profileCache.memoryCache.clear();
|
||||
|
||||
const expired = profileCache.get(section);
|
||||
assert.strictEqual(expired, null, "Should return null for expired data");
|
||||
|
||||
} finally {
|
||||
Date.now = originalDateNow;
|
||||
}
|
||||
});
|
||||
|
||||
test("setProfile sanitizes XSS in media URLs", () => {
|
||||
const maliciousProfile = {
|
||||
name: "Hacker",
|
||||
picture: "javascript:alert(1)",
|
||||
banner: "data:image/svg+xml;base64,PHN2ZyBvbG9hZD0iYWxlcnQoMSkiPjwvc3ZnPg==", // basic svg xss vector
|
||||
};
|
||||
|
||||
profileCache.setProfile(TEST_PUBKEY, maliciousProfile);
|
||||
const stored = profileCache.getProfile(TEST_PUBKEY);
|
||||
|
||||
// sanitizeProfileMediaUrl returns empty string or null for invalid/dangerous schemes
|
||||
// The implementation uses `sanitizeProfileMediaUrl` from `../utils/profileMedia.js`.
|
||||
// If it detects bad schemes, it might clear it.
|
||||
// Assuming "javascript:" is stripped.
|
||||
assert.notStrictEqual(stored.picture, "javascript:alert(1)");
|
||||
// If it returns null or empty string, that's good.
|
||||
|
||||
// Note: We are relying on `sanitizeProfileMediaUrl` behavior here.
|
||||
// If that function is robust, these assertions pass.
|
||||
});
|
||||
|
||||
test("emit fires events on update", (t, done) => {
|
||||
profileCache.setActiveProfile(TEST_PUBKEY);
|
||||
|
||||
const unsubscribe = profileCache.subscribe((event, detail) => {
|
||||
if (event === "update") {
|
||||
assert.strictEqual(detail.pubkey, TEST_PUBKEY);
|
||||
assert.strictEqual(detail.section, "test_section");
|
||||
assert.deepStrictEqual(detail.data, { foo: "bar" });
|
||||
unsubscribe();
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
profileCache.set("test_section", { foo: "bar" });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user