2026-07-18 16:56:08 -04:00
|
|
|
|
import { test } from 'node:test';
|
|
|
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
|
|
|
|
|
|
|
|
// These modules touch localStorage at import/runtime. Provide a minimal mock
|
|
|
|
|
|
// that records removed keys, then import dynamically (a static import would
|
|
|
|
|
|
// hoist above the mock).
|
|
|
|
|
|
const removed: string[] = [];
|
|
|
|
|
|
const store = new Map<string, string>();
|
2026-09-19 13:38:47 -04:00
|
|
|
|
// A Proxy so `Object.keys(localStorage)` (used by the prefix sweeps) sees the
|
|
|
|
|
|
// stored keys, like the real Storage object.
|
|
|
|
|
|
const api = {
|
2026-07-18 16:56:08 -04:00
|
|
|
|
getItem: (k: string) => store.get(k) ?? null,
|
|
|
|
|
|
setItem: (k: string, v: string) => {
|
|
|
|
|
|
store.set(k, v);
|
|
|
|
|
|
},
|
|
|
|
|
|
removeItem: (k: string) => {
|
|
|
|
|
|
removed.push(k);
|
|
|
|
|
|
store.delete(k);
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
2026-09-19 13:38:47 -04:00
|
|
|
|
(globalThis as { localStorage?: unknown }).localStorage = new Proxy(api, {
|
|
|
|
|
|
ownKeys: () => Array.from(store.keys()),
|
|
|
|
|
|
getOwnPropertyDescriptor: (target, key) =>
|
|
|
|
|
|
typeof key === 'string' && store.has(key)
|
|
|
|
|
|
? { value: store.get(key), enumerable: true, configurable: true, writable: true }
|
|
|
|
|
|
: Object.getOwnPropertyDescriptor(target, key),
|
|
|
|
|
|
});
|
2026-07-18 16:56:08 -04:00
|
|
|
|
|
|
|
|
|
|
const { clearPlaintextCaches } = await import('./plaintextCaches');
|
|
|
|
|
|
|
|
|
|
|
|
test('clearPlaintextCaches removes every plaintext/PII localStorage key', () => {
|
|
|
|
|
|
store.clear();
|
|
|
|
|
|
store.set('cinny_translation_cache_v1', '[]');
|
|
|
|
|
|
store.set('cinny_scheduled_messages_v1', '{}');
|
|
|
|
|
|
store.set('cinny_recent_searches_v1', '[]');
|
|
|
|
|
|
store.set('cinny_recent_forward_targets_v1', '[]');
|
|
|
|
|
|
store.set('cinny_recent_gifs_v1', '[]');
|
|
|
|
|
|
store.set('cinny_recent_stickers_v1', '[]');
|
2026-09-28 21:02:45 -04:00
|
|
|
|
store.set('lotus_outbox_v1', '{"userId":"@me:server","entries":[]}');
|
2026-07-18 16:56:08 -04:00
|
|
|
|
removed.length = 0;
|
|
|
|
|
|
|
|
|
|
|
|
clearPlaintextCaches();
|
|
|
|
|
|
|
|
|
|
|
|
for (const key of [
|
|
|
|
|
|
'cinny_translation_cache_v1',
|
|
|
|
|
|
'cinny_scheduled_messages_v1',
|
|
|
|
|
|
'cinny_recent_searches_v1',
|
|
|
|
|
|
'cinny_recent_forward_targets_v1',
|
|
|
|
|
|
'cinny_recent_gifs_v1',
|
|
|
|
|
|
'cinny_recent_stickers_v1',
|
2026-09-28 21:02:45 -04:00
|
|
|
|
'lotus_outbox_v1', // [Gitea #112] unsent message content
|
2026-07-18 16:56:08 -04:00
|
|
|
|
]) {
|
|
|
|
|
|
assert.ok(removed.includes(key), `${key} cleared`);
|
|
|
|
|
|
}
|
|
|
|
|
|
assert.equal(store.size, 0, 'all keys gone from store');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('clearPlaintextCaches clears the per-user nav-path store only when given a userId', () => {
|
|
|
|
|
|
store.clear();
|
|
|
|
|
|
store.set('navToActivePath@me:server', '{}');
|
|
|
|
|
|
removed.length = 0;
|
|
|
|
|
|
|
|
|
|
|
|
clearPlaintextCaches(); // no userId -> nav path untouched
|
|
|
|
|
|
assert.ok(!removed.includes('navToActivePath@me:server'), 'nav path kept without userId');
|
|
|
|
|
|
|
|
|
|
|
|
clearPlaintextCaches('@me:server');
|
|
|
|
|
|
assert.ok(removed.includes('navToActivePath@me:server'), 'nav path cleared with userId');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-09-19 13:38:47 -04:00
|
|
|
|
test('clearPlaintextCaches wipes drafts (#41) and the status message (#204) but not session keys', () => {
|
2026-07-18 16:56:08 -04:00
|
|
|
|
store.clear();
|
|
|
|
|
|
store.set('draft-msg-!room:server', '{"body":"unsent"}');
|
2026-09-19 13:38:47 -04:00
|
|
|
|
store.set('lotus-status-msg-@me:server', 'at the dentist');
|
|
|
|
|
|
store.set('lotus-status-expiry-@me:server', '123');
|
2026-07-18 16:56:08 -04:00
|
|
|
|
store.set('cinny_session', '{"accessToken":"x"}');
|
|
|
|
|
|
removed.length = 0;
|
|
|
|
|
|
|
|
|
|
|
|
clearPlaintextCaches('@me:server');
|
|
|
|
|
|
|
2026-09-19 13:38:47 -04:00
|
|
|
|
assert.ok(removed.includes('draft-msg-!room:server'), 'draft cleared');
|
|
|
|
|
|
assert.ok(removed.includes('lotus-status-msg-@me:server'), 'status message cleared');
|
|
|
|
|
|
assert.ok(removed.includes('lotus-status-expiry-@me:server'), 'status expiry cleared');
|
2026-07-18 16:56:08 -04:00
|
|
|
|
assert.ok(!removed.includes('cinny_session'), 'session key not this module’s concern');
|
2026-09-19 13:38:47 -04:00
|
|
|
|
assert.ok(store.has('cinny_session'));
|
2026-07-18 16:56:08 -04:00
|
|
|
|
});
|