Batches 6-9: behaviour-preserving extractions of testable pure cores plus the tests they unblock, and a fix for the broken client test environment. Full suites green: server 113 suites / 1117 + 1 todo, client 30 files / 338. client (R0 infra): - vitest.setup.ts: in-memory localStorage/sessionStorage Storage stub wired via setupFiles. Unblocks menu-items.gating.test.ts (was 9 failing) -> client suite fully green. + menu-items.suggestions.test.ts (getSuggestionItems filter/sort). share: - extract buildShareMetaHtml (share-seo.util.ts) from the SEO controller; tests for reflected-XSS escaping in <title>/og/twitter meta, noindex, truncation; extractPageSlugId; updateAttachmentAttr; prepareContentForShare comment-strip (anonymous-viewer metadata-leak guard). ai-chat (security extractions): - selectAccessibleHits: CASL post-filter for semantic search (restricted page in an accessible space must NOT leak to the agent). - validateResolvedAddresses: SSRF connect-time guard (block if ANY resolved address is private). - resolveAudioFormat: mime whitelist (dead `?? 'webm'` fallback dropped, set unchanged). + mcp-servers toView header-leak guard, MCP tool namespacing. collaboration (data-loss area): - extract computeHistoryJob (pins the "agent delay MUST stay 0" invariant) and resolveSource. Integration: onAuthenticate read-only matrix (collab auth bypass), HistoryProcessor (contributor restore on save failure), onStoreDocument Approach-A boundary snapshot (human revision pinned before agent overwrite). Reviewed (APPROVE WITH SUGGESTIONS): extractions behaviour-preserving, security tests mutation-resistant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
2.0 KiB
TypeScript
52 lines
2.0 KiB
TypeScript
// Vitest global setup (test-infra only — no production app source).
|
|
//
|
|
// Under Node 25 / jsdom 25 / vitest 4 the jsdom `localStorage` exposed on the
|
|
// global is not a usable Storage: its methods (`setItem`/`getItem`/...) are not
|
|
// callable, so any code touching `localStorage` throws `... is not a function`.
|
|
// Production code such as `isHtmlEmbedFeatureEnabled()` reads
|
|
// `localStorage.getItem("currentUser")`, which made dependent tests fail.
|
|
//
|
|
// We install a correct in-memory Storage stub on the global BEFORE tests run so
|
|
// the Web Storage contract holds: string coercion of keys/values, `null` for
|
|
// missing keys, working `length`/`key(index)`, and `clear()`.
|
|
import { vi } from "vitest";
|
|
|
|
// Minimal, spec-faithful in-memory implementation of the Web Storage API.
|
|
function createStorage(): Storage {
|
|
let store = new Map<string, string>();
|
|
|
|
const storage: Storage = {
|
|
get length(): number {
|
|
return store.size;
|
|
},
|
|
clear(): void {
|
|
store = new Map<string, string>();
|
|
},
|
|
getItem(key: string): string | null {
|
|
// Missing keys must return `null`, not `undefined`.
|
|
const value = store.get(String(key));
|
|
return value === undefined ? null : value;
|
|
},
|
|
setItem(key: string, value: string): void {
|
|
// Web Storage coerces both key and value to strings.
|
|
store.set(String(key), String(value));
|
|
},
|
|
removeItem(key: string): void {
|
|
store.delete(String(key));
|
|
},
|
|
key(index: number): string | null {
|
|
// Insertion order matches Map iteration order; out-of-range => null.
|
|
const keys = Array.from(store.keys());
|
|
return index >= 0 && index < keys.length ? keys[index] : null;
|
|
},
|
|
};
|
|
|
|
return storage;
|
|
}
|
|
|
|
// Install on the jsdom global. `vi.stubGlobal` also reflects onto `window`
|
|
// (jsdom shares `globalThis` and `window`), so both `localStorage` and
|
|
// `window.localStorage` resolve to the same working stub.
|
|
vi.stubGlobal("localStorage", createStorage());
|
|
vi.stubGlobal("sessionStorage", createStorage());
|