test(17-02): D-06 ownerTenantId tagging for RSS ingestion + seed idempotency
- 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
This commit is contained in:
@@ -306,6 +306,82 @@ describe('RssAdapter', () => {
|
||||
|
||||
expect(records).toEqual([]); // oversized feed skipped, catch-per-feed (D-01)
|
||||
});
|
||||
|
||||
describe('D-06 ownerTenantId tagging (Phase 17, Plan 02, personal vs platform-wide feeds)', () => {
|
||||
it('a feed row with a tenantId produces ONLY tagged records', async () => {
|
||||
const feeds = [
|
||||
{
|
||||
id: 'f1',
|
||||
url: 'https://personal.invalid/rss.xml',
|
||||
label: 'personal-feed',
|
||||
isActive: true,
|
||||
userId: 'user-a',
|
||||
tenantId: 'tenant-a',
|
||||
},
|
||||
];
|
||||
const prisma = makeFakePrisma(feeds);
|
||||
stubFetchResolvingXml({ 'https://personal.invalid/rss.xml': SERVICE_BUND_FIXTURE });
|
||||
const adapter = new RssAdapter(prisma);
|
||||
|
||||
const records = await adapter.fetchTenders('2026-07-23');
|
||||
|
||||
expect(records.length).toBeGreaterThan(0);
|
||||
expect(records.every((r) => r.ownerTenantId === 'tenant-a')).toBe(true);
|
||||
});
|
||||
|
||||
it('a feed row without a tenantId (platform-wide) produces ONLY untagged records', async () => {
|
||||
const feeds = [
|
||||
{
|
||||
id: 'f1',
|
||||
url: 'https://platform.invalid/rss.xml',
|
||||
label: 'platform-feed',
|
||||
isActive: true,
|
||||
userId: null,
|
||||
tenantId: null,
|
||||
},
|
||||
];
|
||||
const prisma = makeFakePrisma(feeds);
|
||||
stubFetchResolvingXml({ 'https://platform.invalid/rss.xml': SERVICE_BUND_FIXTURE });
|
||||
const adapter = new RssAdapter(prisma);
|
||||
|
||||
const records = await adapter.fetchTenders('2026-07-23');
|
||||
|
||||
expect(records.length).toBeGreaterThan(0);
|
||||
expect(records.every((r) => r.ownerTenantId === undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('two feeds of different owners are both fetched in one run; the first failing never blocks the second\'s tagged records (D-01)', async () => {
|
||||
const feeds = [
|
||||
{
|
||||
id: 'f1',
|
||||
url: 'https://broken-owner.invalid/rss.xml',
|
||||
label: 'broken-owner-feed',
|
||||
isActive: true,
|
||||
userId: 'user-a',
|
||||
tenantId: 'tenant-a',
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
url: 'https://ok-owner.invalid/rss.xml',
|
||||
label: 'ok-owner-feed',
|
||||
isActive: true,
|
||||
userId: 'user-b',
|
||||
tenantId: 'tenant-b',
|
||||
},
|
||||
];
|
||||
const prisma = makeFakePrisma(feeds);
|
||||
stubFetchResolvingXml({
|
||||
// 'broken-owner.invalid' deliberately absent -> stub resolves 404 -> throws
|
||||
'https://ok-owner.invalid/rss.xml': SERVICE_BUND_FIXTURE,
|
||||
});
|
||||
const adapter = new RssAdapter(prisma);
|
||||
|
||||
const records = await adapter.fetchTenders('2026-07-23');
|
||||
|
||||
expect(records.length).toBeGreaterThan(0);
|
||||
expect(records.every((r) => r.ownerTenantId === 'tenant-b')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('never imports or uses axios (native fetch is the sole HTTP client convention)', () => {
|
||||
|
||||
Reference in New Issue
Block a user