import { test } from 'node:test' import assert from 'node:assert/strict' import { MAX_TEMPLATE_DEPTH, MAX_TEMPLATE_SOURCE_LENGTH, renderTemplate } from '../template' // 设计稿 §10.1 的「边界/性能」组:单文件上限是 1 MiB(§6.2),引擎不能在这个量级上炸或卡死。 // 后面几组钉住审查 [11-1] 要求的安全阀:嵌套太深 / 体积太大一律降级成结构化 error,不抛异常。 test('1 MiB 量级的模板能正常渲染', () => { const filler = 'x'.repeat(1024) const source = `${filler}\n`.repeat(1024) + '{{ title }}\n' assert.ok(source.length > 1024 * 1024, '构造的模板要超过 1 MiB') const result = renderTemplate(source, { title: '超长模板' }) assert.equal(result.error, null) assert.equal(result.html.endsWith('超长模板\n'), true) assert.deepEqual(result.warnings, []) }) test('1000 项循环:结果正确,同一处缺失路径只留一条警告', () => { const rows = Array.from({ length: 1000 }, (_, i) => ({ n: i })) const result = renderTemplate('{{#each rows}}{{ this.n }};{{/each}}', { rows }) assert.equal(result.error, null) assert.equal(result.html.startsWith('0;1;2;'), true) assert.equal(result.html.endsWith('999;'), true) const missing = renderTemplate('{{#each rows}}{{ this.absent }}{{/each}}', { rows }) assert.equal(missing.html, '') assert.equal(missing.warnings.length, 1) assert.equal(missing.warnings[0].count, 1000) }) test('深嵌套 #each/#if(30 层)不炸,作用域逐层正确', () => { const depth = 30 let source = '' const root: Record = { name: 'root' } let cursor = root for (let i = 0; i < depth; i += 1) { source += `{{#each level${i}}}` const inner: Record = { name: `n${i}` } cursor[`level${i}`] = [inner] cursor = inner } source += '{{ this.name }}' for (let i = 0; i < depth; i += 1) source += '{{/each}}' const result = renderTemplate(source, root) assert.equal(result.error, null) assert.equal(result.html, `n${depth - 1}`) }) test('10 层嵌套 #if 渲染正确,没有警告(人工模板的正常量级)', () => { const depth = 10 const source = '{{#if ok}}'.repeat(depth) + '{{ title }}' + '{{/if}}'.repeat(depth) const result = renderTemplate(source, { ok: true, title: '十层' }) assert.equal(result.error, null) assert.equal(result.html, '十层') assert.deepEqual(result.warnings, []) }) test(`正好 ${MAX_TEMPLATE_DEPTH} 层照常渲染,多一层降级成 too-deep`, () => { const openTag = '{{#if ok}}' const wrap = (depth: number): string => openTag.repeat(depth) + 'x' + '{{/if}}'.repeat(depth) const atLimit = renderTemplate(wrap(MAX_TEMPLATE_DEPTH), { ok: true }) assert.equal(atLimit.error, null) assert.equal(atLimit.html, 'x') const overLimit = renderTemplate(wrap(MAX_TEMPLATE_DEPTH + 1), { ok: true }) assert.equal(overLimit.error?.code, 'too-deep') // 位置指向超出的那个 {{#if}}(全在一行) assert.equal(overLimit.error?.line, 1) assert.equal(overLimit.error?.offset, MAX_TEMPLATE_DEPTH * openTag.length) assert.equal(overLimit.error?.column, overLimit.error!.offset + 1) assert.ok(overLimit.error?.message.includes(String(MAX_TEMPLATE_DEPTH))) assert.equal(overLimit.html, '') assert.deepEqual(overLimit.warnings, []) }) test('审查 [11-1] 的复现输入(3000 层 {{#if}},约 48 KB)不抛异常,降级为 too-deep', () => { const depth = 3000 const source = '{{#if a}}'.repeat(depth) + 'x' + '{{/if}}'.repeat(depth) assert.doesNotThrow(() => renderTemplate(source, { a: true })) const result = renderTemplate(source, { a: true }) assert.equal(result.error?.code, 'too-deep') assert.equal(result.html, '') assert.deepEqual(result.warnings, []) }) test('超深 {{#each}} 同样降级(两种块共用同一个层数计数器)', () => { const depth = MAX_TEMPLATE_DEPTH + 1 const source = '{{#each rows}}'.repeat(depth) + 'x' + '{{/each}}'.repeat(depth) assert.doesNotThrow(() => renderTemplate(source, { rows: [] })) assert.equal(renderTemplate(source, { rows: [] }).error?.code, 'too-deep') }) test(`体积超过 ${MAX_TEMPLATE_SOURCE_LENGTH}:不解析、不抛异常,降级为 too-large`, () => { const source = `{{ a }}${'x'.repeat(MAX_TEMPLATE_SOURCE_LENGTH)}` assert.doesNotThrow(() => renderTemplate(source, { a: 1 })) const result = renderTemplate(source, { a: 1 }) assert.equal(result.error?.code, 'too-large') assert.equal(result.error?.line, 1) assert.equal(result.error?.column, 1) assert.equal(result.error?.offset, 0) assert.equal(result.html, '') assert.deepEqual(result.warnings, []) }) test('体积正好等于上限:照常解析(上限是「超过才拒」)', () => { const source = '{{ a }}'.padEnd(MAX_TEMPLATE_SOURCE_LENGTH, 'x') assert.equal(source.length, MAX_TEMPLATE_SOURCE_LENGTH) const result = renderTemplate(source, { a: 1 }) assert.equal(result.error, null) assert.equal(result.html.startsWith('1'), true) })