import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { newFixture, type Fixture } from '../helpers/fixture.js'; import { JSON_BODY_LIMIT, ARTIFACT_FORM_LIMIT, BETA_REVISION_FORM_LIMIT } from '../../src/lib/server/request-body.js'; import { MAX_ARTIFACT_BYTES } from '../../src/lib/artifact-limits.js'; vi.mock('../../src/lib/server/pipeline/worker.js', () => ({ kick: vi.fn(), kickProvisioning: vi.fn() })); vi.mock('../../src/lib/server/pipeline/beta-plan-runner.js', () => ({ plannedBetaTick: vi.fn() })); type Event = { request: Request; url: URL; params: { id: string } }; const routes = import.meta.glob Promise>>( '../../src/routes/api/**/+server.ts' ); let fixture: Fixture; let scriptId: number; let betaId: number; const token = 'ec_telemetry_0123456789abcdef0123456789abcdef'; beforeEach(async () => { vi.resetModules(); vi.stubEnv('EC_TELEMETRY_TOKEN', token); fixture = await newFixture(); const { createScript } = await import('../../src/lib/server/fleet/scripts.js'); const { db } = await import('../../src/lib/server/db/index.js'); const { script, version } = createScript({ name: 'HUD', targetRelPath: 'hud.gsc', content: Buffer.from('main(){}') }); scriptId = script.id; betaId = Number( db() .prepare( "INSERT INTO betas (script_id, script_version_id, trigger_kind, state) VALUES (?, ?, 'manual', 'running')" ) .run(script.id, version.id).lastInsertRowid ); }); afterEach(async () => { vi.unstubAllEnvs(); await fixture.cleanup(); }); function oversizedRequest(method: string, limit: number, declared: boolean) { const headers: Record = { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }; if (declared) headers['Content-Length'] = String(limit + 1); return new Request('http://localhost', { method, headers, body: declared ? '{}' : ' '.repeat(limit + 1) }); } describe('JSON route size enforcement', () => { it.each([ ['autoscaling', 'POST'], ['betas', 'POST'], ['betas/[id]/promote', 'POST'], ['betas/[id]/retry', 'POST'], ['betas/[id]/test', 'POST'], ['betas/presets', 'POST'], ['blueprints', 'POST'], ['blueprints/[id]', 'PUT'], ['configs/inspect', 'POST'], ['deployment-collections', 'POST'], ['deployment-collections/preview', 'POST'], ['deployments/house-party', 'POST'], ['deployments/preview', 'POST'], ['drift', 'POST'], ['maintenance', 'POST'], ['maintenance/[id]', 'POST'], ['plugins/github/import', 'POST'], ['plutonium', 'POST'], ['provisioning', 'POST'], ['rebalancing', 'POST'], ['scripts/[id]/scope', 'POST'], ['server-keys', 'PUT'], ['server-keys', 'DELETE'], ['servers', 'POST'], ['settings', 'PUT'], ['settings', 'PATCH'], ['settings/retention', 'POST'], ['settings/retention/preview', 'POST'], ['telemetry', 'POST'], ['watchdog', 'POST'], ['watchdog/[id]', 'POST'] ])( '%s %s preserves HTTP 413 for declared and undeclared oversized bodies', async (path, method) => { if (path === 'betas/[id]/test') { const { db } = await import('../../src/lib/server/db/index.js'); db() .prepare('INSERT INTO beta_test_plans (beta_id, plan) VALUES (?, ?)') .run(betaId, '{}'); } const route = await routes[`../../src/routes/api/${path}/+server.ts`](); for (const declared of [true, false]) { await expect( route[method]({ request: oversizedRequest(method, JSON_BODY_LIMIT, declared), url: new URL('http://localhost'), params: { id: String(path.startsWith('betas/') ? betaId : scriptId) } }) ).rejects.toMatchObject({ status: 413 }); } } ); it('retains bad-request responses for malformed JSON', async () => { for (const path of ['servers', 'plugins/github/import', 'settings']) { const method = path === 'settings' ? 'PUT' : 'POST'; const route = await routes[`../../src/routes/api/${path}/+server.ts`](); const response = await route[method]({ request: new Request('http://localhost', { method, body: '{' }), url: new URL('http://localhost'), params: { id: '' } }); expect(response.status).toBe(400); } }); }); describe('artifact upload route limits', () => { it('preserves the malformed form error on beta revisions', async () => { const { POST } = await routes['../../src/routes/api/betas/[id]/revisions/+server.ts'](); await expect( POST({ request: new Request('http://localhost', { method: 'POST', body: 'invalid form' }), url: new URL('http://localhost'), params: { id: String(betaId) } }) ).rejects.toMatchObject({ status: 400, body: { message: 'Request body must be valid form data.' } }); }); it.each([ 'scripts', 'configs', 'plugins', 'scripts/inspect', 'scripts/[id]/versions', 'betas/[id]/revisions' ])('%s rejects excessive declared bodies before creating versions', async (path) => { const route = await routes[`../../src/routes/api/${path}/+server.ts`](); const limit = path.startsWith('betas/') ? BETA_REVISION_FORM_LIMIT : ARTIFACT_FORM_LIMIT; await expect( route.POST({ request: oversizedRequest('POST', limit, true), url: new URL('http://localhost'), params: { id: String(scriptId) } }) ).rejects.toMatchObject({ status: 413 }); const { db } = await import('../../src/lib/server/db/index.js'); expect(db().prepare('SELECT COUNT(*) AS count FROM script_versions').get()).toEqual({ count: 1 }); }); it('enforces the artifact limit for direct creation and version writes as well', async () => { const { createScript, uploadNewVersion, listVersions, getScriptByName } = await import('../../src/lib/server/fleet/scripts.js'); const content = Buffer.alloc(MAX_ARTIFACT_BYTES + 1); expect(() => createScript({ name: 'Too big', targetRelPath: 'large.dll', kind: 'plugin', content }) ).toThrow(/32 MiB/); expect(() => uploadNewVersion(scriptId, content)).toThrow(/32 MiB/); expect(getScriptByName('Too big')).toBeUndefined(); expect(listVersions(scriptId)).toHaveLength(1); }); });