Files
bitvid/tests/state/profile-cache.test.mjs
T
google-labs-jules[bot] 9cdc91a186 test(ai): fix sessionActor tests and enhance profileCache coverage
- Fixed `tests/nostr/sessionActor.test.js` to remove outdated plain-text storage test and use encrypted payload for cleanup test.
- Enhanced `tests/state/profile-cache.test.mjs` with tests for:
    - Event emission (`profileChanged`)
    - TTL expiration (using `watchHistory`)
    - XSS sanitization in `setProfile`
- Removed redundant/competing `tests/state/profileCache.test.mjs`.
- Verified tests pass with `scripts/run-targeted-tests.mjs`.
2026-01-26 11:35:18 +00:00

159 lines
5.6 KiB
JavaScript

// Run with: node scripts/run-targeted-tests.mjs tests/state/profile-cache.test.mjs
import "../test-helpers/setup-localstorage.mjs";
import assert from "node:assert/strict";
import test, { beforeEach } from "node:test";
import { profileCache } from "../../js/state/profileCache.js";
beforeEach(() => {
if (globalThis.localStorage && typeof globalThis.localStorage.clear === "function") {
globalThis.localStorage.clear();
}
// Reset singleton state
if (profileCache.activePubkey) {
profileCache.clearMemoryCache(profileCache.activePubkey);
profileCache.activePubkey = null;
}
profileCache.memoryCache.clear();
});
test("ProfileCache: normalizeHexPubkey validates and cleans inputs", () => {
assert.equal(profileCache.normalizeHexPubkey(null), null);
assert.equal(profileCache.normalizeHexPubkey("invalid"), null);
const valid = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
assert.equal(profileCache.normalizeHexPubkey(valid), valid);
assert.equal(profileCache.normalizeHexPubkey(` ${valid.toUpperCase()} `), valid);
});
test("ProfileCache: resolves storage keys correctly", () => {
const pubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
// Test watchHistory (kind:pubkey:d)
const historyKey = profileCache.resolveAddressKey("watchHistory", { pubkey });
assert.match(historyKey, new RegExp(`bitvid:profile:${pubkey}:watchHistory:watch-history:v1`));
// Test profile (simple)
const profileKey = profileCache.resolveAddressKey("profile", { pubkey });
assert.match(profileKey, new RegExp(`bitvid:profile:${pubkey}:profileMetadata:v1`));
});
test("ProfileCache: persists data to localStorage and memory", () => {
const pubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const data = { foo: "bar" };
profileCache.setProfileData(pubkey, "test-section", data);
// Check memory
const memData = profileCache.getProfileData(pubkey, "test-section");
assert.deepEqual(memData, data);
// Check storage
const key = profileCache.getStorageKey(pubkey, "test-section");
const stored = globalThis.localStorage.getItem(key);
assert.ok(stored);
assert.deepEqual(JSON.parse(stored), data);
});
test("ProfileCache: loads from localStorage if memory miss", () => {
const pubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const data = { loaded: true };
const key = profileCache.getStorageKey(pubkey, "test-load");
// Pre-seed storage
globalThis.localStorage.setItem(key, JSON.stringify(data));
// Clear memory to force load
profileCache.clearMemoryCache(pubkey);
const result = profileCache.getProfileData(pubkey, "test-load");
assert.deepEqual(result, data);
});
test("ProfileCache: handles active profile switching", () => {
const pubkey1 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const pubkey2 = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210";
profileCache.setActiveProfile(pubkey1);
profileCache.set("section", { p: 1 });
assert.deepEqual(profileCache.get("section"), { p: 1 });
profileCache.setActiveProfile(pubkey2);
// Should not access pubkey1's data via active convenience methods
assert.equal(profileCache.get("section"), null);
// But pubkey1 data should still exist in persistence if saved
const p1Data = profileCache.getProfileData(pubkey1, "section");
assert.deepEqual(p1Data, { p: 1 });
});
test("ProfileCache: setProfile normalizes and saves", () => {
const pubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const rawProfile = {
name: " Tester ",
picture: "https://example.com/pic.jpg",
about: "I am a tester",
};
const entry = profileCache.setProfile(pubkey, rawProfile);
assert.equal(entry.profile.name, "Tester");
assert.equal(entry.profile.picture, "https://example.com/pic.jpg");
assert.ok(entry.timestamp);
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);
});