feat(14-02): add RssAdapter.parseFeed + 'rss' normalizer dispatch
Fixture-first RSS parsing (INGEST-04): parses live-captured
service.bund.de (pubDate present, numeric-HTML-entity titles) and
subreport-elvis (pubDate absent, CDATA titles) feed shapes into
RawTenderRecord[] via fast-xml-parser, mirroring the DoeOpenDataAdapter
config. SourceType extended with 'rss'; normalize() dispatches 'rss'
through the existing normalizeBag() path unchanged (D-04/D-05).
Rule 1 fix: fast-xml-parser only decodes the 5 predefined XML entities,
not numeric character references — added an explicit decode step so
service.bund.de titles ("Übermittlung...") render correctly
instead of leaking raw entity syntax.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -81,7 +81,11 @@ function bagRecord(
|
||||
},
|
||||
): RawTenderRecord {
|
||||
const sourcePortal =
|
||||
sourceType === 'cosinex-dtvp' ? 'cosinex-dtvp' : 'tender24';
|
||||
sourceType === 'cosinex-dtvp'
|
||||
? 'cosinex-dtvp'
|
||||
: sourceType === 'rss'
|
||||
? 'service-bund'
|
||||
: 'tender24';
|
||||
return {
|
||||
sourceType,
|
||||
sourcePortal,
|
||||
@@ -317,4 +321,38 @@ describe('TenderNormalizerService', () => {
|
||||
expect(normalized.contentHash).toMatch(/^[a-f0-9]{64}$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("'rss' sourceType (Phase 14, Plan 02, INGEST-04)", () => {
|
||||
it('maps an rss bag through normalizeBag(): title passes through, cpvDivisions stays empty', () => {
|
||||
const raw = bagRecord('rss', {
|
||||
title: 'Übermittlung von Bohrungsdaten',
|
||||
buyerName: null,
|
||||
procedureType: null,
|
||||
legalFramework: null,
|
||||
deadlineAt: null,
|
||||
});
|
||||
|
||||
const normalized = service.normalize(raw);
|
||||
|
||||
expect(normalized.title).toBe('Übermittlung von Bohrungsdaten');
|
||||
expect(normalized.cpvDivisions).toEqual([]);
|
||||
expect(normalized.cpvCodes).toEqual([]);
|
||||
expect(normalized.region).toBeNull();
|
||||
expect(normalized.bundesland).toBeNull();
|
||||
expect(normalized.buyerName).toBeNull();
|
||||
expect(normalized.procedureType).toBeNull();
|
||||
expect(normalized.deadlineAt).toBeNull();
|
||||
expect(normalized.dedupKey).toBe(
|
||||
`${raw.sourcePortal}:${raw.sourceNoticeId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('rss title falls back to "Unbenannte Ausschreibung" when empty', () => {
|
||||
const raw = bagRecord('rss', { title: '' });
|
||||
|
||||
const normalized = service.normalize(raw);
|
||||
|
||||
expect(normalized.title).toBe('Unbenannte Ausschreibung');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user