import { DyNTS_AI_LLM_ServiceBase } from './ai-llm.service-base'; import { DyFM_AI_CallSettings, DyFM_AI_MessageRole } from '@futdevpro/fsm-dynamo/ai'; import { DyFM_Log, DyFM_Object } from '@futdevpro/fsm-dynamo'; class TestLLMService extends DyNTS_AI_LLM_ServiceBase { readonly aiProvider: any = {} as any; readonly capabilities: any = {} as any; setup = (config: any): void => {}; testConnection = async (issuer: string): Promise => true; readonly defaultSettings: DyFM_AI_CallSettings = { systemPrompt: 'Test system prompt', useModel: 'test-model', debugLog: false, }; readonly predefinedRequests = { yesNo: { upperCaseYes: 'YES', }, }; requestSimpleMessage = async (set: any): Promise => { return ''; }; requestYesNo = async (set: any): Promise => { return false; }; requestPercentage = async (set: any): Promise => { return 0; }; requestSelect = async (set: any): Promise => { return null as any; }; requestMultiselect = async (set: any): Promise => { return []; }; requestJSON = async (set: any): Promise => { return null as any; }; requestJSONQuestionWithKeysDescription = async (set: any): Promise => { return null as any; }; requestJSONWithExactKeys = async (set: any): Promise => { return null as any; }; requestList = async (set: any): Promise => { return []; }; static getInstance(): TestLLMService { return TestLLMService.getSingletonInstance(); } } describe('| DyNTS_AI_LLM_ServiceBase', () => { let service: TestLLMService; beforeEach(() => { service = TestLLMService.getInstance(); }); it('| should be a singleton instance', () => { const instance1 = TestLLMService.getInstance(); const instance2 = TestLLMService.getInstance(); expect(instance1).toBe(instance2); expect(instance1).toBeInstanceOf(TestLLMService); }); describe('| defaultSystemPrompt', () => { it('| should return system prompt from default settings', () => { expect(service.defaultSystemPrompt).toBe('Test system prompt'); }); }); describe('| defaultModel', () => { it('| should return model from default settings', () => { expect(service.defaultModel).toBe('test-model'); }); }); describe('| debugLog', () => { it('| should return debugLog from default settings', () => { expect(service.debugLog).toBe(false); }); it('| should return _debugLog when defaultSettings debugLog is not set', () => { const serviceWithoutDebugLog = new (class extends TestLLMService { override readonly defaultSettings: DyFM_AI_CallSettings = { systemPrompt: 'Test', useModel: 'test', }; override readonly aiProvider: any = {} as any; override readonly capabilities: any = {} as any; override setup = (config: any): void => {}; override testConnection = async (issuer: string): Promise => true; } as any)(); // Set _debugLog on the new instance serviceWithoutDebugLog._debugLog = true; expect(serviceWithoutDebugLog.debugLog).toBe(true); }); }); describe('| convertAnswerToBoolean', () => { it('| should return true when answer contains YES', () => { const result = (service as any).convertAnswerToBoolean('YES'); expect(result).toBe(true); }); it('| should return true when answer contains yes in uppercase', () => { const result = (service as any).convertAnswerToBoolean('The answer is YES'); expect(result).toBe(true); }); it('| should return false when answer does not contain YES', () => { const result = (service as any).convertAnswerToBoolean('NO'); expect(result).toBe(false); }); it('| should be case insensitive', () => { const result = (service as any).convertAnswerToBoolean('yes'); expect(result).toBe(true); }); }); describe('| convertAnswerToNumber', () => { it('| should return number when answer is valid', () => { const result = (service as any).convertAnswerToNumber('42', 'What is the answer?'); expect(result).toBe(42); }); it('| should return null when answer is invalid (contains skip flag)', () => { spyOn(service as any, 'isAnswerValid').and.returnValue(true); const result = (service as any).convertAnswerToNumber('42', 'What is the answer?'); expect(result).toBeNull(); }); it('| should return null when answer is NaN', () => { const logSpy = spyOn(DyFM_Log, 'T_error'); spyOn(service as any, 'isAnswerValid').and.returnValue(false); const result = (service as any).convertAnswerToNumber('not-a-number', 'What is the answer?'); expect(result).toBeNull(); expect(logSpy).toHaveBeenCalled(); }); it('| should handle decimal numbers', () => { const result = (service as any).convertAnswerToNumber('42.5', 'What is the answer?'); expect(result).toBe(42.5); }); }); describe('| convertAnswerToSelectOption', () => { it('| should return selected option when found', () => { const options = ['option1', 'option2', 'option3']; spyOn(service as any, 'isAnswerValid').and.returnValue(false); spyOn(service as any, 'stringifySelectOptions').and.returnValue(['option1', 'option2', 'option3']); const result = (service as any).convertAnswerToSelectOption('option1', 'Select an option', options); expect(result).toBe('option1'); }); it('| should return null when answer is invalid', () => { spyOn(service as any, 'isAnswerValid').and.returnValue(true); const result = (service as any).convertAnswerToSelectOption('option1', 'Select an option', ['option1']); expect(result).toBeNull(); }); it('| should return null when option not found', () => { spyOn(service as any, 'isAnswerValid').and.returnValue(false); spyOn(service as any, 'stringifySelectOptions').and.returnValue(['option1', 'option2']); const result = (service as any).convertAnswerToSelectOption('option3', 'Select an option', ['option1', 'option2']); expect(result).toBeNull(); }); it('| should handle JSON options', () => { const options = [{ id: 1, name: 'test' }]; spyOn(service as any, 'isAnswerValid').and.returnValue(false); spyOn(service as any, 'stringifySelectOptions').and.returnValue([JSON.stringify({ id: 1, name: 'test' })]); spyOn(DyFM_Object, 'safeParseJSON').and.returnValue({ id: 1, name: 'test' }); const result = (service as any).convertAnswerToSelectOption( JSON.stringify({ id: 1, name: 'test' }), 'Select an option', options ); expect(result).toEqual({ id: 1, name: 'test' }); }); }); describe('| convertAnswerToSelectOptions', () => { it('| should return multiple selected options', () => { const options = ['option1', 'option2', 'option3']; spyOn(service as any, 'isAnswerValid').and.returnValue(false); spyOn(service as any, 'stringifySelectOption').and.callFake((opt: string) => opt); const result = (service as any).convertAnswerToSelectOptions('option1 and option2', 'Select options', options); expect(result).toEqual(['option1', 'option2']); }); it('| should return null when answer is invalid', () => { spyOn(service as any, 'isAnswerValid').and.returnValue(true); const result = (service as any).convertAnswerToSelectOptions('option1', 'Select options', ['option1']); expect(result).toBeNull(); }); it('| should return empty array when no options match', () => { spyOn(service as any, 'isAnswerValid').and.returnValue(false); spyOn(service as any, 'stringifySelectOption').and.callFake((opt: string) => opt); const result = (service as any).convertAnswerToSelectOptions('option4', 'Select options', ['option1', 'option2']); expect(result).toEqual([]); }); }); describe('| convertAnswerToJSON', () => { it('| should return parsed JSON when valid', () => { const jsonString = '{"key": "value"}'; spyOn(service as any, 'isAnswerValid').and.returnValue(false); spyOn(DyFM_Object, 'safeParseJSON').and.returnValue({ key: 'value' }); const result = (service as any).convertAnswerToJSON(jsonString, 'Enter JSON') as { key: string }; expect(result).toEqual({ key: 'value' }); }); it('| should return unparsableResult when answer is invalid', () => { spyOn(service as any, 'isAnswerValid').and.returnValue(true); const result = (service as any).convertAnswerToJSON('{"key": "value"}', 'Enter JSON'); expect(result).toEqual({ unparsableResult: '{"key": "value"}' }); }); it('| should return unparsableResult when JSON is invalid', () => { const logSpy = spyOn(DyFM_Log, 'T_error'); spyOn(service as any, 'isAnswerValid').and.returnValue(false); spyOn(DyFM_Object, 'safeParseJSON').and.returnValue({ unparsableResult: 'invalid json' }); const result = (service as any).convertAnswerToJSON('invalid json', 'Enter JSON'); expect(result).toEqual({ unparsableResult: 'invalid json' }); expect(logSpy).toHaveBeenCalled(); }); }); });