import { PassThrough } from 'node:stream'; import { describe, expect, it, vi } from 'vitest'; import { CERT_BUILD_JSON_LIMIT, CERT_BUILD_ROUTE, certBuildBodyErrors, certBuildJsonBody, } from './cert-json-body'; import { CERT_BASENAME_MAX, CERT_PASSWORD_MAX, CERT_PEM_MAX, CERT_POOL_MAX, } from './dto/cert-build.dto'; /** PEM-aehnlicher Text: eine Zeile je 64 Zeichen, genau `chars` Zeichen lang. */ function pemLike(chars: number): string { let out = ''; while (out.length < chars) out += `${'A'.repeat(64)}\n`; return out.slice(0, chars); } interface FakeReq extends PassThrough { headers: Record; method: string; body?: unknown; } function fakeRequest(raw: string): FakeReq { const req = new PassThrough() as FakeReq; req.headers = { 'content-type': 'application/json', 'content-length': String(Buffer.byteLength(raw)), }; req.method = 'POST'; req.end(raw); return req; } function runParser(raw: string): Promise<{ req: FakeReq; error: unknown }> { const req = fakeRequest(raw); return new Promise((resolve) => { certBuildJsonBody(req as never, {} as never, (error?: unknown) => resolve({ req, error })); }); } function fakeResponse() { const res = { statusCode: 0, payload: undefined as unknown, status(code: number) { res.statusCode = code; return res; }, json(body: unknown) { res.payload = body; return res; }, }; return res; } describe('cert-json-body (D-26)', () => { it('traegt den Namen certBuildJsonBody, nie jsonParser oder urlencodedParser', () => { expect(certBuildJsonBody.name).toBe('certBuildJsonBody'); expect(CERT_BUILD_ROUTE).toBe('/modules/cert-manager/build'); expect(CERT_BUILD_JSON_LIMIT).toBe(512 * 1024); }); it('die groesste Anfrage innerhalb der DTO-Grenzen bleibt unter dem Grenzwert und wird gelesen', async () => { const body = { content: 'fullchain', certPem: pemLike(CERT_PEM_MAX), poolPems: Array.from({ length: CERT_POOL_MAX }, () => pemLike(CERT_PEM_MAX)), keyPem: pemLike(CERT_PEM_MAX), csrPem: pemLike(CERT_PEM_MAX), password: 'p'.repeat(CERT_PASSWORD_MAX), baseName: 'b'.repeat(CERT_BASENAME_MAX), }; const raw = JSON.stringify(body); expect(Buffer.byteLength(raw)).toBeLessThan(CERT_BUILD_JSON_LIMIT); const { req, error } = await runParser(raw); expect(error).toBeUndefined(); expect((req.body as typeof body).poolPems).toHaveLength(CERT_POOL_MAX); }); it('eine Anfrage ueber 512 KiB wird mit 413 und Code tooLarge beantwortet', async () => { const { error } = await runParser(JSON.stringify({ content: 'x'.repeat(600 * 1024) })); const res = fakeResponse(); const next = vi.fn(); certBuildBodyErrors(error, {} as never, res as never, next); expect(res.statusCode).toBe(413); expect(res.payload).toMatchObject({ code: 'tooLarge' }); expect(typeof (res.payload as { message: string }).message).toBe('string'); expect(next).not.toHaveBeenCalled(); }); it('fehlerhaftes JSON ergibt 400 mit Code invalidInput', async () => { const { error } = await runParser('{"content": '); const res = fakeResponse(); const next = vi.fn(); certBuildBodyErrors(error, {} as never, res as never, next); expect(res.statusCode).toBe(400); expect(res.payload).toMatchObject({ code: 'invalidInput' }); expect(next).not.toHaveBeenCalled(); }); it('jeden anderen Fehler reicht die Funktion unveraendert weiter', () => { const other = new Error('boom'); const res = fakeResponse(); const next = vi.fn(); certBuildBodyErrors(other, {} as never, res as never, next); expect(next).toHaveBeenCalledWith(other); expect(res.statusCode).toBe(0); }); });