Barazo AppView backend
barazo.forum
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})