7dee116254
- RssAdapter.fetchTenders tags every record from a feed with a tenantId with the same D-13 ownerTenantId origin marking email-alert records carry since Phase 14; platform-wide feeds (no tenantId) stay unmarked. The pure parseFeed mapping is untouched — tagging happens in the fan-out loop that knows which row a batch came from - Extracted the service.bund.de seed out of TendersModule.onModuleInit into seedServiceBundRssFeed() (tenders.seed.ts, same pattern as the existing seedTendersModule), so the find-then-create idempotency added in Task 1 is unit-tested directly instead of only via a Nest bootstrap - New rss-feed-migration-sql.spec.ts: text-only check of the Task 1 migration file (nullable columns, dropped/created indexes, no existing-row mutation, correct ordering) - Files modified: apps/api/src/tenders/adapters/rss.adapter.ts, apps/api/src/tenders/tenders.module.ts, apps/api/src/tenders/tenders.seed.ts, apps/api/src/tenders/adapters/rss.adapter.spec.ts, apps/api/src/tenders/tenders.seed.spec.ts, apps/api/src/tenders/rss-feed-migration-sql.spec.ts
99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
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<string, any>();
|
|
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);
|
|
});
|
|
});
|