Chantier F - Typage Python - ruff S101 déplacé en per-file-ignores (pyproject.toml) - pyrightconfig.json : mode standard confirmé Chantier G - Assertions & bare-except - 16 blocs except documentés dans config.py - --cov-branch activé, seuil coverage 60→68% Chantier H - Cache settings - Stratégie documentée dans settings.py - _resolve_index_dir() logguée dans config.py Chantier I - Split gros fichiers JS - client.js 1509L → client.js 406L + clientAuth.js + clientOps.js + fetchUtils.js - toolbar.js 927L → toolbar.js 458L + toolbarActions.js + toolbarControls.js - FloatingViewer.js → FloatingViewer.impl.js + floatingViewer*(8 modules) - Viewer_impl.js → ViewerRuntime.js + viewerShell.js + viewerThemeStyles.js + viewerOverlayDismiss.js - model3dRenderer.js → model3dRenderer.impl.js + model3dCore.js + model3dLoaders.js + model3dSupport.js - dialogs.js → dialogs.js + DialogTemplates.js Chantier J - Tests frontend (Phases 1-2) - use_drag_drop.vitest.mjs : 5 tests - use_grid_state.vitest.mjs : 18 tests - security_client_headers, security_deep_merge, security_runtime_state, runtime_store, dialog_templates - Total: 80 fichiers test / 350 tests vitest Chantier K - Pinia migration - runtimeState.js entièrement migré vers Pinia - getOptionalRuntimeStore.js + runtimeEnrichmentState.js ajoutés Chantier L - Durcissement sécurité - tests/security/test_rate_limit.py : 8 tests rate-limit (state machine complète) - clientAuth.js : docstring sessionStorage security boundary - three.js 0.183.2 audité : 0 CVE (Snyk) Chantier M - Simplification config - deps.py, tool_detect.py, utils.py nettoyés docs: PLAN_REFACTO_V2_LONG_TERME.md ajouté et clôturé .gitignore: coverage/ ajouté
67 lines
2.2 KiB
JavaScript
67 lines
2.2 KiB
JavaScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
getRuntimeState,
|
|
setRuntimeStatePatch,
|
|
} from "../app/runtimeState.js";
|
|
|
|
describe("runtimeState prototype-pollution resistance", () => {
|
|
beforeEach(() => {
|
|
const state = getRuntimeState();
|
|
for (const key of Object.keys(state)) {
|
|
delete state[key];
|
|
}
|
|
Object.assign(state, {
|
|
api: null,
|
|
assetsDeletedHandler: null,
|
|
enrichmentActive: false,
|
|
enrichmentQueueLength: 0,
|
|
});
|
|
});
|
|
|
|
it("blocks __proto__ key from being set via patch", () => {
|
|
const proto = Object.getPrototypeOf({});
|
|
const patch = JSON.parse('{"__proto__": {"polluted": true}}');
|
|
setRuntimeStatePatch(patch);
|
|
|
|
expect(({}).polluted).toBeUndefined();
|
|
expect(Object.getPrototypeOf({})).toBe(proto);
|
|
});
|
|
|
|
it("blocks constructor key from being set via patch", () => {
|
|
setRuntimeStatePatch({ constructor: "evil" });
|
|
|
|
const state = getRuntimeState();
|
|
expect(Object.hasOwn(state, "constructor")).toBe(false);
|
|
});
|
|
|
|
it("blocks prototype key from being set via patch", () => {
|
|
setRuntimeStatePatch({ prototype: { polluted: true } });
|
|
|
|
const state = getRuntimeState();
|
|
expect(state.prototype).toBeUndefined();
|
|
});
|
|
|
|
it("applies safe keys normally", () => {
|
|
setRuntimeStatePatch({ enrichmentActive: true, enrichmentQueueLength: 5 });
|
|
|
|
const state = getRuntimeState();
|
|
expect(state.enrichmentActive).toBe(true);
|
|
expect(state.enrichmentQueueLength).toBe(5);
|
|
});
|
|
|
|
it("handles null/undefined patch gracefully", () => {
|
|
expect(() => setRuntimeStatePatch(null)).not.toThrow();
|
|
expect(() => setRuntimeStatePatch(undefined)).not.toThrow();
|
|
expect(() => setRuntimeStatePatch()).not.toThrow();
|
|
});
|
|
|
|
it("uses Symbol key so state is not enumerable on globalThis", () => {
|
|
getRuntimeState();
|
|
const keys = Object.keys(globalThis);
|
|
const hasPlainKey = keys.some(
|
|
(k) => k.includes("runtime_state") || k.includes("majoor"),
|
|
);
|
|
expect(hasPlainKey).toBe(false);
|
|
});
|
|
});
|