Files
tessera-ctl/apps/api/src/tenders/source-registry.spec.ts
T
schalli 78b17ef28b test(13-02): add failing SourceRegistry denylist-gate spec
RED — proves Erfolgskriterium 4 (INGEST-07): registering an adapter
whose portals include vergabe24 or aumass must throw DeniedPortalError,
including a mixed portals array with one denylisted entry. Also covers
legitimate register/get/activeAdapters happy paths. Fake adapter stub,
no real scraping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 08:42:51 +02:00

74 lines
2.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { RawTenderRecord, SourceType } from './tender.types';
import type { TenderSourceAdapter } from './adapters/tender-source-adapter.interface';
import { DeniedPortalError, SourceRegistry } from './source-registry';
/**
* Fake adapter stub — no real scraping/HTTP, just satisfies the
* TenderSourceAdapter contract for registry-behavior tests.
*/
function fakeAdapter(
sourceType: SourceType,
portals: readonly string[],
): TenderSourceAdapter {
return {
sourceType,
portals,
async fetchTenders(_dayCursor: string): Promise<RawTenderRecord[]> {
return [];
},
};
}
describe('SourceRegistry', () => {
it('throws DeniedPortalError when registering an adapter serving vergabe24', () => {
const registry = new SourceRegistry();
const adapter = fakeAdapter('ai-netserver', ['vergabe24']);
expect(() => registry.register(adapter)).toThrow(DeniedPortalError);
});
it('throws DeniedPortalError when registering an adapter serving aumass', () => {
const registry = new SourceRegistry();
const adapter = fakeAdapter('cosinex-dtvp', ['aumass']);
expect(() => registry.register(adapter)).toThrow(DeniedPortalError);
});
it('rejects a mixed portals array wholesale when one entry is denylisted', () => {
const registry = new SourceRegistry();
const adapter = fakeAdapter('ai-netserver', ['tender24', 'aumass']);
expect(() => registry.register(adapter)).toThrow(DeniedPortalError);
expect(registry.get('ai-netserver')).toBeUndefined();
});
it('registers a legitimate adapter and returns it via get()', () => {
const registry = new SourceRegistry();
const adapter = fakeAdapter('ai-netserver', ['tender24']);
expect(() => registry.register(adapter)).not.toThrow();
expect(registry.get('ai-netserver')).toBe(adapter);
});
it('activeAdapters() returns all registered adapters', () => {
const registry = new SourceRegistry();
const doe = fakeAdapter('doe-opendata', ['doe-opendata']);
const netserver = fakeAdapter('ai-netserver', ['tender24']);
registry.register(doe);
registry.register(netserver);
expect(registry.activeAdapters()).toHaveLength(2);
expect(registry.activeAdapters()).toEqual(
expect.arrayContaining([doe, netserver]),
);
});
it('get() returns undefined for an unregistered sourceType (no throw)', () => {
const registry = new SourceRegistry();
expect(registry.get('cosinex-dtvp')).toBeUndefined();
});
});