glancey 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +435 -0
  3. package/dist/__tests__/ast-chunker.test.d.ts +2 -0
  4. package/dist/__tests__/ast-chunker.test.d.ts.map +1 -0
  5. package/dist/__tests__/ast-chunker.test.js +307 -0
  6. package/dist/__tests__/ast-chunker.test.js.map +1 -0
  7. package/dist/__tests__/config.test.d.ts +2 -0
  8. package/dist/__tests__/config.test.d.ts.map +1 -0
  9. package/dist/__tests__/config.test.js +242 -0
  10. package/dist/__tests__/config.test.js.map +1 -0
  11. package/dist/__tests__/dashboard/beads.test.d.ts +2 -0
  12. package/dist/__tests__/dashboard/beads.test.d.ts.map +1 -0
  13. package/dist/__tests__/dashboard/beads.test.js +151 -0
  14. package/dist/__tests__/dashboard/beads.test.js.map +1 -0
  15. package/dist/__tests__/dashboard/index.test.d.ts +2 -0
  16. package/dist/__tests__/dashboard/index.test.d.ts.map +1 -0
  17. package/dist/__tests__/dashboard/index.test.js +116 -0
  18. package/dist/__tests__/dashboard/index.test.js.map +1 -0
  19. package/dist/__tests__/dashboard/routes.test.d.ts +2 -0
  20. package/dist/__tests__/dashboard/routes.test.d.ts.map +1 -0
  21. package/dist/__tests__/dashboard/routes.test.js +125 -0
  22. package/dist/__tests__/dashboard/routes.test.js.map +1 -0
  23. package/dist/__tests__/dashboard/server.test.d.ts +2 -0
  24. package/dist/__tests__/dashboard/server.test.d.ts.map +1 -0
  25. package/dist/__tests__/dashboard/server.test.js +75 -0
  26. package/dist/__tests__/dashboard/server.test.js.map +1 -0
  27. package/dist/__tests__/dashboard/state.test.d.ts +2 -0
  28. package/dist/__tests__/dashboard/state.test.d.ts.map +1 -0
  29. package/dist/__tests__/dashboard/state.test.js +124 -0
  30. package/dist/__tests__/dashboard/state.test.js.map +1 -0
  31. package/dist/__tests__/dashboard/token-tracking.test.d.ts +2 -0
  32. package/dist/__tests__/dashboard/token-tracking.test.d.ts.map +1 -0
  33. package/dist/__tests__/dashboard/token-tracking.test.js +315 -0
  34. package/dist/__tests__/dashboard/token-tracking.test.js.map +1 -0
  35. package/dist/__tests__/embeddings/factory.test.d.ts +2 -0
  36. package/dist/__tests__/embeddings/factory.test.d.ts.map +1 -0
  37. package/dist/__tests__/embeddings/factory.test.js +157 -0
  38. package/dist/__tests__/embeddings/factory.test.js.map +1 -0
  39. package/dist/__tests__/embeddings/gemini.test.d.ts +2 -0
  40. package/dist/__tests__/embeddings/gemini.test.d.ts.map +1 -0
  41. package/dist/__tests__/embeddings/gemini.test.js +178 -0
  42. package/dist/__tests__/embeddings/gemini.test.js.map +1 -0
  43. package/dist/__tests__/embeddings/ollama.test.d.ts +2 -0
  44. package/dist/__tests__/embeddings/ollama.test.d.ts.map +1 -0
  45. package/dist/__tests__/embeddings/ollama.test.js +273 -0
  46. package/dist/__tests__/embeddings/ollama.test.js.map +1 -0
  47. package/dist/__tests__/embeddings/rate-limiter.test.d.ts +2 -0
  48. package/dist/__tests__/embeddings/rate-limiter.test.d.ts.map +1 -0
  49. package/dist/__tests__/embeddings/rate-limiter.test.js +163 -0
  50. package/dist/__tests__/embeddings/rate-limiter.test.js.map +1 -0
  51. package/dist/__tests__/embeddings/retry.test.d.ts +2 -0
  52. package/dist/__tests__/embeddings/retry.test.d.ts.map +1 -0
  53. package/dist/__tests__/embeddings/retry.test.js +334 -0
  54. package/dist/__tests__/embeddings/retry.test.js.map +1 -0
  55. package/dist/__tests__/embeddings/types.test.d.ts +2 -0
  56. package/dist/__tests__/embeddings/types.test.d.ts.map +1 -0
  57. package/dist/__tests__/embeddings/types.test.js +31 -0
  58. package/dist/__tests__/embeddings/types.test.js.map +1 -0
  59. package/dist/__tests__/mocks/embedding-backend.mock.d.ts +10 -0
  60. package/dist/__tests__/mocks/embedding-backend.mock.d.ts.map +1 -0
  61. package/dist/__tests__/mocks/embedding-backend.mock.js +39 -0
  62. package/dist/__tests__/mocks/embedding-backend.mock.js.map +1 -0
  63. package/dist/__tests__/mocks/fetch.mock.d.ts +49 -0
  64. package/dist/__tests__/mocks/fetch.mock.d.ts.map +1 -0
  65. package/dist/__tests__/mocks/fetch.mock.js +102 -0
  66. package/dist/__tests__/mocks/fetch.mock.js.map +1 -0
  67. package/dist/__tests__/mocks/lancedb.mock.d.ts +38 -0
  68. package/dist/__tests__/mocks/lancedb.mock.d.ts.map +1 -0
  69. package/dist/__tests__/mocks/lancedb.mock.js +63 -0
  70. package/dist/__tests__/mocks/lancedb.mock.js.map +1 -0
  71. package/dist/__tests__/search/clustering.test.d.ts +2 -0
  72. package/dist/__tests__/search/clustering.test.d.ts.map +1 -0
  73. package/dist/__tests__/search/clustering.test.js +230 -0
  74. package/dist/__tests__/search/clustering.test.js.map +1 -0
  75. package/dist/__tests__/search/hybrid-search.test.d.ts +2 -0
  76. package/dist/__tests__/search/hybrid-search.test.d.ts.map +1 -0
  77. package/dist/__tests__/search/hybrid-search.test.js +279 -0
  78. package/dist/__tests__/search/hybrid-search.test.js.map +1 -0
  79. package/dist/__tests__/search/indexer.test.d.ts +2 -0
  80. package/dist/__tests__/search/indexer.test.d.ts.map +1 -0
  81. package/dist/__tests__/search/indexer.test.js +1305 -0
  82. package/dist/__tests__/search/indexer.test.js.map +1 -0
  83. package/dist/__tests__/search/tree-sitter-chunker.test.d.ts +2 -0
  84. package/dist/__tests__/search/tree-sitter-chunker.test.d.ts.map +1 -0
  85. package/dist/__tests__/search/tree-sitter-chunker.test.js +228 -0
  86. package/dist/__tests__/search/tree-sitter-chunker.test.js.map +1 -0
  87. package/dist/__tests__/setup.d.ts +2 -0
  88. package/dist/__tests__/setup.d.ts.map +1 -0
  89. package/dist/__tests__/setup.js +11 -0
  90. package/dist/__tests__/setup.js.map +1 -0
  91. package/dist/__tests__/tools/clustering-handlers.test.d.ts +2 -0
  92. package/dist/__tests__/tools/clustering-handlers.test.d.ts.map +1 -0
  93. package/dist/__tests__/tools/clustering-handlers.test.js +286 -0
  94. package/dist/__tests__/tools/clustering-handlers.test.js.map +1 -0
  95. package/dist/__tests__/tools/commit-handlers.test.d.ts +2 -0
  96. package/dist/__tests__/tools/commit-handlers.test.d.ts.map +1 -0
  97. package/dist/__tests__/tools/commit-handlers.test.js +153 -0
  98. package/dist/__tests__/tools/commit-handlers.test.js.map +1 -0
  99. package/dist/__tests__/tools/index-handlers.test.d.ts +2 -0
  100. package/dist/__tests__/tools/index-handlers.test.d.ts.map +1 -0
  101. package/dist/__tests__/tools/index-handlers.test.js +184 -0
  102. package/dist/__tests__/tools/index-handlers.test.js.map +1 -0
  103. package/dist/__tests__/tools/instructions-handlers.test.d.ts +2 -0
  104. package/dist/__tests__/tools/instructions-handlers.test.d.ts.map +1 -0
  105. package/dist/__tests__/tools/instructions-handlers.test.js +53 -0
  106. package/dist/__tests__/tools/instructions-handlers.test.js.map +1 -0
  107. package/dist/__tests__/tools/memory-handlers.test.d.ts +2 -0
  108. package/dist/__tests__/tools/memory-handlers.test.d.ts.map +1 -0
  109. package/dist/__tests__/tools/memory-handlers.test.js +175 -0
  110. package/dist/__tests__/tools/memory-handlers.test.js.map +1 -0
  111. package/dist/__tests__/tools/search-handlers.test.d.ts +2 -0
  112. package/dist/__tests__/tools/search-handlers.test.d.ts.map +1 -0
  113. package/dist/__tests__/tools/search-handlers.test.js +229 -0
  114. package/dist/__tests__/tools/search-handlers.test.js.map +1 -0
  115. package/dist/__tests__/tools/symbol-handlers.test.d.ts +2 -0
  116. package/dist/__tests__/tools/symbol-handlers.test.d.ts.map +1 -0
  117. package/dist/__tests__/tools/symbol-handlers.test.js +348 -0
  118. package/dist/__tests__/tools/symbol-handlers.test.js.map +1 -0
  119. package/dist/__tests__/tools/worktree-handlers.test.d.ts +2 -0
  120. package/dist/__tests__/tools/worktree-handlers.test.d.ts.map +1 -0
  121. package/dist/__tests__/tools/worktree-handlers.test.js +297 -0
  122. package/dist/__tests__/tools/worktree-handlers.test.js.map +1 -0
  123. package/dist/__tests__/utils/cache.test.d.ts +2 -0
  124. package/dist/__tests__/utils/cache.test.d.ts.map +1 -0
  125. package/dist/__tests__/utils/cache.test.js +147 -0
  126. package/dist/__tests__/utils/cache.test.js.map +1 -0
  127. package/dist/__tests__/utils/concurrency.test.d.ts +2 -0
  128. package/dist/__tests__/utils/concurrency.test.d.ts.map +1 -0
  129. package/dist/__tests__/utils/concurrency.test.js +83 -0
  130. package/dist/__tests__/utils/concurrency.test.js.map +1 -0
  131. package/dist/__tests__/utils/errors.test.d.ts +2 -0
  132. package/dist/__tests__/utils/errors.test.d.ts.map +1 -0
  133. package/dist/__tests__/utils/errors.test.js +136 -0
  134. package/dist/__tests__/utils/errors.test.js.map +1 -0
  135. package/dist/__tests__/utils/logger.test.d.ts +2 -0
  136. package/dist/__tests__/utils/logger.test.d.ts.map +1 -0
  137. package/dist/__tests__/utils/logger.test.js +131 -0
  138. package/dist/__tests__/utils/logger.test.js.map +1 -0
  139. package/dist/__tests__/utils/type-guards.test.d.ts +2 -0
  140. package/dist/__tests__/utils/type-guards.test.d.ts.map +1 -0
  141. package/dist/__tests__/utils/type-guards.test.js +80 -0
  142. package/dist/__tests__/utils/type-guards.test.js.map +1 -0
  143. package/dist/__tests__/worktree/worktree-manager.test.d.ts +2 -0
  144. package/dist/__tests__/worktree/worktree-manager.test.d.ts.map +1 -0
  145. package/dist/__tests__/worktree/worktree-manager.test.js +403 -0
  146. package/dist/__tests__/worktree/worktree-manager.test.js.map +1 -0
  147. package/dist/config.d.ts +191 -0
  148. package/dist/config.d.ts.map +1 -0
  149. package/dist/config.js +788 -0
  150. package/dist/config.js.map +1 -0
  151. package/dist/dashboard/beads.d.ts +35 -0
  152. package/dist/dashboard/beads.d.ts.map +1 -0
  153. package/dist/dashboard/beads.js +102 -0
  154. package/dist/dashboard/beads.js.map +1 -0
  155. package/dist/dashboard/events.d.ts +64 -0
  156. package/dist/dashboard/events.d.ts.map +1 -0
  157. package/dist/dashboard/events.js +211 -0
  158. package/dist/dashboard/events.js.map +1 -0
  159. package/dist/dashboard/index.d.ts +69 -0
  160. package/dist/dashboard/index.d.ts.map +1 -0
  161. package/dist/dashboard/index.js +93 -0
  162. package/dist/dashboard/index.js.map +1 -0
  163. package/dist/dashboard/routes.d.ts +6 -0
  164. package/dist/dashboard/routes.d.ts.map +1 -0
  165. package/dist/dashboard/routes.js +505 -0
  166. package/dist/dashboard/routes.js.map +1 -0
  167. package/dist/dashboard/server.d.ts +27 -0
  168. package/dist/dashboard/server.d.ts.map +1 -0
  169. package/dist/dashboard/server.js +72 -0
  170. package/dist/dashboard/server.js.map +1 -0
  171. package/dist/dashboard/state.d.ts +172 -0
  172. package/dist/dashboard/state.d.ts.map +1 -0
  173. package/dist/dashboard/state.js +362 -0
  174. package/dist/dashboard/state.js.map +1 -0
  175. package/dist/dashboard/token-tracking.d.ts +143 -0
  176. package/dist/dashboard/token-tracking.d.ts.map +1 -0
  177. package/dist/dashboard/token-tracking.js +363 -0
  178. package/dist/dashboard/token-tracking.js.map +1 -0
  179. package/dist/dashboard/ui.d.ts +6 -0
  180. package/dist/dashboard/ui.d.ts.map +1 -0
  181. package/dist/dashboard/ui.js +2710 -0
  182. package/dist/dashboard/ui.js.map +1 -0
  183. package/dist/embeddings/gemini.d.ts +28 -0
  184. package/dist/embeddings/gemini.d.ts.map +1 -0
  185. package/dist/embeddings/gemini.js +165 -0
  186. package/dist/embeddings/gemini.js.map +1 -0
  187. package/dist/embeddings/index.d.ts +34 -0
  188. package/dist/embeddings/index.d.ts.map +1 -0
  189. package/dist/embeddings/index.js +120 -0
  190. package/dist/embeddings/index.js.map +1 -0
  191. package/dist/embeddings/ollama.d.ts +22 -0
  192. package/dist/embeddings/ollama.d.ts.map +1 -0
  193. package/dist/embeddings/ollama.js +179 -0
  194. package/dist/embeddings/ollama.js.map +1 -0
  195. package/dist/embeddings/rate-limiter.d.ts +75 -0
  196. package/dist/embeddings/rate-limiter.d.ts.map +1 -0
  197. package/dist/embeddings/rate-limiter.js +145 -0
  198. package/dist/embeddings/rate-limiter.js.map +1 -0
  199. package/dist/embeddings/retry.d.ts +20 -0
  200. package/dist/embeddings/retry.d.ts.map +1 -0
  201. package/dist/embeddings/retry.js +227 -0
  202. package/dist/embeddings/retry.js.map +1 -0
  203. package/dist/embeddings/types.d.ts +103 -0
  204. package/dist/embeddings/types.d.ts.map +1 -0
  205. package/dist/embeddings/types.js +23 -0
  206. package/dist/embeddings/types.js.map +1 -0
  207. package/dist/index.d.ts +12 -0
  208. package/dist/index.d.ts.map +1 -0
  209. package/dist/index.js +2028 -0
  210. package/dist/index.js.map +1 -0
  211. package/dist/memory/index.d.ts +63 -0
  212. package/dist/memory/index.d.ts.map +1 -0
  213. package/dist/memory/index.js +168 -0
  214. package/dist/memory/index.js.map +1 -0
  215. package/dist/search/ast-chunker.d.ts +29 -0
  216. package/dist/search/ast-chunker.d.ts.map +1 -0
  217. package/dist/search/ast-chunker.js +236 -0
  218. package/dist/search/ast-chunker.js.map +1 -0
  219. package/dist/search/chunk-utils.d.ts +24 -0
  220. package/dist/search/chunk-utils.d.ts.map +1 -0
  221. package/dist/search/chunk-utils.js +41 -0
  222. package/dist/search/chunk-utils.js.map +1 -0
  223. package/dist/search/clustering.d.ts +77 -0
  224. package/dist/search/clustering.d.ts.map +1 -0
  225. package/dist/search/clustering.js +455 -0
  226. package/dist/search/clustering.js.map +1 -0
  227. package/dist/search/indexer.d.ts +377 -0
  228. package/dist/search/indexer.d.ts.map +1 -0
  229. package/dist/search/indexer.js +1557 -0
  230. package/dist/search/indexer.js.map +1 -0
  231. package/dist/search/tree-sitter-chunker.d.ts +64 -0
  232. package/dist/search/tree-sitter-chunker.d.ts.map +1 -0
  233. package/dist/search/tree-sitter-chunker.js +412 -0
  234. package/dist/search/tree-sitter-chunker.js.map +1 -0
  235. package/dist/symbols/index.d.ts +14 -0
  236. package/dist/symbols/index.d.ts.map +1 -0
  237. package/dist/symbols/index.js +19 -0
  238. package/dist/symbols/index.js.map +1 -0
  239. package/dist/symbols/name-path.d.ts +113 -0
  240. package/dist/symbols/name-path.d.ts.map +1 -0
  241. package/dist/symbols/name-path.js +194 -0
  242. package/dist/symbols/name-path.js.map +1 -0
  243. package/dist/symbols/pattern-search.d.ts +14 -0
  244. package/dist/symbols/pattern-search.d.ts.map +1 -0
  245. package/dist/symbols/pattern-search.js +224 -0
  246. package/dist/symbols/pattern-search.js.map +1 -0
  247. package/dist/symbols/reference-finder.d.ts +38 -0
  248. package/dist/symbols/reference-finder.d.ts.map +1 -0
  249. package/dist/symbols/reference-finder.js +376 -0
  250. package/dist/symbols/reference-finder.js.map +1 -0
  251. package/dist/symbols/symbol-editor.d.ts +81 -0
  252. package/dist/symbols/symbol-editor.d.ts.map +1 -0
  253. package/dist/symbols/symbol-editor.js +257 -0
  254. package/dist/symbols/symbol-editor.js.map +1 -0
  255. package/dist/symbols/symbol-extractor.d.ts +49 -0
  256. package/dist/symbols/symbol-extractor.d.ts.map +1 -0
  257. package/dist/symbols/symbol-extractor.js +593 -0
  258. package/dist/symbols/symbol-extractor.js.map +1 -0
  259. package/dist/symbols/symbol-renamer.d.ts +81 -0
  260. package/dist/symbols/symbol-renamer.d.ts.map +1 -0
  261. package/dist/symbols/symbol-renamer.js +204 -0
  262. package/dist/symbols/symbol-renamer.js.map +1 -0
  263. package/dist/symbols/types.d.ts +234 -0
  264. package/dist/symbols/types.d.ts.map +1 -0
  265. package/dist/symbols/types.js +106 -0
  266. package/dist/symbols/types.js.map +1 -0
  267. package/dist/tools/clustering-handlers.d.ts +70 -0
  268. package/dist/tools/clustering-handlers.d.ts.map +1 -0
  269. package/dist/tools/clustering-handlers.js +150 -0
  270. package/dist/tools/clustering-handlers.js.map +1 -0
  271. package/dist/tools/commit-handlers.d.ts +77 -0
  272. package/dist/tools/commit-handlers.d.ts.map +1 -0
  273. package/dist/tools/commit-handlers.js +251 -0
  274. package/dist/tools/commit-handlers.js.map +1 -0
  275. package/dist/tools/index-handlers.d.ts +31 -0
  276. package/dist/tools/index-handlers.d.ts.map +1 -0
  277. package/dist/tools/index-handlers.js +53 -0
  278. package/dist/tools/index-handlers.js.map +1 -0
  279. package/dist/tools/instructions-handlers.d.ts +25 -0
  280. package/dist/tools/instructions-handlers.d.ts.map +1 -0
  281. package/dist/tools/instructions-handlers.js +36 -0
  282. package/dist/tools/instructions-handlers.js.map +1 -0
  283. package/dist/tools/memory-handlers.d.ts +95 -0
  284. package/dist/tools/memory-handlers.d.ts.map +1 -0
  285. package/dist/tools/memory-handlers.js +126 -0
  286. package/dist/tools/memory-handlers.js.map +1 -0
  287. package/dist/tools/search-handlers.d.ts +51 -0
  288. package/dist/tools/search-handlers.d.ts.map +1 -0
  289. package/dist/tools/search-handlers.js +128 -0
  290. package/dist/tools/search-handlers.js.map +1 -0
  291. package/dist/tools/symbol-handlers.d.ts +171 -0
  292. package/dist/tools/symbol-handlers.d.ts.map +1 -0
  293. package/dist/tools/symbol-handlers.js +456 -0
  294. package/dist/tools/symbol-handlers.js.map +1 -0
  295. package/dist/tools/types.d.ts +32 -0
  296. package/dist/tools/types.d.ts.map +1 -0
  297. package/dist/tools/types.js +17 -0
  298. package/dist/tools/types.js.map +1 -0
  299. package/dist/tools/worktree-handlers.d.ts +96 -0
  300. package/dist/tools/worktree-handlers.d.ts.map +1 -0
  301. package/dist/tools/worktree-handlers.js +186 -0
  302. package/dist/tools/worktree-handlers.js.map +1 -0
  303. package/dist/utils/cache.d.ts +77 -0
  304. package/dist/utils/cache.d.ts.map +1 -0
  305. package/dist/utils/cache.js +134 -0
  306. package/dist/utils/cache.js.map +1 -0
  307. package/dist/utils/concurrency.d.ts +32 -0
  308. package/dist/utils/concurrency.d.ts.map +1 -0
  309. package/dist/utils/concurrency.js +57 -0
  310. package/dist/utils/concurrency.js.map +1 -0
  311. package/dist/utils/errors.d.ts +36 -0
  312. package/dist/utils/errors.d.ts.map +1 -0
  313. package/dist/utils/errors.js +91 -0
  314. package/dist/utils/errors.js.map +1 -0
  315. package/dist/utils/logger.d.ts +37 -0
  316. package/dist/utils/logger.d.ts.map +1 -0
  317. package/dist/utils/logger.js +114 -0
  318. package/dist/utils/logger.js.map +1 -0
  319. package/dist/utils/type-guards.d.ts +17 -0
  320. package/dist/utils/type-guards.d.ts.map +1 -0
  321. package/dist/utils/type-guards.js +25 -0
  322. package/dist/utils/type-guards.js.map +1 -0
  323. package/dist/worktree/index.d.ts +6 -0
  324. package/dist/worktree/index.d.ts.map +1 -0
  325. package/dist/worktree/index.js +6 -0
  326. package/dist/worktree/index.js.map +1 -0
  327. package/dist/worktree/types.d.ts +101 -0
  328. package/dist/worktree/types.d.ts.map +1 -0
  329. package/dist/worktree/types.js +6 -0
  330. package/dist/worktree/types.js.map +1 -0
  331. package/dist/worktree/worktree-manager.d.ts +80 -0
  332. package/dist/worktree/worktree-manager.d.ts.map +1 -0
  333. package/dist/worktree/worktree-manager.js +407 -0
  334. package/dist/worktree/worktree-manager.js.map +1 -0
  335. package/package.json +87 -0
  336. package/scripts/postinstall.js +48 -0
