mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
- 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`.
159 lines
5.6 KiB
JavaScript
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);
|
|
});
|