diff --git a/apps/api/src/tenders/source-registry.spec.ts b/apps/api/src/tenders/source-registry.spec.ts new file mode 100644 index 0000000..172a4a2 --- /dev/null +++ b/apps/api/src/tenders/source-registry.spec.ts @@ -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 { + 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(); + }); +});