feat(12-04): tender-radar notification-pref service + routes, instantAlert passthrough

- TenderNotificationPrefService: per-user digestInterval CRUD (default
  'daily', upsert on @@unique userId, D-01/D-03)
- UpdateNotificationPrefDto: @IsIn(['daily','weekly','off']) validation (V5)
- GET/PUT /modules/tender-radar/notification-pref, declared before
  @Get(':id') (route-order pitfall)
- instantAlert passthrough in Create/UpdateSavedSearchDto and
  TenderSavedSearchService.create/update (NOTIFY-02, D-04)
- All pref/profile routes scoped strictly via extractTriageContext(req),
  never from body/query (T-12-14, IDOR)
- Updated tenders.controller.spec.ts fakes for the new constructor param

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-22 09:28:02 +02:00
parent 1a0cd375c2
commit 9e7ba5353d
9 changed files with 343 additions and 1 deletions
@@ -184,4 +184,45 @@ describe('TenderSavedSearchService', () => {
expect(await service.list('u1')).toEqual([]);
});
// --- instantAlert passthrough (NOTIFY-02, D-04) ---------------------------
it('create() persists instantAlert=true when supplied', async () => {
const prisma = makeFakePrisma();
const service = new TenderSavedSearchService(prisma as any);
const result = await service.create('u1', 'tenant1', {
name: 'Bau NRW',
filters: {},
instantAlert: true,
});
expect(result.instantAlert).toBe(true);
});
it('create() leaves instantAlert unset (falls back to the Prisma column default) when omitted', async () => {
const prisma = makeFakePrisma();
const service = new TenderSavedSearchService(prisma as any);
const result = await service.create('u1', 'tenant1', {
name: 'Bau NRW',
filters: {},
});
expect(result.instantAlert).toBeUndefined();
});
it('update() persists an instantAlert toggle for an owned profile', async () => {
const prisma = makeFakePrisma();
const service = new TenderSavedSearchService(prisma as any);
const created = await service.create('u1', 'tenant1', {
name: 'Alt',
filters: {},
instantAlert: false,
});
const updated = await service.update(created.id, 'u1', { instantAlert: true });
expect(updated.instantAlert).toBe(true);
});
});