Enforce output file count and size limits in the Pyodide worker

GitOrigin-RevId: 2cc61613381243d810a8cb9e1c2c32fa9e751da7
This commit is contained in:
Domagoj Kriskovic
2026-05-07 08:06:46 +00:00
committed by Copybot
parent d97a659f92
commit 672e01c703
3 changed files with 208 additions and 20 deletions
@@ -0,0 +1,115 @@
import { expect } from 'chai'
import {
MAX_OUTPUT_FILES,
MAX_OUTPUT_FILE_BYTES,
MAX_OUTPUT_TOTAL_BYTES,
checkOutputLimits,
} from '@/features/ide-react/components/editor/python/pyodide-worker-output-limits'
const BYTES_PER_MB = 1024 * 1024
function makeFiles(
count: number,
sizePerFile: number
): { path: string; size: number }[] {
return Array.from({ length: count }, (_, i) => ({
path: `/project/file${i}.bin`,
size: sizePerFile,
}))
}
describe('checkOutputLimits', function () {
it('returns null when both limits are within bounds', function () {
const files = makeFiles(10, 1024)
expect(checkOutputLimits(files)).to.equal(null)
})
it('returns null for an empty file list', function () {
expect(checkOutputLimits([])).to.equal(null)
})
it('returns null at exactly the file count limit', function () {
const files = makeFiles(MAX_OUTPUT_FILES, 1024)
expect(checkOutputLimits(files)).to.equal(null)
})
it('returns a count violation when the file count exceeds the limit', function () {
const files = makeFiles(MAX_OUTPUT_FILES + 1, 1)
const violation = checkOutputLimits(files)
expect(violation).to.deep.equal({
kind: 'count',
message: `Output limit exceeded: ${MAX_OUTPUT_FILES + 1} files generated (max ${MAX_OUTPUT_FILES})`,
})
})
it('reports the actual file count in the count violation message', function () {
const files = makeFiles(73, 1)
const violation = checkOutputLimits(files)
expect(violation).to.not.equal(null)
expect(violation!.kind).to.equal('count')
expect(violation!.message).to.equal(
'Output limit exceeded: 73 files generated (max 50)'
)
})
it('returns null at exactly the per-file size limit', function () {
const files = [{ path: '/project/big.bin', size: MAX_OUTPUT_FILE_BYTES }]
expect(checkOutputLimits(files)).to.equal(null)
})
it('returns a single-file-size violation when one file exceeds the per-file limit', function () {
const files = [
{ path: '/project/big.bin', size: MAX_OUTPUT_FILE_BYTES + 1 },
]
const violation = checkOutputLimits(files)
expect(violation).to.not.equal(null)
expect(violation!.kind).to.equal('single-file-size')
expect(violation!.message).to.equal(
'Output limit exceeded: /project/big.bin is 51MB (max 50MB per file)'
)
})
it('reports the offending file path and rounded size in the single-file-size message', function () {
const files = [
{ path: '/project/small.bin', size: 1 * BYTES_PER_MB },
{ path: '/project/huge.bin', size: 80 * BYTES_PER_MB },
]
const violation = checkOutputLimits(files)
expect(violation).to.deep.equal({
kind: 'single-file-size',
message:
'Output limit exceeded: /project/huge.bin is 80MB (max 50MB per file)',
})
})
it('returns a total-output-size violation when the summed size exceeds the total limit', function () {
const files = [
{ path: '/project/a.bin', size: 40 * BYTES_PER_MB },
{ path: '/project/b.bin', size: 40 * BYTES_PER_MB },
{ path: '/project/c.bin', size: 40 * BYTES_PER_MB },
]
const violation = checkOutputLimits(files)
expect(violation).to.not.equal(null)
expect(violation!.kind).to.equal('total-output-size')
expect(violation!.message).to.equal(
'Output limit exceeded: 120MB total (max 100MB)'
)
})
it('returns null at exactly the total size limit', function () {
const files = [
{ path: '/project/a.bin', size: 50 * BYTES_PER_MB },
{ path: '/project/b.bin', size: 50 * BYTES_PER_MB },
]
expect(checkOutputLimits(files)).to.equal(null)
const total = files.reduce((acc, f) => acc + f.size, 0)
expect(total).to.equal(MAX_OUTPUT_TOTAL_BYTES)
})
it('reports the count violation when both limits are exceeded', function () {
const files = makeFiles(MAX_OUTPUT_FILES + 10, MAX_OUTPUT_TOTAL_BYTES)
const violation = checkOutputLimits(files)
expect(violation).to.not.equal(null)
expect(violation!.kind).to.equal('count')
})
})