fix(quick-260909-laa): dritte Eindeutigkeitsverletzung uebersetzen statt sie zu behaupten
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { ConflictException } from '@nestjs/common';
|
||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { TenderTriageService } from './tender-triage.service';
|
import { TenderTriageService } from './tender-triage.service';
|
||||||
|
|
||||||
@@ -207,4 +208,49 @@ describe('TenderTriageService', () => {
|
|||||||
expectBoundCall(prisma, 't1', 'findMany');
|
expectBoundCall(prisma, 't1', 'findMany');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gegenrichtung der Bindung (Befund F, 260909-laa). Der Eindeutigkeits-
|
||||||
|
* schluessel `@@unique([userId, tenderId])` traegt keine Mandanten-
|
||||||
|
* dimension. Ist die vorhandene Zeile unter dem gebundenen Kontext
|
||||||
|
* unsichtbar, findet das `upsert` sie nicht, versucht anzulegen und
|
||||||
|
* laeuft in die Eindeutigkeitsverletzung — aus stillem Ueberschreiben
|
||||||
|
* wird ein harter Fehler.
|
||||||
|
*
|
||||||
|
* Diese Pruefung fehlte in der ersten Lieferung von 260909-laa: die
|
||||||
|
* Zusammenfassung behauptete die Uebersetzung fuer ALLE DREI
|
||||||
|
* mandantenlosen Eindeutigkeitsschluessel, gebaut war sie nur fuer zwei.
|
||||||
|
* Vom Verifizierer gefunden, hier nachgereicht.
|
||||||
|
*/
|
||||||
|
describe('P2002 auf mandantenlosem Eindeutigkeitsschluessel (Befund F)', () => {
|
||||||
|
it('setTriage() uebersetzt die Eindeutigkeitsverletzung in eine ConflictException statt in einen 500', async () => {
|
||||||
|
const prisma = makeFakePrisma();
|
||||||
|
const verletzung: any = new Error(
|
||||||
|
'Unique constraint failed on the fields: (`userId`,`tenderId`)',
|
||||||
|
);
|
||||||
|
verletzung.code = 'P2002';
|
||||||
|
prisma.tenderTriage.upsert = vi.fn(async () => {
|
||||||
|
throw verletzung;
|
||||||
|
});
|
||||||
|
const service = new TenderTriageService(prisma as any);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.setTriage('u1', 't1', 'tender-x', { isFavorite: true }),
|
||||||
|
).rejects.toBeInstanceOf(ConflictException);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setTriage() reicht jeden anderen Datenbankfehler unveraendert durch', async () => {
|
||||||
|
const prisma = makeFakePrisma();
|
||||||
|
const anderer: any = new Error('Verbindung verloren');
|
||||||
|
anderer.code = 'P1001';
|
||||||
|
prisma.tenderTriage.upsert = vi.fn(async () => {
|
||||||
|
throw anderer;
|
||||||
|
});
|
||||||
|
const service = new TenderTriageService(prisma as any);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.setTriage('u1', 't1', 'tender-x', { isFavorite: true }),
|
||||||
|
).rejects.toBe(anderer);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { ConflictException, Injectable } from '@nestjs/common';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { forTenant } from '../prisma/prisma-tenant.extension';
|
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||||
|
|
||||||
@@ -39,6 +39,17 @@ export class TenderTriageService {
|
|||||||
* with the same params never creates a second row. Only the fields
|
* with the same params never creates a second row. Only the fields
|
||||||
* present in `dto` are touched; the other flag (and its timestamp) is
|
* present in `dto` are touched; the other flag (and its timestamp) is
|
||||||
* left as-is on both the update and create branches.
|
* left as-is on both the update and create branches.
|
||||||
|
*
|
||||||
|
* Gegenrichtung der Bindung (Befund F, 260909-laa): der Eindeutigkeits-
|
||||||
|
* schluessel `@@unique([userId, tenderId])` traegt KEINE Mandanten-
|
||||||
|
* dimension. Ist die vorhandene Zeile unter dem gebundenen Kontext
|
||||||
|
* unsichtbar (veralteter Mandant in der Sitzung), findet das `upsert`
|
||||||
|
* sie nicht, versucht anzulegen und laeuft in die Eindeutigkeits-
|
||||||
|
* verletzung — aus stillem Ueberschreiben wird ein harter Fehler. Der
|
||||||
|
* `P2002`-Zweig uebersetzt das in eine verstaendliche deutsche Meldung
|
||||||
|
* statt in einen 500, wie in `tender-saved-search.service.ts`. Gemessen
|
||||||
|
* in `rls-scratch-check.mjs`, Pruefung
|
||||||
|
* `tendertriage-einfuegen-auf-unsichtbare-zeile-verletzt-eindeutigkeit`.
|
||||||
*/
|
*/
|
||||||
async setTriage(
|
async setTriage(
|
||||||
userId: string,
|
userId: string,
|
||||||
@@ -59,7 +70,8 @@ export class TenderTriageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tenantPrisma = forTenant(this.prisma, tenantId) as any;
|
const tenantPrisma = forTenant(this.prisma, tenantId) as any;
|
||||||
return tenantPrisma.tenderTriage.upsert({
|
try {
|
||||||
|
return await tenantPrisma.tenderTriage.upsert({
|
||||||
where: { userId_tenderId: { userId, tenderId } },
|
where: { userId_tenderId: { userId, tenderId } },
|
||||||
update,
|
update,
|
||||||
create: {
|
create: {
|
||||||
@@ -72,6 +84,14 @@ export class TenderTriageService {
|
|||||||
favoritedAt: dto.isFavorite ? now : null,
|
favoritedAt: dto.isFavorite ? now : null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.code === 'P2002') {
|
||||||
|
throw new ConflictException(
|
||||||
|
'Der Bearbeitungsstand zu dieser Ausschreibung konnte nicht gespeichert werden. Bitte die Seite neu laden und es erneut versuchen.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user