import { DyNTS_AI_CostEvent } from '../_models/interfaces/dynts-ai-cost-event.interface'; import { DyNTS_AI_CostEventCallback } from '../_models/interfaces/dynts-ai-cost-event-callback.interface'; import { DyNTS_AI_Provider_ServiceBase } from './ai-provider.service-base'; import { DyFM_AI_Provider, DyFM_AI_ProviderCapabilities, DyFM_AI_Config } from '@futdevpro/fsm-dynamo/ai'; class TestProviderService extends DyNTS_AI_Provider_ServiceBase { readonly aiProvider: DyFM_AI_Provider = DyFM_AI_Provider.OpenAI; readonly capabilities: DyFM_AI_ProviderCapabilities = { chat: true, embeddings: true, imageGeneration: false, vision: false, audioGeneration: false, audioAnalysis: false, functionCalling: false, streaming: false, batchOperations: false, supportedModelTypes: [], }; setup = (config: DyFM_AI_Config): void => { // Mock implementation }; testConnection = async (issuer: string): Promise => { return true; }; /** Test-only helper: ráad egy callback-et a protected onCostEvent fielden át. */ public setOnCostEventForTest(cb: DyNTS_AI_CostEventCallback | undefined): void { this.onCostEvent = cb; } /** Test-only helper: meghívja a protected emitCostEvent-et (a sub-class amúgy is hívná). */ public emitForTest(event: DyNTS_AI_CostEvent): void { this.emitCostEvent(event); } static getInstance(): TestProviderService { return TestProviderService.getSingletonInstance(); } } /** Helper: minimal valid DyNTS_AI_CostEvent. */ function makeCostEvent(overrides: Partial = {}): DyNTS_AI_CostEvent { return { callType: 'embedding-single', provider: 'openai', model: 'text-embedding-3-small', tokensUsed: { input: 10, total: 10 }, durationMs: 42, issuer: 'test-issuer', timestamp: new Date(), ...overrides, }; } describe('| DyNTS_AI_Provider_ServiceBase', () => { let service: TestProviderService; beforeEach(() => { service = TestProviderService.getInstance(); }); it('| should be a singleton instance', () => { const instance1 = TestProviderService.getInstance(); const instance2 = TestProviderService.getInstance(); expect(instance1).toBe(instance2); expect(instance1).toBeInstanceOf(TestProviderService); }); it('| should have aiProvider property', () => { expect(service.aiProvider).toBe(DyFM_AI_Provider.OpenAI); }); it('| should have capabilities property', () => { expect(service.capabilities).toBeDefined(); expect(service.capabilities.chat).toBe(true); expect(service.capabilities.embeddings).toBe(true); expect(service.capabilities.chat).toBe(true); }); describe('| setup', () => { it('| should accept config parameter', () => { const config: DyFM_AI_Config = { apiKey: 'test-key', }; expect(() => { service.setup(config); }).not.toThrow(); }); }); describe('| testConnection', () => { it('| should return true when connection is successful', async () => { const result = await service.testConnection('issuer-123'); expect(result).toBe(true); }); }); describe('| emitCostEvent (FR-002 cost-event hook)', () => { afterEach(() => { // Reset callback hogy ne szivárogjon át teszt-cases között (singleton service) service.setOnCostEventForTest(undefined); }); it('| should be no-op when onCostEvent is undefined', () => { service.setOnCostEventForTest(undefined); expect(() => { service.emitForTest(makeCostEvent()); }).not.toThrow(); }); it('| should invoke the registered callback with the event payload', () => { const received: DyNTS_AI_CostEvent[] = []; service.setOnCostEventForTest((e: DyNTS_AI_CostEvent) => { received.push(e); }); const event: DyNTS_AI_CostEvent = makeCostEvent({ model: 'gpt-4o-mini', callType: 'llm-completion', tokensUsed: { input: 100, output: 50, total: 150 }, }); service.emitForTest(event); expect(received.length).toBe(1); expect(received[0]).toEqual(event); expect(received[0].tokensUsed.total).toBe(150); }); it('| should NOT throw when the callback itself throws (safe emit)', () => { service.setOnCostEventForTest(() => { throw new Error('consumer callback failure'); }); expect(() => { service.emitForTest(makeCostEvent()); }).not.toThrow(); }); it('| should leave estimatedCostUsd undefined by default (Dynamo has no pricing registry)', () => { const received: DyNTS_AI_CostEvent[] = []; service.setOnCostEventForTest((e: DyNTS_AI_CostEvent) => { received.push(e); }); service.emitForTest(makeCostEvent()); expect(received[0].estimatedCostUsd).toBeUndefined(); }); }); });