54121c1721
- SmtpConfig.bugReportRecipient (nullable, additive Migration 20260914170000), DTO @IsOptional @IsEmail, SAFE_SELECT, getBugReportRecipient gebunden - MailService: Versandkern deliver (wirft, Anhaenge), sendViaTenantTransport bleibt verschluckender Mantel (T-02-12), sendBugReport laesst Fehler durch - POST /bug-reports: Multipart 4 MiB je Route, alle angemeldeten Rollen, Drossel 5/10 min -> 429, PNG-Signatur -> 400, kein Empfaenger -> 409, Versandfehler -> 502, eine Protokollzeile - Falsifizierungen (a)-(d) als Specs; @Expose() im DTO, damit errors auch bei fehlendem Feld zu [] wird - Doku-Zeile fuer rls-access-inventory, TESSERA_BUGREPORT_TO in docker-compose.prod.yml Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
67 lines
2.8 KiB
TypeScript
67 lines
2.8 KiB
TypeScript
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).
|
|
*
|
|
* Drei 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`.
|
|
*/
|
|
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');
|
|
});
|
|
});
|