import { describe, expect, it, vi } from 'vitest'; import { SERVICE_BUND_RSS_URL, seedServiceBundRssFeed, seedTendersModule } from './tenders.seed'; describe('seedTendersModule', () => { it('calls moduleRegistryService.seedModule once with tender-radar slug', async () => { const mockSeedModule = vi.fn().mockResolvedValue(undefined); const mockModuleRegistryService = { seedModule: mockSeedModule } as any; await seedTendersModule(mockModuleRegistryService); expect(mockSeedModule).toHaveBeenCalledTimes(1); const arg = mockSeedModule.mock.calls[0][0]; expect(arg.slug).toBe('tender-radar'); expect(arg.isSystem).toBe(true); expect(arg.description).toHaveProperty('de'); expect(arg.description).toHaveProperty('en'); }); }); /** * seedServiceBundRssFeed.spec — proves the Phase 17, Plan 02 (D-02) fix: * "upsert on url" stopped compiling once the unique index moved to * (userId, url) — Prisma's generated compound-unique input type requires a * plain `string` userId, which a platform-wide row (userId = null) can * never provide. Uses "find a platform-wide row with this url first, only * create if none exists" instead. In-memory fake, no live DB connection. */ function makeFakePrisma() { const rows = new Map(); let seq = 0; return { tenderRssFeedSource: { findFirst: async ({ where }: any) => { for (const row of rows.values()) { if ( row.userId === where.userId && row.url === where.url ) { return row; } } return null; }, create: async ({ data }: any) => { seq += 1; const row = { id: `feed-${seq}`, userId: null, tenantId: null, ...data }; rows.set(row.id, row); return row; }, }, __rows: rows, }; } describe('seedServiceBundRssFeed', () => { it('creates a single active, ownerless service.bund.de row when none exists yet', async () => { const prisma = makeFakePrisma(); await seedServiceBundRssFeed(prisma as any); expect(prisma.__rows.size).toBe(1); const [row] = [...prisma.__rows.values()]; expect(row.url).toBe(SERVICE_BUND_RSS_URL); expect(row.label).toBe('service-bund'); expect(row.isActive).toBe(true); expect(row.userId).toBeNull(); }); it('running the seed twice in a row (simulating a second app start) leaves exactly one ownerless row (D-02, idempotent)', async () => { const prisma = makeFakePrisma(); await seedServiceBundRssFeed(prisma as any); await seedServiceBundRssFeed(prisma as any); expect(prisma.__rows.size).toBe(1); const [row] = [...prisma.__rows.values()]; expect(row.userId).toBeNull(); }); it('does not touch an existing personal feed at the same URL owned by a real user', async () => { const prisma = makeFakePrisma(); prisma.__rows.set('personal-1', { id: 'personal-1', userId: 'user-a', tenantId: 'tenant-a', url: SERVICE_BUND_RSS_URL, label: 'mein-service-bund-feed', isActive: true, }); await seedServiceBundRssFeed(prisma as any); expect(prisma.__rows.size).toBe(2); const platformRows = [...prisma.__rows.values()].filter((r: any) => r.userId === null); expect(platformRows).toHaveLength(1); }); });