import 'reflect-metadata'; import { BadRequestException, ValidationPipe } from '@nestjs/common'; import { describe, expect, it } from 'vitest'; import { ROLES_KEY } from '../auth/decorators/roles.decorator'; import { BugReportsController } from './bug-reports.controller'; import { BugReportDto } from './dto/bug-report.dto'; /** * BugReportsController.spec — NEU (quick-260914-m97, Fehler-melden-Knopf). * * Vier Tests an der Grenze Browser -> API: * 1. die globale Pipe (`whitelist: true, transform: true`, wie in * `main.ts`) entfernt Fremdfelder wie `tenantId` (T-M97-06) und * normalisiert `errors` (multer/append-field liefert EIN Feld als * String, mehrere als Array, keins als undefined); * 2. die DTO-Grenzen greifen (31 Eintraege, 4001 Zeichen -> 400); * 3. die Route steht JEDEM angemeldeten Benutzer offen — kein * `@Roles`-Metadatum, Pfad `bug-reports`. * 4. (quick-260918-gza) die vier neuen Client-Felder bleiben optional * und laengenbegrenzt erhalten. */ const pipe = new ValidationPipe({ whitelist: true, transform: true }); const meta = { type: 'body' as const, metatype: BugReportDto }; const baseBody = { page: '/x', webVersion: 'v1', webChannel: 'beta', webCommit: '', userAgent: 'UA', viewport: '1x1', clientTime: 't', }; describe('BugReportsController (quick-260914-m97)', () => { it('Test 1: whitelist entfernt tenantId; errors wird aus String/undefined/Array normalisiert', async () => { const single = (await pipe.transform({ ...baseBody, errors: 'einzeln', tenantId: 'fremd' }, meta)) as any; expect(Object.prototype.hasOwnProperty.call(single, 'tenantId')).toBe(false); expect(single.errors).toEqual(['einzeln']); const none = (await pipe.transform({ ...baseBody }, meta)) as any; expect(none.errors).toEqual([]); const many = (await pipe.transform({ ...baseBody, errors: ['a', 'b'] }, meta)) as any; expect(many.errors).toEqual(['a', 'b']); }); it('Test 2: Grenzen — 31 Eintraege oder 4001 Zeichen -> BadRequestException; 30 Eintraege und 4000 Zeichen gelingen', async () => { const thirtyOne = Array.from({ length: 31 }, (_, i) => `e${i}`); await expect(pipe.transform({ ...baseBody, errors: thirtyOne }, meta)).rejects.toThrow(BadRequestException); await expect( pipe.transform({ ...baseBody, description: 'x'.repeat(4001) }, meta), ).rejects.toThrow(BadRequestException); const ok = (await pipe.transform( { ...baseBody, errors: thirtyOne.slice(0, 30), description: 'x'.repeat(4000) }, meta, )) as any; expect(ok.errors).toHaveLength(30); expect(ok.description).toHaveLength(4000); }); it('Test 3: nur angemeldet — kein @Roles-Metadatum auf submit, Controller-Pfad bug-reports', () => { expect(Reflect.getMetadata(ROLES_KEY, BugReportsController.prototype.submit)).toBeUndefined(); expect(Reflect.getMetadata('path', BugReportsController)).toBe('bug-reports'); }); it('Test 4 (quick-260918-gza): die vier Client-Felder bleiben optional und laengenbegrenzt erhalten', async () => { const withDesktop = (await pipe.transform( { ...baseBody, clientKind: 'desktop', clientOs: 'windows', clientVersion: '1.2.0', clientCommit: '' }, meta, )) as any; expect(withDesktop.clientKind).toBe('desktop'); expect(withDesktop.clientOs).toBe('windows'); expect(withDesktop.clientVersion).toBe('1.2.0'); expect(withDesktop.clientCommit).toBe(''); const withoutClientFields = (await pipe.transform({ ...baseBody }, meta)) as any; expect(withoutClientFields.clientKind).toBeUndefined(); await expect(pipe.transform({ ...baseBody, clientKind: 'tablet' }, meta)).rejects.toThrow(BadRequestException); await expect(pipe.transform({ ...baseBody, clientOs: 'x'.repeat(21) }, meta)).rejects.toThrow( BadRequestException, ); await expect(pipe.transform({ ...baseBody, clientVersion: 'x'.repeat(41) }, meta)).rejects.toThrow( BadRequestException, ); await expect(pipe.transform({ ...baseBody, clientCommit: 'x'.repeat(41) }, meta)).rejects.toThrow( BadRequestException, ); }); });