fix(quick-260910-krx): Konfliktklasse ueber den generierten Client messen statt sie zu behaupten

This commit is contained in:
2026-09-11 09:16:39 +02:00
parent b286bfb1a3
commit 6e7120648b
2 changed files with 101 additions and 1 deletions
@@ -35,6 +35,8 @@ vi.mock('../prisma/prisma-tenant.extension', () => ({
forTenant: vi.fn((prisma: any, tenantId: string) => prisma.__makeBoundClient(tenantId)),
}));
import { ConflictException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { forTenant } from '../prisma/prisma-tenant.extension';
import { DashboardService } from './dashboard.service';
@@ -408,6 +410,56 @@ describe('DashboardService — Anordnung und Widgets gebunden an forTenant() (26
expectBoundCall(prisma, 'tenant-1', 'dashboardLayout', 'upsert');
});
/**
* Gegenrichtung der Bindung (w4, 260910-krx). `DashboardLayout.userId` ist
* plattformweit eindeutig, ohne Mandantenanteil. Ist die vorhandene Zeile
* unter dem gebundenen Kontext unsichtbar, laeuft das `upsert` in einen
* Konflikt — und der aeussert sich hier NICHT als der bekannte P2002-Fehler
* (`PrismaClientKnownRequestError`), sondern als
* `PrismaClientUnknownRequestError`, weil die Zeilenschutz-Regel den
* Schreibzugriff mit SQLSTATE 42501 abweist, bevor die Eindeutigkeit
* ueberhaupt geprueft wird. Gemessen in `rls-scratch-check.mjs`
* (`dashboardlayout-gebundenes-upsert-auf-unsichtbare-zeile-wirft-unknown`).
*
* Dieser Test fehlte in der ersten Lieferung — die Zusammenfassung berief
* sich auf eine nicht committete Ad-hoc-Messung. Vom Verifizierer gefunden.
*/
describe('saveLayout: Konflikt auf unsichtbare Zeile (w4)', () => {
it('uebersetzt PrismaClientUnknownRequestError in eine ConflictException statt in einen 500', async () => {
const prisma = makeFakePrisma({});
const moduleAccessService = makeFakeModuleAccessService(new Set());
const unknown = new Prisma.PrismaClientUnknownRequestError(
'Error occurred during query execution: ConnectorError(... new row violates row-level security policy ...)',
{ clientVersion: 'test' },
);
prisma.dashboardLayout.upsert = vi.fn(async () => {
throw unknown;
});
const service = new DashboardService(prisma as any, moduleAccessService as any);
await expect(
service.saveLayout('user-1', 'tenant-1', { layouts: { lg: [] } } as any),
).rejects.toBeInstanceOf(ConflictException);
});
it('reicht einen bekannten Prisma-Fehler (z.B. P2002) unveraendert durch — der ist hier NICHT der gemessene Fall', async () => {
const prisma = makeFakePrisma({});
const moduleAccessService = makeFakeModuleAccessService(new Set());
const known = new Prisma.PrismaClientKnownRequestError('Unique constraint failed', {
code: 'P2002',
clientVersion: 'test',
});
prisma.dashboardLayout.upsert = vi.fn(async () => {
throw known;
});
const service = new DashboardService(prisma as any, moduleAccessService as any);
await expect(
service.saveLayout('user-1', 'tenant-1', { layouts: { lg: [] } } as any),
).rejects.toBe(known);
});
});
it('Anordnung lesen und speichern sind GEMEINSAM gebunden: beide laufen über denselben gebundenen Klienten und dieselbe Mandantenkennung', async () => {
const prisma = makeFakePrisma({
layout: { userId: 'user-1', tenantId: 'tenant-1', layouts: {} },