fix(quick-260910-krx): Konfliktklasse ueber den generierten Client messen statt sie zu behaupten
This commit is contained in:
@@ -2451,7 +2451,9 @@ async function runDashboardAreaChecks(adminUrl, scratchRoleUrl, results) {
|
||||
id text PRIMARY KEY,
|
||||
"userId" text NOT NULL UNIQUE,
|
||||
"tenantId" text NOT NULL,
|
||||
layouts jsonb NOT NULL DEFAULT '{}'::jsonb
|
||||
layouts jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
"createdAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
await db.$executeRawUnsafe(`
|
||||
@@ -2604,6 +2606,52 @@ async function runDashboardAreaChecks(adminUrl, scratchRoleUrl, results) {
|
||||
conflictDetail,
|
||||
);
|
||||
|
||||
// 5b: dashboardlayout-gebundenes-upsert-auf-unsichtbare-zeile-wirft-unknown
|
||||
//
|
||||
// Pruefung 5 misst mit Roh-SQL, WAS die Datenbank tut (42501). Der
|
||||
// Anwendungscode in `saveLayout` faengt aber nicht SQLSTATEs, sondern die
|
||||
// Fehlerklasse, die der GENERIERTE Prisma-Client daraus macht — und die
|
||||
// ist nicht dieselbe wie beim P2002-Fall der Bereiche `tenders`/`user`
|
||||
// (`PrismaClientKnownRequestError`), sondern
|
||||
// `PrismaClientUnknownRequestError`, weil die Regel den Schreibzugriff
|
||||
// abweist, bevor eine Eindeutigkeit ueberhaupt geprueft wird. Genau
|
||||
// DIESE Klasse muss `saveLayout` uebersetzen; eine Uebersetzung der
|
||||
// falschen Klasse spraenge nie an.
|
||||
//
|
||||
// Diese Messung fehlte in der ersten Lieferung von 260910-krx — die
|
||||
// Zusammenfassung berief sich auf eine nicht committete Ad-hoc-Messung.
|
||||
// Vom Verifizierer gefunden, hier nachgereicht: derselbe Vorgang wie in
|
||||
// Pruefung 5, aber ueber `bound.dashboardLayout.upsert(...)` auf dem
|
||||
// gebundenen generierten Client, und gepruegt wird der KONSTRUKTORNAME
|
||||
// des geworfenen Fehlers.
|
||||
let upsertThrewUnknown = false;
|
||||
let upsertDetail = '';
|
||||
try {
|
||||
const bound = buildInlineExtendedClient(prisma, 'TENANT-A');
|
||||
await bound.dashboardLayout.upsert({
|
||||
where: { userId: 'user-conflict' },
|
||||
update: { layouts: {} },
|
||||
create: {
|
||||
id: 'layout-conflict-attempt-upsert',
|
||||
userId: 'user-conflict',
|
||||
tenantId: 'TENANT-A',
|
||||
layouts: {},
|
||||
},
|
||||
});
|
||||
upsertDetail =
|
||||
'gebundenes dashboardLayout.upsert unter TENANT-A auf die unter TENANT-B unsichtbare Zeile (user-conflict) ist NICHT fehlgeschlagen';
|
||||
} catch (err) {
|
||||
const ctor = err?.constructor?.name ?? 'unbekannt';
|
||||
upsertThrewUnknown = ctor === 'PrismaClientUnknownRequestError';
|
||||
upsertDetail = `gebundenes dashboardLayout.upsert unter TENANT-A auf die unter TENANT-B unsichtbare Zeile wirft ${ctor}${err?.code ? ` (code ${err.code})` : ''} — saveLayout uebersetzt genau diese Klasse; ${upsertThrewUnknown ? 'stimmt mit dem Anwendungscode ueberein' : 'STIMMT NICHT mit dem Anwendungscode ueberein, die Uebersetzung in saveLayout spraenge nie an'}`;
|
||||
}
|
||||
report(
|
||||
results,
|
||||
'dashboardlayout-gebundenes-upsert-auf-unsichtbare-zeile-wirft-unknown',
|
||||
upsertThrewUnknown,
|
||||
upsertDetail,
|
||||
);
|
||||
|
||||
// 6: widgetinstance-gebundenes-einfuegen-fremder-mandant-abgelehnt
|
||||
let foreignWidgetInsertRejected = false;
|
||||
let foreignWidgetInsertDetail = '';
|
||||
|
||||
@@ -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: {} },
|
||||
|
||||
Reference in New Issue
Block a user