test(11-05): add failing TenderTriageService spec + TenderTriage schema/migration

RED phase (TDD) for UI-03/04 per-user triage (gelesen/ungelesen, Favorit).
Adds the TenderTriage Prisma model (userId-scoped, onDelete: Cascade to
Tender per Pitfall 6) and its migration, plus a failing spec proving
upsert idempotency, strict userId scoping (V4/IDOR, T-11-10), and cascade
consistency — service implementation follows in the GREEN commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 16:28:38 +02:00
parent 4c22d7731a
commit 7bb695d37a
3 changed files with 203 additions and 0 deletions
+24
View File
@@ -290,6 +290,30 @@ model Tender {
@@index([bundesland]) // FILTER-02: post-backfill Bundesland-Filter-Performance
@@index([cpvDivisions], type: Gin) // FILTER-03: post-backfill CPV-Divisions-Filter-Performance (hasSome)
// Deliberately NO tenant column and NO tenant index — this is global data (D-03)
triage TenderTriage[]
}
// UI-03/04 — per-user Triage-Zustand pro Tender (gelesen/ungelesen, Favorit).
// Scoping-Muster wie FavoriteLink (T-08-06, Pitfall 4): userId-Scoping im
// Service, KEIN forTenant()/RLS — RLS existiert nur für Auth-Kerntabellen.
// tenantId wird zusätzlich mitgeführt (spätere Tenant-Isolation, T-11-12),
// ist aber NICHT das Scoping-Feld — jede Query filtert auf userId.
model TenderTriage {
id String @id @default(uuid())
userId String
tenantId String
tenderId String
isRead Boolean @default(false)
isFavorite Boolean @default(false)
readAt DateTime?
favoritedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tender Tender @relation(fields: [tenderId], references: [id], onDelete: Cascade)
@@unique([userId, tenderId]) // Upsert-Target (setTriage); ein Triage-Row je (user,tender)
@@index([userId])
@@index([tenderId])
}
// Singleton-per-source admin poll config (INGEST-06 foundation).