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>
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user