feat: Datenmodell Sprachnotizen (T7 L2 Task 4)

VoiceCapture und VoiceCaptureAudio nach Spec §3 mit CHECKs
(audioBytes 1..2 MiB, durationSec 1..240, CONFIRMED genau mit
inspectionId, audioMime nur bei AUDIO), Recht voice-captures:use für
ADMIN, BEEKEEPER und STAFF, Sperrreihenfolge mit VoiceCapture am Ende,
Ausschluss aus dem Backup mit Begründung, Wächter gegen Suche und
Export.

Refs #27

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-16 09:20:04 +02:00
1 parent 257286fbef
commit af9df0afe4
10 files changed
+412

No files matched your search

@@ -0,0 +1,56 @@
import { PERMISSION_CATALOG } from '../src/auth/permissions/permission-catalog';
import { PrismaService } from '../src/prisma/prisma.service';
const KEY = 'voice-captures:use';
/**
* The migration grants the T7 permission to the system roles (the boot-time
* catalog sync only grants new permissions to ADMIN). Checked against the
* migrated test database.
*/
describe('T7 voice permissions (e2e)', () => {
let prisma: PrismaService;
beforeAll(() => {
prisma = new PrismaService();
});
afterAll(async () => {
await prisma.$disconnect();
});
async function holds(roleKey: string): Promise<boolean> {
const grant = await prisma.rolePermission.findFirst({
where: { role: { key: roleKey }, permission: { key: KEY } },
});
return grant !== null;
}
it('ADMIN, BEEKEEPER and STAFF hold voice-captures:use', async () => {
expect(await holds('ADMIN')).toBe(true);
expect(await holds('BEEKEEPER')).toBe(true);
expect(await holds('STAFF')).toBe(true);
});
it('READONLY and API_CLIENT do not', async () => {
expect(await holds('READONLY')).toBe(false);
expect(await holds('API_CLIENT')).toBe(false);
});
it('matches the code catalog in the Sprache group at sortOrder 4000', async () => {
const entry = PERMISSION_CATALOG.find((e) => e.key === KEY);
const row = await prisma.permission.findUniqueOrThrow({
where: { key: KEY },
});
expect(entry).toBeDefined();
expect(row).toMatchObject({
resource: 'voice-captures',
action: 'use',
groupDe: 'Sprache',
labelDe: 'Benutzen',
sortOrder: 4000,
});
expect(entry).toMatchObject({ groupDe: 'Sprache', sortOrder: 4000 });
});
});