Skip to content

Commit 9b0893c

Browse files
committed
tests: add stress, leak, css-vector, and perf regression coverage
1 parent cecc92b commit 9b0893c

4 files changed

Lines changed: 171 additions & 0 deletions

File tree

tests/core.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,52 @@ test("core renderPromptStream uses incremental codegen session", async () => {
452452
await app.stop();
453453
});
454454

455+
test("core renderPromptStream supports 120 concurrent streams", async () => {
456+
const app = createRenderifyApp(createDependencies());
457+
await app.start();
458+
459+
const concurrentStreams = 120;
460+
const started = Date.now();
461+
const runs = await Promise.all(
462+
Array.from({ length: concurrentStreams }, async (_, index) => {
463+
let sawDelta = false;
464+
let sawFinal = false;
465+
let finalHtml = "";
466+
467+
for await (const chunk of app.renderPromptStream(`parallel-${index}`, {
468+
previewEveryChunks: 64,
469+
})) {
470+
if (chunk.type === "llm-delta") {
471+
sawDelta = true;
472+
}
473+
474+
if (chunk.type === "final" && chunk.final) {
475+
sawFinal = true;
476+
finalHtml = chunk.final.html;
477+
}
478+
}
479+
480+
return {
481+
sawDelta,
482+
sawFinal,
483+
finalHtml,
484+
};
485+
}),
486+
);
487+
const elapsed = Date.now() - started;
488+
489+
assert.equal(runs.length, concurrentStreams);
490+
assert.ok(runs.every((entry) => entry.sawDelta));
491+
assert.ok(runs.every((entry) => entry.sawFinal));
492+
assert.ok(runs.every((entry) => entry.finalHtml.length > 0));
493+
assert.ok(
494+
elapsed < 25000,
495+
`concurrent stream regression: elapsed=${elapsed}ms`,
496+
);
497+
498+
await app.stop();
499+
});
500+
455501
test("core renderPromptStream emits error chunk before throwing", async () => {
456502
const app = createRenderifyApp(
457503
createDependencies({

tests/perf-regression.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,47 @@ test("perf regression: runtime executes large plan under threshold", async () =>
7878
await runtime.terminate();
7979
}
8080
});
81+
82+
test("perf regression: runtime executes deeply nested tree under threshold", async () => {
83+
const runtime = new DefaultRuntimeManager();
84+
await runtime.initialize();
85+
86+
try {
87+
const depth = 320;
88+
let root: RuntimePlan["root"] = createTextNode("leaf");
89+
for (let index = 0; index < depth; index += 1) {
90+
root = createElementNode("section", { [`data-depth-${index}`]: "1" }, [
91+
root,
92+
]);
93+
}
94+
95+
const plan: RuntimePlan = {
96+
specVersion: DEFAULT_RUNTIME_PLAN_SPEC_VERSION,
97+
id: "perf_runtime_deep_plan",
98+
version: 1,
99+
capabilities: {
100+
domWrite: true,
101+
},
102+
root,
103+
};
104+
105+
const started = nowMs();
106+
const result = await runtime.executePlan(plan);
107+
const elapsed = nowMs() - started;
108+
109+
assert.equal(result.root.type, "element");
110+
let traversedDepth = 0;
111+
let cursor = result.root;
112+
while (cursor.type === "element" && cursor.children?.length) {
113+
traversedDepth += 1;
114+
cursor = cursor.children[0];
115+
}
116+
assert.ok(traversedDepth >= depth, `deep tree depth=${traversedDepth}`);
117+
assert.ok(
118+
elapsed < 2500,
119+
`runtime deep-tree regression: elapsed=${elapsed}ms`,
120+
);
121+
} finally {
122+
await runtime.terminate();
123+
}
124+
});

tests/runtime.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,6 +1823,59 @@ test("runtime source loader supports disabling fallback cdn attempts", async ()
18231823
}
18241824
});
18251825

1826+
test("runtime module caches are released across lifecycle cycles", async () => {
1827+
const runtime = new DefaultRuntimeManager({
1828+
remoteFallbackCdnBases: [],
1829+
remoteFetchRetries: 0,
1830+
remoteFetchBackoffMs: 10,
1831+
remoteFetchTimeoutMs: 600,
1832+
});
1833+
1834+
const internals = runtime as unknown as {
1835+
createSourceModuleLoader: (
1836+
moduleManifest: RuntimeModuleManifest | undefined,
1837+
diagnostics: Array<{ code?: string; message?: string }>,
1838+
) => {
1839+
materializeRemoteModule(url: string): Promise<string>;
1840+
};
1841+
browserModuleUrlCache: Map<string, string>;
1842+
browserModuleInflight: Map<string, Promise<string>>;
1843+
browserBlobUrls: Set<string>;
1844+
};
1845+
1846+
const originalFetch = globalThis.fetch;
1847+
globalThis.fetch = (async (_input: RequestInfo | URL) => {
1848+
return new Response("export default 'ok';", {
1849+
status: 200,
1850+
headers: {
1851+
"content-type": "text/javascript; charset=utf-8",
1852+
},
1853+
});
1854+
}) as typeof fetch;
1855+
1856+
try {
1857+
for (let cycle = 0; cycle < 3; cycle += 1) {
1858+
await runtime.initialize();
1859+
1860+
const diagnostics: Array<{ code?: string; message?: string }> = [];
1861+
const loader = internals.createSourceModuleLoader(undefined, diagnostics);
1862+
await loader.materializeRemoteModule(
1863+
`https://ga.jspm.io/npm:lit@3.3.0/index.js?cycle=${cycle}`,
1864+
);
1865+
1866+
assert.ok(internals.browserModuleUrlCache.size > 0);
1867+
await runtime.terminate();
1868+
1869+
assert.equal(internals.browserModuleUrlCache.size, 0);
1870+
assert.equal(internals.browserModuleInflight.size, 0);
1871+
assert.equal(internals.browserBlobUrls.size, 0);
1872+
}
1873+
} finally {
1874+
globalThis.fetch = originalFetch;
1875+
await runtime.terminate();
1876+
}
1877+
});
1878+
18261879
test("runtime enforces moduleManifest for bare component specifiers by default", async () => {
18271880
const runtime = new DefaultRuntimeManager({
18281881
moduleLoader: new MockLoader({

tests/ui.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,34 @@ test("ui renderer drops null-byte obfuscated unsafe inline styles", () => {
209209
assert.doesNotMatch(literalNullHtml, /\sstyle=/);
210210
});
211211

212+
test("ui renderer blocks additional css injection vectors", () => {
213+
const renderer = new DefaultUIRenderer();
214+
const vectors = [
215+
"@import url(https://evil.example/style.css);",
216+
"@im\\70ort url(https://evil.example/style.css);",
217+
"behavior:url(#default#time2);",
218+
"-moz-binding:url(https://evil.example/xbl.xml#payload);",
219+
];
220+
221+
for (const styleValue of vectors) {
222+
const html = renderer.renderNode(
223+
createElementNode(
224+
"div",
225+
{
226+
style: styleValue,
227+
},
228+
[createTextNode("unsafe style vector")],
229+
),
230+
);
231+
232+
assert.doesNotMatch(
233+
html,
234+
/\sstyle=/,
235+
`expected style sanitizer to block vector: ${styleValue}`,
236+
);
237+
}
238+
});
239+
212240
test("ui renderer tolerates css escape edge cases in safe styles", () => {
213241
const renderer = new DefaultUIRenderer();
214242
const safeEscapedHtml = renderer.renderNode(

0 commit comments

Comments
 (0)