Barazo AppView backend barazo.forum
at main 197 lines 6.4 kB view raw
1import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' 2import { createEmbeddingService } from '../../../src/services/embedding.js' 3 4// --------------------------------------------------------------------------- 5// Mock logger 6// --------------------------------------------------------------------------- 7 8const mockLogger = { 9 info: vi.fn(), 10 warn: vi.fn(), 11 error: vi.fn(), 12 debug: vi.fn(), 13 fatal: vi.fn(), 14 trace: vi.fn(), 15 child: vi.fn().mockReturnThis(), 16 level: 'info', 17 silent: vi.fn(), 18} 19 20// --------------------------------------------------------------------------- 21// Tests 22// --------------------------------------------------------------------------- 23 24describe('embedding service', () => { 25 beforeEach(() => { 26 vi.clearAllMocks() 27 }) 28 29 // ========================================================================= 30 // Disabled mode (no URL) 31 // ========================================================================= 32 33 describe('when disabled (no URL)', () => { 34 it('isEnabled returns false when URL is undefined', () => { 35 const service = createEmbeddingService(undefined, 768, mockLogger as never) 36 expect(service.isEnabled()).toBe(false) 37 }) 38 39 it('isEnabled returns false when URL is empty string', () => { 40 const service = createEmbeddingService('', 768, mockLogger as never) 41 expect(service.isEnabled()).toBe(false) 42 }) 43 44 it('generateEmbedding returns null when disabled', async () => { 45 const service = createEmbeddingService(undefined, 768, mockLogger as never) 46 const result = await service.generateEmbedding('test query') 47 expect(result).toBeNull() 48 }) 49 }) 50 51 // ========================================================================= 52 // Enabled mode 53 // ========================================================================= 54 55 describe('when enabled', () => { 56 const TEST_URL = 'http://localhost:11434/api/embeddings' 57 const TEST_DIMENSIONS = 768 58 const mockFetch = vi.fn() 59 60 beforeEach(() => { 61 vi.stubGlobal('fetch', mockFetch) 62 }) 63 64 afterEach(() => { 65 vi.unstubAllGlobals() 66 }) 67 68 it('isEnabled returns true when URL is provided', () => { 69 const service = createEmbeddingService(TEST_URL, TEST_DIMENSIONS, mockLogger as never) 70 expect(service.isEnabled()).toBe(true) 71 }) 72 73 it('generateEmbedding returns embedding array on success', async () => { 74 const expectedEmbedding = [0.1, 0.2, 0.3, 0.4, 0.5] 75 mockFetch.mockResolvedValue({ 76 ok: true, 77 json: () => 78 Promise.resolve({ 79 data: [{ embedding: expectedEmbedding }], 80 }), 81 }) 82 83 const service = createEmbeddingService(TEST_URL, TEST_DIMENSIONS, mockLogger as never) 84 const result = await service.generateEmbedding('test query') 85 86 expect(result).toEqual(expectedEmbedding) 87 }) 88 89 it('calls the correct URL with correct payload', async () => { 90 mockFetch.mockResolvedValue({ 91 ok: true, 92 json: () => 93 Promise.resolve({ 94 data: [{ embedding: [0.1, 0.2, 0.3] }], 95 }), 96 }) 97 98 const service = createEmbeddingService(TEST_URL, TEST_DIMENSIONS, mockLogger as never) 99 await service.generateEmbedding('hello world') 100 101 expect(mockFetch).toHaveBeenCalledOnce() 102 expect(mockFetch.mock.calls[0]?.[0]).toBe(TEST_URL) 103 104 const fetchOptions = mockFetch.mock.calls[0]?.[1] as RequestInit 105 expect(fetchOptions.method).toBe('POST') 106 expect(fetchOptions.headers).toEqual({ 107 'Content-Type': 'application/json', 108 }) 109 110 const body = JSON.parse(fetchOptions.body as string) as { 111 input: string 112 model: string 113 dimensions: number 114 } 115 expect(body.input).toBe('hello world') 116 expect(body.model).toBe('default') 117 expect(body.dimensions).toBe(TEST_DIMENSIONS) 118 }) 119 120 it('returns null on API error (non-OK status)', async () => { 121 mockFetch.mockResolvedValue({ 122 ok: false, 123 status: 500, 124 }) 125 126 const service = createEmbeddingService(TEST_URL, TEST_DIMENSIONS, mockLogger as never) 127 const result = await service.generateEmbedding('test query') 128 129 expect(result).toBeNull() 130 expect(mockLogger.warn).toHaveBeenCalledWith( 131 { status: 500, url: TEST_URL }, 132 'Embedding API returned non-OK status' 133 ) 134 }) 135 136 it('returns null on network error', async () => { 137 mockFetch.mockRejectedValue(new Error('fetch failed')) 138 139 const service = createEmbeddingService(TEST_URL, TEST_DIMENSIONS, mockLogger as never) 140 const result = await service.generateEmbedding('test query') 141 142 expect(result).toBeNull() 143 expect(mockLogger.warn).toHaveBeenCalledWith( 144 { err: expect.any(Error) as Error }, 145 'Failed to generate embedding' 146 ) 147 }) 148 149 it('returns null on invalid response format (missing data)', async () => { 150 mockFetch.mockResolvedValue({ 151 ok: true, 152 json: () => Promise.resolve({ data: [] }), 153 }) 154 155 const service = createEmbeddingService(TEST_URL, TEST_DIMENSIONS, mockLogger as never) 156 const result = await service.generateEmbedding('test query') 157 158 expect(result).toBeNull() 159 expect(mockLogger.warn).toHaveBeenCalledWith( 160 'Embedding API returned empty or invalid embedding' 161 ) 162 }) 163 164 it('returns null on invalid response format (empty embedding)', async () => { 165 mockFetch.mockResolvedValue({ 166 ok: true, 167 json: () => 168 Promise.resolve({ 169 data: [{ embedding: [] }], 170 }), 171 }) 172 173 const service = createEmbeddingService(TEST_URL, TEST_DIMENSIONS, mockLogger as never) 174 const result = await service.generateEmbedding('test query') 175 176 expect(result).toBeNull() 177 expect(mockLogger.warn).toHaveBeenCalledWith( 178 'Embedding API returned empty or invalid embedding' 179 ) 180 }) 181 182 it('returns null on invalid response format (non-array embedding)', async () => { 183 mockFetch.mockResolvedValue({ 184 ok: true, 185 json: () => 186 Promise.resolve({ 187 data: [{ embedding: 'not-an-array' }], 188 }), 189 }) 190 191 const service = createEmbeddingService(TEST_URL, TEST_DIMENSIONS, mockLogger as never) 192 const result = await service.generateEmbedding('test query') 193 194 expect(result).toBeNull() 195 }) 196 }) 197})