@@ -0,0 +1,124 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { DashboardStateManager } from '../../dashboard/state.js';
3
+ describe('DashboardStateManager', () => {
4
+ let stateManager;
5
+ beforeEach(() => {
6
+ stateManager = new DashboardStateManager();
7
+ });
8
+ describe('setIndexer', () => {
9
+ it('should set and use the indexer', async () => {
10
+ const mockIndexer = {
11
+ getStatus: vi.fn().mockResolvedValue({
12
+ indexed: true,
13
+ fileCount: 10,
14
+ chunkCount: 50,
15
+ lastUpdated: '2024-01-01T00:00:00.000Z',
16
+ indexPath: '/test/.glancey',
17
+ }),
18
+ };
19
+ stateManager.setIndexer(mockIndexer);
20
+ const status = await stateManager.getStatus();
21
+ expect(status).toEqual({
22
+ indexed: true,
23
+ fileCount: 10,
24
+ chunkCount: 50,
25
+ lastUpdated: '2024-01-01T00:00:00.000Z',
26
+ indexPath: '/test/.glancey',
27
+ });
28
+ expect(mockIndexer.getStatus).toHaveBeenCalled();
29
+ });
30
+ it('should return null if no indexer is set', async () => {
31
+ const status = await stateManager.getStatus();
32
+ expect(status).toBeNull();
33
+ });
34
+ });
35
+ describe('setConfig', () => {
36
+ it('should set and retrieve config', () => {
37
+ const config = {
38
+ patterns: ['**/*.ts'],
39
+ excludePatterns: ['**/node_modules/**'],
40
+ };
41
+ stateManager.setConfig(config);
42
+ expect(stateManager.getConfig()).toEqual(config);
43
+ });
44
+ });
45
+ describe('setProjectPath', () => {
46
+ it('should set and retrieve project path', () => {
47
+ stateManager.setProjectPath('/test/project');
48
+ expect(stateManager.getProjectPath()).toBe('/test/project');
49
+ });
50
+ });
51
+ describe('indexing state', () => {
52
+ it('should track indexing state', () => {
53
+ expect(stateManager.isIndexingInProgress()).toBe(false);
54
+ stateManager.onIndexingStart();
55
+ expect(stateManager.isIndexingInProgress()).toBe(true);
56
+ stateManager.onIndexingComplete({ filesIndexed: 5, chunksCreated: 20 });
57
+ expect(stateManager.isIndexingInProgress()).toBe(false);
58
+ });
59
+ it('should track last progress', () => {
60
+ expect(stateManager.getLastProgress()).toBeNull();
61
+ const progress = {
62
+ phase: 'embedding',
63
+ current: 5,
64
+ total: 10,
65
+ message: 'Embedding chunks...',
66
+ };
67
+ stateManager.onProgress(progress);
68
+ expect(stateManager.getLastProgress()).toEqual(progress);
69
+ });
70
+ it('should clear last progress on indexing complete', () => {
71
+ stateManager.onIndexingStart();
72
+ stateManager.onProgress({
73
+ phase: 'embedding',
74
+ current: 5,
75
+ total: 10,
76
+ message: 'Embedding chunks...',
77
+ });
78
+ expect(stateManager.getLastProgress()).not.toBeNull();
79
+ stateManager.onIndexingComplete({ filesIndexed: 5, chunksCreated: 20 });
80
+ expect(stateManager.getLastProgress()).toBeNull();
81
+ });
82
+ });
83
+ describe('events', () => {
84
+ it('should emit indexing:start event', () => {
85
+ const listener = vi.fn();
86
+ stateManager.on('indexing:start', listener);
87
+ stateManager.onIndexingStart();
88
+ expect(listener).toHaveBeenCalled();
89
+ });
90
+ it('should emit progress event', () => {
91
+ const listener = vi.fn();
92
+ stateManager.on('progress', listener);
93
+ const progress = {
94
+ phase: 'chunking',
95
+ current: 3,
96
+ total: 10,
97
+ message: 'Chunking files...',
98
+ };
99
+ stateManager.onProgress(progress);
100
+ expect(listener).toHaveBeenCalledWith(progress);
101
+ });
102
+ it('should emit indexing:complete event', () => {
103
+ const listener = vi.fn();
104
+ stateManager.on('indexing:complete', listener);
105
+ const result = { filesIndexed: 5, chunksCreated: 20 };
106
+ stateManager.onIndexingComplete(result);
107
+ expect(listener).toHaveBeenCalledWith(result);
108
+ });
109
+ it('should emit status:change event', () => {
110
+ const listener = vi.fn();
111
+ stateManager.on('status:change', listener);
112
+ const status = {
113
+ indexed: true,
114
+ fileCount: 10,
115
+ chunkCount: 50,
116
+ lastUpdated: '2024-01-01T00:00:00.000Z',
117
+ indexPath: '/test/.glancey',
118
+ };
119
+ stateManager.onStatusChange(status);
120
+ expect(listener).toHaveBeenCalledWith(status);
121
+ });
122
+ });
123
+ });
124
+ //# sourceMappingURL=state.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.test.js","sourceRoot":"","sources":["../../../src/__tests__/dashboard/state.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=token-tracking.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-tracking.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/dashboard/token-tracking.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,315 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import { TokenSavingsTracker } from '../../dashboard/token-tracking.js';
4
+ // Mock fs module
5
+ vi.mock('fs');
6
+ describe('TokenSavingsTracker', () => {
7
+ let tracker;
8
+ const mockProjectPath = '/test/project';
9
+ beforeEach(() => {
10
+ vi.clearAllMocks();
11
+ tracker = new TokenSavingsTracker();
12
+ // Default: file doesn't exist
13
+ vi.mocked(fs.existsSync).mockReturnValue(false);
14
+ });
15
+ afterEach(() => {
16
+ vi.useRealTimers();
17
+ });
18
+ describe('setProjectPath', () => {
19
+ it('should load persisted data when file exists', () => {
20
+ const persistedData = {
21
+ events: [
22
+ {
23
+ type: 'search_code',
24
+ timestamp: 1234567890,
25
+ charsReturned: 100,
26
+ charsAvoided: 500,
27
+ filesAvoided: 2,
28
+ },
29
+ ],
30
+ sessionStart: 1234567800,
31
+ };
32
+ vi.mocked(fs.existsSync).mockReturnValue(true);
33
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(persistedData));
34
+ tracker.setProjectPath(mockProjectPath);
35
+ const stats = tracker.getStats();
36
+ expect(stats.operationCount).toBe(1);
37
+ expect(stats.sessionStart).toBe(1234567800);
38
+ });
39
+ it('should validate events on load and filter invalid ones', () => {
40
+ const persistedData = {
41
+ events: [
42
+ // Valid event
43
+ {
44
+ type: 'search_code',
45
+ timestamp: 1234567890,
46
+ charsReturned: 100,
47
+ charsAvoided: 500,
48
+ filesAvoided: 2,
49
+ },
50
+ // Invalid: missing type
51
+ {
52
+ timestamp: 1234567891,
53
+ charsReturned: 50,
54
+ charsAvoided: 250,
55
+ filesAvoided: 1,
56
+ },
57
+ // Invalid: wrong type
58
+ {
59
+ type: 'invalid_type',
60
+ timestamp: 1234567892,
61
+ charsReturned: 50,
62
+ charsAvoided: 250,
63
+ filesAvoided: 1,
64
+ },
65
+ // Invalid: missing required field
66
+ {
67
+ type: 'search_code',
68
+ timestamp: 1234567893,
69
+ charsReturned: 50,
70
+ // missing charsAvoided and filesAvoided
71
+ },
72
+ ],
73
+ sessionStart: 1234567800,
74
+ };
75
+ vi.mocked(fs.existsSync).mockReturnValue(true);
76
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(persistedData));
77
+ tracker.setProjectPath(mockProjectPath);
78
+ const stats = tracker.getStats();
79
+ // Only the first valid event should be loaded
80
+ expect(stats.operationCount).toBe(1);
81
+ });
82
+ it('should handle missing file gracefully', () => {
83
+ vi.mocked(fs.existsSync).mockReturnValue(false);
84
+ tracker.setProjectPath(mockProjectPath);
85
+ const stats = tracker.getStats();
86
+ expect(stats.operationCount).toBe(0);
87
+ });
88
+ it('should handle corrupted JSON gracefully', () => {
89
+ vi.mocked(fs.existsSync).mockReturnValue(true);
90
+ vi.mocked(fs.readFileSync).mockReturnValue('not valid json');
91
+ tracker.setProjectPath(mockProjectPath);
92
+ const stats = tracker.getStats();
93
+ expect(stats.operationCount).toBe(0);
94
+ });
95
+ });
96
+ describe('event recording', () => {
97
+ beforeEach(() => {
98
+ vi.useFakeTimers();
99
+ tracker.setProjectPath(mockProjectPath);
100
+ });
101
+ it('should record search_code events with correct estimates', () => {
102
+ tracker.recordSearchCode(1000, 3, 50);
103
+ const stats = tracker.getStats();
104
+ expect(stats.operationCount).toBe(1);
105
+ expect(stats.byType.search_code.count).toBe(1);
106
+ expect(stats.byType.search_code.charsReturned).toBe(1000);
107
+ // charsAvoided = max(0, 100 * 60 - 1000) = 5000
108
+ expect(stats.byType.search_code.charsAvoided).toBe(5000);
109
+ });
110
+ it('should record search_similar events', () => {
111
+ tracker.recordSearchSimilar(500, 5);
112
+ const stats = tracker.getStats();
113
+ expect(stats.byType.search_similar.count).toBe(1);
114
+ expect(stats.byType.search_similar.charsReturned).toBe(500);
115
+ });
116
+ it('should record get_symbols_overview events', () => {
117
+ tracker.recordSymbolsOverview(200, 100);
118
+ const stats = tracker.getStats();
119
+ expect(stats.byType.get_symbols_overview.count).toBe(1);
120
+ // charsAvoided = max(0, 100 * 60 - 200) = 5800
121
+ expect(stats.byType.get_symbols_overview.charsAvoided).toBe(5800);
122
+ });
123
+ it('should record find_symbol events', () => {
124
+ tracker.recordFindSymbol(300, 20);
125
+ const stats = tracker.getStats();
126
+ expect(stats.byType.find_symbol.count).toBe(1);
127
+ });
128
+ it('should record summarize_codebase events', () => {
129
+ tracker.recordSummarizeCodebase(1500, 100);
130
+ const stats = tracker.getStats();
131
+ expect(stats.byType.summarize_codebase.count).toBe(1);
132
+ });
133
+ it('should record list_concepts events', () => {
134
+ tracker.recordListConcepts(400, 5);
135
+ const stats = tracker.getStats();
136
+ expect(stats.byType.list_concepts.count).toBe(1);
137
+ });
138
+ it('should record search_by_concept events', () => {
139
+ tracker.recordSearchByConcept(600, 10);
140
+ const stats = tracker.getStats();
141
+ expect(stats.byType.search_by_concept.count).toBe(1);
142
+ });
143
+ it('should not record negative charsAvoided', () => {
144
+ // Return more chars than estimated, should clamp to 0
145
+ tracker.recordSearchCode(10000, 1, 10);
146
+ const stats = tracker.getStats();
147
+ expect(stats.byType.search_code.charsAvoided).toBe(0);
148
+ });
149
+ });
150
+ describe('getStats', () => {
151
+ beforeEach(() => {
152
+ vi.useFakeTimers();
153
+ tracker.setProjectPath(mockProjectPath);
154
+ });
155
+ it('should calculate correct totals', () => {
156
+ tracker.recordSearchCode(100, 2, 50);
157
+ tracker.recordFindSymbol(200, 30);
158
+ const stats = tracker.getStats();
159
+ expect(stats.operationCount).toBe(2);
160
+ expect(stats.charsReturned).toBe(300);
161
+ });
162
+ it('should calculate efficiency percentage correctly', () => {
163
+ // Record event where we return 1000 chars and avoid 5000
164
+ tracker.recordSearchCode(1000, 2, 50);
165
+ const stats = tracker.getStats();
166
+ // Total chars = 1000 + 5000 = 6000
167
+ // Efficiency = 5000 / 6000 = 83.33% -> rounds to 83%
168
+ expect(stats.efficiencyPercent).toBe(83);
169
+ });
170
+ it('should return 0% efficiency when no events', () => {
171
+ const stats = tracker.getStats();
172
+ expect(stats.efficiencyPercent).toBe(0);
173
+ });
174
+ it('should calculate tokens saved correctly', () => {
175
+ // 4 chars per token
176
+ tracker.recordSearchCode(1000, 2, 50);
177
+ const stats = tracker.getStats();
178
+ // charsAvoided = 5000, tokens = 5000 / 4 = 1250
179
+ expect(stats.tokensSaved).toBe(1250);
180
+ });
181
+ });
182
+ describe('event rotation', () => {
183
+ beforeEach(() => {
184
+ vi.useFakeTimers();
185
+ tracker.setProjectPath(mockProjectPath);
186
+ });
187
+ it('should rotate events when exceeding MAX_EVENTS', () => {
188
+ // Record more than 1000 events
189
+ for (let i = 0; i < 1005; i++) {
190
+ tracker.recordSearchCode(100, 1, 10);
191
+ }
192
+ const stats = tracker.getStats();
193
+ // Should be capped at 1000
194
+ expect(stats.operationCount).toBe(1000);
195
+ });
196
+ it('should keep most recent events when rotating', () => {
197
+ // Load persisted data with 1005 events
198
+ const events = [];
199
+ for (let i = 0; i < 1005; i++) {
200
+ events.push({
201
+ type: 'search_code',
202
+ timestamp: 1000 + i,
203
+ charsReturned: 100,
204
+ charsAvoided: 500,
205
+ filesAvoided: 1,
206
+ });
207
+ }
208
+ vi.mocked(fs.existsSync).mockReturnValue(true);
209
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ events, sessionStart: 1000 }));
210
+ tracker.setProjectPath(mockProjectPath);
211
+ const recentEvents = tracker.getRecentEvents(5);
212
+ // Should have the most recent events (highest timestamps)
213
+ expect(recentEvents[0].timestamp).toBeGreaterThan(1000);
214
+ });
215
+ });
216
+ describe('debounced saving', () => {
217
+ beforeEach(() => {
218
+ vi.useFakeTimers();
219
+ vi.mocked(fs.existsSync).mockReturnValue(true);
220
+ vi.mocked(fs.mkdirSync).mockReturnValue(undefined);
221
+ vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
222
+ tracker.setProjectPath(mockProjectPath);
223
+ });
224
+ it('should debounce multiple rapid saves', () => {
225
+ tracker.recordSearchCode(100, 1, 10);
226
+ tracker.recordSearchCode(200, 2, 20);
227
+ tracker.recordSearchCode(300, 3, 30);
228
+ // Should not have saved yet
229
+ expect(fs.writeFileSync).not.toHaveBeenCalled();
230
+ // Advance past debounce delay
231
+ vi.advanceTimersByTime(1500);
232
+ // Should have saved once with all events
233
+ expect(fs.writeFileSync).toHaveBeenCalledTimes(1);
234
+ const savedData = JSON.parse(fs.writeFileSync.mock.calls[0][1]);
235
+ expect(savedData.events.length).toBe(3);
236
+ });
237
+ });
238
+ describe('onUpdate callback', () => {
239
+ beforeEach(() => {
240
+ vi.useFakeTimers();
241
+ tracker.setProjectPath(mockProjectPath);
242
+ });
243
+ it('should call onUpdate callback when events are recorded', () => {
244
+ const callback = vi.fn();
245
+ tracker.setOnUpdate(callback);
246
+ tracker.recordSearchCode(100, 1, 10);
247
+ expect(callback).toHaveBeenCalledTimes(1);
248
+ });
249
+ it('should call onUpdate for each event recorded', () => {
250
+ const callback = vi.fn();
251
+ tracker.setOnUpdate(callback);
252
+ tracker.recordSearchCode(100, 1, 10);
253
+ tracker.recordFindSymbol(200, 20);
254
+ expect(callback).toHaveBeenCalledTimes(2);
255
+ });
256
+ });
257
+ describe('getRecentEvents', () => {
258
+ beforeEach(() => {
259
+ vi.useFakeTimers();
260
+ tracker.setProjectPath(mockProjectPath);
261
+ });
262
+ it('should return last N events', () => {
263
+ for (let i = 0; i < 20; i++) {
264
+ tracker.recordSearchCode(100 + i, 1, 10);
265
+ }
266
+ const recent = tracker.getRecentEvents(5);
267
+ expect(recent.length).toBe(5);
268
+ // Should be the last 5 (highest charsReturned values)
269
+ expect(recent[0].charsReturned).toBe(115);
270
+ expect(recent[4].charsReturned).toBe(119);
271
+ });
272
+ it('should return all events if fewer than limit', () => {
273
+ tracker.recordSearchCode(100, 1, 10);
274
+ tracker.recordSearchCode(200, 2, 20);
275
+ const recent = tracker.getRecentEvents(10);
276
+ expect(recent.length).toBe(2);
277
+ });
278
+ it('should default to 10 events', () => {
279
+ for (let i = 0; i < 20; i++) {
280
+ tracker.recordSearchCode(100, 1, 10);
281
+ }
282
+ const recent = tracker.getRecentEvents();
283
+ expect(recent.length).toBe(10);
284
+ });
285
+ });
286
+ describe('filesAvoided estimates', () => {
287
+ beforeEach(() => {
288
+ vi.useFakeTimers();
289
+ tracker.setProjectPath(mockProjectPath);
290
+ });
291
+ it('should estimate filesAvoided for search_code based on files searched', () => {
292
+ // 50 files searched, 3 matched -> avoided ~7 files (min(10, 50) - 3)
293
+ tracker.recordSearchCode(100, 3, 50);
294
+ const events = tracker.getRecentEvents(1);
295
+ expect(events[0].filesAvoided).toBe(7);
296
+ });
297
+ it('should estimate filesAvoided for search_similar as at least 3', () => {
298
+ tracker.recordSearchSimilar(100, 2);
299
+ const events = tracker.getRecentEvents(1);
300
+ expect(events[0].filesAvoided).toBe(3);
301
+ });
302
+ it('should estimate filesAvoided for summarize_codebase capped at 20', () => {
303
+ tracker.recordSummarizeCodebase(100, 100);
304
+ const events = tracker.getRecentEvents(1);
305
+ expect(events[0].filesAvoided).toBe(20);
306
+ });
307
+ it('should estimate filesAvoided for list_concepts as 3x clusters capped at 15', () => {
308
+ tracker.recordListConcepts(100, 10);
309
+ const events = tracker.getRecentEvents(1);
310
+ // 10 clusters * 3 = 30, capped at 15
311
+ expect(events[0].filesAvoided).toBe(15);
312
+ });
313
+ });
314
+ });
315
+ //# sourceMappingURL=token-tracking.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-tracking.test.js","sourceRoot":"","sources":["../../../src/__tests__/dashboard/token-tracking.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=factory.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/embeddings/factory.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { createSuccessFetch } from '../mocks/fetch.mock.js';
3
+ import { DEFAULT_OLLAMA_MODEL } from '../../embeddings/ollama.js';
4
+ /** Helper to create a mock /api/tags response with the default model available */
5
+ function createTagsResponseWithDefaultModel() {
6
+ return { models: [{ name: DEFAULT_OLLAMA_MODEL }] };
7
+ }
8
+ /** Helper to create a mock Gemini embedding response */
9
+ function createGeminiEmbeddingResponse() {
10
+ return { embedding: { values: [0.1, 0.2] } };
11
+ }
12
+ // We need to dynamically import createEmbeddingBackend after setting env vars
13
+ async function getCreateEmbeddingBackend() {
14
+ // Reset module cache to pick up new env vars
15
+ vi.resetModules();
16
+ const module = await import('../../embeddings/index.js');
17
+ return module.createEmbeddingBackend;
18
+ }
19
+ describe('createEmbeddingBackend', () => {
20
+ const originalEnv = { ...process.env };
21
+ beforeEach(() => {
22
+ // Clear relevant env vars
23
+ delete process.env.GEMINI_API_KEY;
24
+ delete process.env.OLLAMA_URL;
25
+ });
26
+ afterEach(() => {
27
+ // Restore original env
28
+ process.env = { ...originalEnv };
29
+ vi.unstubAllGlobals();
30
+ vi.resetModules();
31
+ });
32
+ describe('priority order', () => {
33
+ it('should prefer Gemini when GEMINI_API_KEY is set', async () => {
34
+ process.env.GEMINI_API_KEY = 'test-gemini-key';
35
+ const mockFetch = vi.fn().mockResolvedValue({
36
+ ok: true,
37
+ status: 200,
38
+ json: async () => createGeminiEmbeddingResponse(),
39
+ });
40
+ vi.stubGlobal('fetch', mockFetch);
41
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
42
+ const { backend } = await createEmbeddingBackend();
43
+ expect(backend.name).toBe('gemini');
44
+ });
45
+ it('should fallback to Ollama when Gemini fails', async () => {
46
+ process.env.GEMINI_API_KEY = 'invalid-gemini-key';
47
+ const mockFetch = vi
48
+ .fn()
49
+ // Gemini fails
50
+ .mockResolvedValueOnce({ ok: false, status: 401, text: async () => 'Unauthorized' })
51
+ // Ollama succeeds (with default model available)
52
+ .mockResolvedValue({
53
+ ok: true,
54
+ status: 200,
55
+ json: async () => createTagsResponseWithDefaultModel(),
56
+ });
57
+ vi.stubGlobal('fetch', mockFetch);
58
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
59
+ const { backend } = await createEmbeddingBackend();
60
+ expect(backend.name).toBe('ollama');
61
+ });
62
+ it('should use Ollama when no API keys are set', async () => {
63
+ vi.stubGlobal('fetch', createSuccessFetch(createTagsResponseWithDefaultModel()));
64
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
65
+ const { backend } = await createEmbeddingBackend();
66
+ expect(backend.name).toBe('ollama');
67
+ });
68
+ });
69
+ describe('error when all backends fail', () => {
70
+ it('should throw when no backend is available', async () => {
71
+ const mockFetch = vi.fn().mockRejectedValue(new Error('Connection refused'));
72
+ vi.stubGlobal('fetch', mockFetch);
73
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
74
+ await expect(createEmbeddingBackend()).rejects.toThrow('No embedding backend available');
75
+ });
76
+ });
77
+ describe('config options', () => {
78
+ it('should pass apiKey to Gemini backend from config', async () => {
79
+ const mockFetch = vi.fn().mockResolvedValue({
80
+ ok: true,
81
+ status: 200,
82
+ json: async () => createGeminiEmbeddingResponse(),
83
+ });
84
+ vi.stubGlobal('fetch', mockFetch);
85
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
86
+ const { backend } = await createEmbeddingBackend({ apiKey: 'config-gemini-key' });
87
+ expect(backend.name).toBe('gemini');
88
+ });
89
+ it('should use OLLAMA_URL from environment', async () => {
90
+ process.env.OLLAMA_URL = 'http://remote-ollama:11434';
91
+ const mockFetch = vi.fn().mockImplementation(async (url) => {
92
+ expect(url).toContain('remote-ollama');
93
+ return { ok: true, status: 200, json: async () => createTagsResponseWithDefaultModel() };
94
+ });
95
+ vi.stubGlobal('fetch', mockFetch);
96
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
97
+ const { backend } = await createEmbeddingBackend();
98
+ expect(backend.name).toBe('ollama');
99
+ });
100
+ it('should prefer baseUrl from config over environment', async () => {
101
+ process.env.OLLAMA_URL = 'http://env-ollama:11434';
102
+ const mockFetch = vi.fn().mockImplementation(async (url) => {
103
+ expect(url).toContain('config-ollama');
104
+ return { ok: true, status: 200, json: async () => createTagsResponseWithDefaultModel() };
105
+ });
106
+ vi.stubGlobal('fetch', mockFetch);
107
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
108
+ const { backend } = await createEmbeddingBackend({ baseUrl: 'http://config-ollama:11434' });
109
+ expect(backend.name).toBe('ollama');
110
+ });
111
+ });
112
+ describe('explicit backend with fallback', () => {
113
+ it('should fallback to Ollama when explicitly configured Gemini fails', async () => {
114
+ process.env.GEMINI_API_KEY = 'test-key';
115
+ const mockFetch = vi
116
+ .fn()
117
+ // Gemini initialization fails with an error
118
+ .mockResolvedValueOnce({ ok: false, status: 401, text: async () => 'Invalid API key' })
119
+ // Ollama succeeds
120
+ .mockResolvedValue({
121
+ ok: true,
122
+ status: 200,
123
+ json: async () => createTagsResponseWithDefaultModel(),
124
+ });
125
+ vi.stubGlobal('fetch', mockFetch);
126
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
127
+ const { backend, fallback } = await createEmbeddingBackend({ backend: 'gemini' });
128
+ expect(backend.name).toBe('ollama');
129
+ expect(fallback).toBeDefined();
130
+ expect(fallback?.occurred).toBe(true);
131
+ expect(fallback?.originalBackend).toBe('gemini');
132
+ expect(fallback?.fallbackBackend).toBe('ollama');
133
+ expect(fallback?.reason).toContain('401');
134
+ });
135
+ it('should throw when explicitly configured Gemini fails and Ollama also fails', async () => {
136
+ process.env.GEMINI_API_KEY = 'test-key';
137
+ const mockFetch = vi.fn().mockRejectedValue(new Error('Connection refused'));
138
+ vi.stubGlobal('fetch', mockFetch);
139
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
140
+ await expect(createEmbeddingBackend({ backend: 'gemini' })).rejects.toThrow('Configured gemini backend failed');
141
+ });
142
+ it('should not have fallback info when explicitly configured backend succeeds', async () => {
143
+ process.env.GEMINI_API_KEY = 'test-key';
144
+ const mockFetch = vi.fn().mockResolvedValue({
145
+ ok: true,
146
+ status: 200,
147
+ json: async () => createGeminiEmbeddingResponse(),
148
+ });
149
+ vi.stubGlobal('fetch', mockFetch);
150
+ const createEmbeddingBackend = await getCreateEmbeddingBackend();
151
+ const { backend, fallback } = await createEmbeddingBackend({ backend: 'gemini' });
152
+ expect(backend.name).toBe('gemini');
153
+ expect(fallback).toBeUndefined();
154
+ });
155
+ });
156
+ });
157
+ //# sourceMappingURL=factory.test.js.map