/** * @claude-flow/browser - ReasoningBank Adapter Tests */ import { describe, it, expect, beforeEach } from 'vitest'; import { ReasoningBankAdapter, getReasoningBank } from '../src/infrastructure/reasoningbank-adapter.js'; import type { BrowserTrajectory } from '../src/domain/types.js'; describe('ReasoningBankAdapter', () => { let adapter: ReasoningBankAdapter; beforeEach(() => { adapter = new ReasoningBankAdapter(); }); describe('getReasoningBank singleton', () => { it('should return singleton instance', () => { const instance1 = getReasoningBank(); const instance2 = getReasoningBank(); expect(instance1).toBe(instance2); }); }); describe('trajectory storage', () => { const mockTrajectory: BrowserTrajectory = { id: 'traj-1', goal: 'Login to dashboard', startUrl: 'https://example.com/login', startTime: new Date().toISOString(), endTime: new Date().toISOString(), steps: [ { action: 'open', input: { url: 'https://example.com/login' }, result: { success: true, duration: 100 }, timestamp: new Date().toISOString(), }, { action: 'fill', input: { target: '@e1', value: 'user@example.com' }, result: { success: true, duration: 50 }, timestamp: new Date().toISOString(), }, { action: 'fill', input: { target: '@e2', value: 'password' }, result: { success: true, duration: 50 }, timestamp: new Date().toISOString(), }, { action: 'click', input: { target: '@e3' }, result: { success: true, duration: 200 }, timestamp: new Date().toISOString(), }, ], success: true, verdict: 'Login successful', }; it('should store successful trajectory and extract pattern', async () => { await adapter.storeTrajectory(mockTrajectory); const stats = adapter.getStats(); expect(stats.totalPatterns).toBeGreaterThan(0); }); it('should not create pattern from single-step trajectory', async () => { const singleStepTrajectory: BrowserTrajectory = { ...mockTrajectory, id: 'traj-single', steps: [mockTrajectory.steps[0]], }; await adapter.storeTrajectory(singleStepTrajectory); // Pattern extraction requires at least 2 steps const patterns = adapter.exportPatterns(); const singlePattern = patterns.find(p => p.goal === singleStepTrajectory.goal && p.steps.length === 1); expect(singlePattern).toBeUndefined(); }); it('should update existing pattern on repeated success', async () => { await adapter.storeTrajectory(mockTrajectory); await adapter.storeTrajectory(mockTrajectory); const patterns = adapter.exportPatterns(); const pattern = patterns.find(p => p.goal === mockTrajectory.goal); expect(pattern).toBeDefined(); expect(pattern?.usageCount).toBeGreaterThan(1); }); }); describe('pattern matching', () => { const mockTrajectory: BrowserTrajectory = { id: 'traj-2', goal: 'Fill contact form', startUrl: 'https://example.com/contact', startTime: new Date().toISOString(), endTime: new Date().toISOString(), steps: [ { action: 'fill', input: { target: '@e1', value: 'Name' }, result: { success: true, duration: 50 }, timestamp: new Date().toISOString() }, { action: 'fill', input: { target: '@e2', value: 'email' }, result: { success: true, duration: 50 }, timestamp: new Date().toISOString() }, { action: 'click', input: { target: '@e3' }, result: { success: true, duration: 100 }, timestamp: new Date().toISOString() }, ], success: true, }; it('should find similar patterns by goal', async () => { await adapter.storeTrajectory(mockTrajectory); const patterns = await adapter.findSimilarPatterns('contact form'); expect(Array.isArray(patterns)).toBe(true); }); it('should return empty array for no matches', async () => { const patterns = await adapter.findSimilarPatterns('completely unrelated xyz'); expect(patterns).toEqual([]); }); it('should get recommended steps for similar goal', async () => { await adapter.storeTrajectory(mockTrajectory); const steps = await adapter.getRecommendedSteps('Fill a form'); expect(Array.isArray(steps)).toBe(true); }); }); describe('verdict recording', () => { it('should record verdict for trajectory', async () => { const mockTrajectory: BrowserTrajectory = { id: 'traj-3', goal: 'Test pattern', startUrl: 'https://example.com', startTime: new Date().toISOString(), endTime: new Date().toISOString(), steps: [ { action: 'click', input: { target: '@e1' }, result: { success: true, duration: 100 }, timestamp: new Date().toISOString() }, { action: 'click', input: { target: '@e2' }, result: { success: true, duration: 100 }, timestamp: new Date().toISOString() }, ], success: true, }; await adapter.storeTrajectory(mockTrajectory); await adapter.recordVerdict('traj-3', true, 'Works great'); // Just verify it doesn't throw expect(true).toBe(true); }); }); describe('stats', () => { it('should return stats with correct structure', () => { const stats = adapter.getStats(); expect(stats).toHaveProperty('totalPatterns'); expect(stats).toHaveProperty('avgSuccessRate'); expect(stats).toHaveProperty('bufferedTrajectories'); expect(typeof stats.totalPatterns).toBe('number'); expect(typeof stats.avgSuccessRate).toBe('number'); expect(typeof stats.bufferedTrajectories).toBe('number'); }); it('should return zero patterns initially', () => { const stats = adapter.getStats(); expect(stats.totalPatterns).toBe(0); expect(stats.avgSuccessRate).toBe(0); }); }); describe('pattern export/import', () => { it('should export patterns as array', async () => { const mockTrajectory: BrowserTrajectory = { id: 'traj-4', goal: 'Export test', startUrl: 'https://example.com', startTime: new Date().toISOString(), endTime: new Date().toISOString(), steps: [ { action: 'open', input: { url: 'https://example.com' }, result: { success: true, duration: 100 }, timestamp: new Date().toISOString() }, { action: 'click', input: { target: '@e1' }, result: { success: true, duration: 50 }, timestamp: new Date().toISOString() }, ], success: true, }; await adapter.storeTrajectory(mockTrajectory); const patterns = adapter.exportPatterns(); expect(Array.isArray(patterns)).toBe(true); expect(patterns.length).toBeGreaterThan(0); }); it('should import patterns', () => { const patternsToImport = [ { id: 'imported-1', type: 'navigation' as const, goal: 'Imported pattern', steps: [{ action: 'open' }], successRate: 0.9, avgDuration: 100, lastUsed: new Date().toISOString(), usageCount: 5, }, ]; adapter.importPatterns(patternsToImport); const stats = adapter.getStats(); expect(stats.totalPatterns).toBe(1); }); }); });