@skillsmith/core 0.4.14 → 0.4.16

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 (287) hide show
  1. package/README.md +2 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/analysis/DependencyMerger.d.ts +39 -0
  4. package/dist/src/analysis/DependencyMerger.d.ts.map +1 -0
  5. package/dist/src/analysis/DependencyMerger.js +191 -0
  6. package/dist/src/analysis/DependencyMerger.js.map +1 -0
  7. package/dist/src/analysis/DependencyMerger.test.d.ts +2 -0
  8. package/dist/src/analysis/DependencyMerger.test.d.ts.map +1 -0
  9. package/dist/src/analysis/DependencyMerger.test.js +258 -0
  10. package/dist/src/analysis/DependencyMerger.test.js.map +1 -0
  11. package/dist/src/analysis/McpReferenceExtractor.d.ts +42 -0
  12. package/dist/src/analysis/McpReferenceExtractor.d.ts.map +1 -0
  13. package/dist/src/analysis/McpReferenceExtractor.js +92 -0
  14. package/dist/src/analysis/McpReferenceExtractor.js.map +1 -0
  15. package/dist/src/analysis/McpReferenceExtractor.test.d.ts +2 -0
  16. package/dist/src/analysis/McpReferenceExtractor.test.d.ts.map +1 -0
  17. package/dist/src/analysis/McpReferenceExtractor.test.js +262 -0
  18. package/dist/src/analysis/McpReferenceExtractor.test.js.map +1 -0
  19. package/dist/src/api/client.d.ts +1 -0
  20. package/dist/src/api/client.d.ts.map +1 -1
  21. package/dist/src/api/client.health.d.ts +26 -0
  22. package/dist/src/api/client.health.d.ts.map +1 -0
  23. package/dist/src/api/client.health.js +74 -0
  24. package/dist/src/api/client.health.js.map +1 -0
  25. package/dist/src/api/client.js +3 -51
  26. package/dist/src/api/client.js.map +1 -1
  27. package/dist/src/api/schemas.d.ts +4 -4
  28. package/dist/src/db/createDatabase.d.ts +9 -1
  29. package/dist/src/db/createDatabase.d.ts.map +1 -1
  30. package/dist/src/db/createDatabase.js +9 -1
  31. package/dist/src/db/createDatabase.js.map +1 -1
  32. package/dist/src/db/drivers/sqljsDriver.d.ts.map +1 -1
  33. package/dist/src/db/drivers/sqljsDriver.js +7 -8
  34. package/dist/src/db/drivers/sqljsDriver.js.map +1 -1
  35. package/dist/src/db/migrations/v10-dependencies.d.ts +18 -0
  36. package/dist/src/db/migrations/v10-dependencies.d.ts.map +1 -0
  37. package/dist/src/db/migrations/v10-dependencies.js +50 -0
  38. package/dist/src/db/migrations/v10-dependencies.js.map +1 -0
  39. package/dist/src/db/migrations/v2-phase5-columns.d.ts +7 -0
  40. package/dist/src/db/migrations/v2-phase5-columns.d.ts.map +1 -0
  41. package/dist/src/db/migrations/v2-phase5-columns.js +16 -0
  42. package/dist/src/db/migrations/v2-phase5-columns.js.map +1 -0
  43. package/dist/src/db/migrations/v3-sync-tables.d.ts +6 -0
  44. package/dist/src/db/migrations/v3-sync-tables.d.ts.map +1 -0
  45. package/dist/src/db/migrations/v3-sync-tables.js +41 -0
  46. package/dist/src/db/migrations/v3-sync-tables.js.map +1 -0
  47. package/dist/src/db/migrations/v4-security-columns.d.ts +7 -0
  48. package/dist/src/db/migrations/v4-security-columns.d.ts.map +1 -0
  49. package/dist/src/db/migrations/v4-security-columns.js +17 -0
  50. package/dist/src/db/migrations/v4-security-columns.js.map +1 -0
  51. package/dist/src/db/migrations/v5b-change-type.d.ts +19 -0
  52. package/dist/src/db/migrations/v5b-change-type.d.ts.map +1 -0
  53. package/dist/src/db/migrations/v5b-change-type.js +22 -0
  54. package/dist/src/db/migrations/v5b-change-type.js.map +1 -0
  55. package/dist/src/db/migrations/v6-advisories.d.ts +32 -0
  56. package/dist/src/db/migrations/v6-advisories.d.ts.map +1 -0
  57. package/dist/src/db/migrations/v6-advisories.js +53 -0
  58. package/dist/src/db/migrations/v6-advisories.js.map +1 -0
  59. package/dist/src/db/migrations/v7-compatibility.d.ts +21 -0
  60. package/dist/src/db/migrations/v7-compatibility.d.ts.map +1 -0
  61. package/dist/src/db/migrations/v7-compatibility.js +23 -0
  62. package/dist/src/db/migrations/v7-compatibility.js.map +1 -0
  63. package/dist/src/db/migrations/v8-co-installs.d.ts +21 -0
  64. package/dist/src/db/migrations/v8-co-installs.d.ts.map +1 -0
  65. package/dist/src/db/migrations/v8-co-installs.js +33 -0
  66. package/dist/src/db/migrations/v8-co-installs.js.map +1 -0
  67. package/dist/src/db/schema.d.ts +5 -16
  68. package/dist/src/db/schema.d.ts.map +1 -1
  69. package/dist/src/db/schema.js +50 -79
  70. package/dist/src/db/schema.js.map +1 -1
  71. package/dist/src/exports/repositories.d.ts +6 -0
  72. package/dist/src/exports/repositories.d.ts.map +1 -1
  73. package/dist/src/exports/repositories.js +21 -0
  74. package/dist/src/exports/repositories.js.map +1 -1
  75. package/dist/src/exports/services.d.ts +2 -0
  76. package/dist/src/exports/services.d.ts.map +1 -1
  77. package/dist/src/exports/services.js +5 -0
  78. package/dist/src/exports/services.js.map +1 -1
  79. package/dist/src/exports/types.d.ts +2 -1
  80. package/dist/src/exports/types.d.ts.map +1 -1
  81. package/dist/src/exports/types.js.map +1 -1
  82. package/dist/src/indexer/GitHubIndexer.d.ts +3 -1
  83. package/dist/src/indexer/GitHubIndexer.d.ts.map +1 -1
  84. package/dist/src/indexer/GitHubIndexer.js.map +1 -1
  85. package/dist/src/indexer/SkillParser.d.ts +22 -9
  86. package/dist/src/indexer/SkillParser.d.ts.map +1 -1
  87. package/dist/src/indexer/SkillParser.js +17 -10
  88. package/dist/src/indexer/SkillParser.js.map +1 -1
  89. package/dist/src/learning/PatternStore.d.ts +0 -2
  90. package/dist/src/learning/PatternStore.d.ts.map +1 -1
  91. package/dist/src/learning/PatternStore.helpers.d.ts +19 -1
  92. package/dist/src/learning/PatternStore.helpers.d.ts.map +1 -1
  93. package/dist/src/learning/PatternStore.helpers.js +51 -0
  94. package/dist/src/learning/PatternStore.helpers.js.map +1 -1
  95. package/dist/src/learning/PatternStore.js +5 -37
  96. package/dist/src/learning/PatternStore.js.map +1 -1
  97. package/dist/src/repositories/AdvisoryRepository.d.ts +80 -0
  98. package/dist/src/repositories/AdvisoryRepository.d.ts.map +1 -0
  99. package/dist/src/repositories/AdvisoryRepository.js +128 -0
  100. package/dist/src/repositories/AdvisoryRepository.js.map +1 -0
  101. package/dist/src/repositories/AdvisoryRepository.test.d.ts +6 -0
  102. package/dist/src/repositories/AdvisoryRepository.test.d.ts.map +1 -0
  103. package/dist/src/repositories/AdvisoryRepository.test.js +149 -0
  104. package/dist/src/repositories/AdvisoryRepository.test.js.map +1 -0
  105. package/dist/src/repositories/CoInstallRepository.d.ts +68 -0
  106. package/dist/src/repositories/CoInstallRepository.d.ts.map +1 -0
  107. package/dist/src/repositories/CoInstallRepository.js +122 -0
  108. package/dist/src/repositories/CoInstallRepository.js.map +1 -0
  109. package/dist/src/repositories/IndexerRepository.d.ts.map +1 -1
  110. package/dist/src/repositories/IndexerRepository.js +5 -1
  111. package/dist/src/repositories/IndexerRepository.js.map +1 -1
  112. package/dist/src/repositories/SkillDependencyRepository.d.ts +73 -0
  113. package/dist/src/repositories/SkillDependencyRepository.d.ts.map +1 -0
  114. package/dist/src/repositories/SkillDependencyRepository.js +199 -0
  115. package/dist/src/repositories/SkillDependencyRepository.js.map +1 -0
  116. package/dist/src/repositories/SkillRepository.d.ts.map +1 -1
  117. package/dist/src/repositories/SkillRepository.js +16 -5
  118. package/dist/src/repositories/SkillRepository.js.map +1 -1
  119. package/dist/src/scripts/validation/types.d.ts +2 -2
  120. package/dist/src/services/SearchService.helpers.d.ts.map +1 -1
  121. package/dist/src/services/SearchService.helpers.js +4 -0
  122. package/dist/src/services/SearchService.helpers.js.map +1 -1
  123. package/dist/src/services/SearchService.types.d.ts +1 -0
  124. package/dist/src/services/SearchService.types.d.ts.map +1 -1
  125. package/dist/src/services/TaskRunner.d.ts +5 -81
  126. package/dist/src/services/TaskRunner.d.ts.map +1 -1
  127. package/dist/src/services/TaskRunner.js +7 -68
  128. package/dist/src/services/TaskRunner.js.map +1 -1
  129. package/dist/src/services/TaskRunner.process.d.ts +33 -0
  130. package/dist/src/services/TaskRunner.process.d.ts.map +1 -0
  131. package/dist/src/services/TaskRunner.process.js +70 -0
  132. package/dist/src/services/TaskRunner.process.js.map +1 -0
  133. package/dist/src/services/TaskRunner.types.d.ts +76 -0
  134. package/dist/src/services/TaskRunner.types.d.ts.map +1 -0
  135. package/dist/src/services/TaskRunner.types.js +22 -0
  136. package/dist/src/services/TaskRunner.types.js.map +1 -0
  137. package/dist/src/services/__tests__/TaskRunner.process.test.d.ts +13 -0
  138. package/dist/src/services/__tests__/TaskRunner.process.test.d.ts.map +1 -0
  139. package/dist/src/services/__tests__/TaskRunner.process.test.js +91 -0
  140. package/dist/src/services/__tests__/TaskRunner.process.test.js.map +1 -0
  141. package/dist/src/services/quarantine/QuarantineService.d.ts +3 -24
  142. package/dist/src/services/quarantine/QuarantineService.d.ts.map +1 -1
  143. package/dist/src/services/quarantine/QuarantineService.js +8 -205
  144. package/dist/src/services/quarantine/QuarantineService.js.map +1 -1
  145. package/dist/src/services/quarantine/QuarantineService.multiapproval.d.ts +57 -0
  146. package/dist/src/services/quarantine/QuarantineService.multiapproval.d.ts.map +1 -0
  147. package/dist/src/services/quarantine/QuarantineService.multiapproval.js +211 -0
  148. package/dist/src/services/quarantine/QuarantineService.multiapproval.js.map +1 -0
  149. package/dist/src/session/SessionManager.d.ts +2 -33
  150. package/dist/src/session/SessionManager.d.ts.map +1 -1
  151. package/dist/src/session/SessionManager.js +14 -238
  152. package/dist/src/session/SessionManager.js.map +1 -1
  153. package/dist/src/session/SessionManager.memory.d.ts +67 -0
  154. package/dist/src/session/SessionManager.memory.d.ts.map +1 -0
  155. package/dist/src/session/SessionManager.memory.js +262 -0
  156. package/dist/src/session/SessionManager.memory.js.map +1 -0
  157. package/dist/src/sources/SourceIndexer.d.ts +3 -1
  158. package/dist/src/sources/SourceIndexer.d.ts.map +1 -1
  159. package/dist/src/sources/SourceIndexer.js.map +1 -1
  160. package/dist/src/sync/SyncEngine.d.ts +13 -1
  161. package/dist/src/sync/SyncEngine.d.ts.map +1 -1
  162. package/dist/src/sync/SyncEngine.js +50 -1
  163. package/dist/src/sync/SyncEngine.js.map +1 -1
  164. package/dist/src/testing/MultiLLMProvider.d.ts +5 -6
  165. package/dist/src/testing/MultiLLMProvider.d.ts.map +1 -1
  166. package/dist/src/testing/MultiLLMProvider.js +18 -130
  167. package/dist/src/testing/MultiLLMProvider.js.map +1 -1
  168. package/dist/src/testing/MultiLLMProvider.metrics.d.ts +33 -0
  169. package/dist/src/testing/MultiLLMProvider.metrics.d.ts.map +1 -0
  170. package/dist/src/testing/MultiLLMProvider.metrics.js +87 -0
  171. package/dist/src/testing/MultiLLMProvider.metrics.js.map +1 -0
  172. package/dist/src/testing/MultiLLMProvider.selection.d.ts +77 -0
  173. package/dist/src/testing/MultiLLMProvider.selection.d.ts.map +1 -0
  174. package/dist/src/testing/MultiLLMProvider.selection.js +151 -0
  175. package/dist/src/testing/MultiLLMProvider.selection.js.map +1 -0
  176. package/dist/src/types/dependencies.d.ts +85 -0
  177. package/dist/src/types/dependencies.d.ts.map +1 -0
  178. package/dist/src/types/dependencies.js +11 -0
  179. package/dist/src/types/dependencies.js.map +1 -0
  180. package/dist/src/types/skill.d.ts +13 -0
  181. package/dist/src/types/skill.d.ts.map +1 -1
  182. package/dist/src/types/skill.js.map +1 -1
  183. package/dist/src/types.d.ts +32 -0
  184. package/dist/src/types.d.ts.map +1 -1
  185. package/dist/src/versioning/change-classifier.d.ts +38 -0
  186. package/dist/src/versioning/change-classifier.d.ts.map +1 -0
  187. package/dist/src/versioning/change-classifier.js +187 -0
  188. package/dist/src/versioning/change-classifier.js.map +1 -0
  189. package/dist/src/versioning/change-classifier.test.d.ts +6 -0
  190. package/dist/src/versioning/change-classifier.test.d.ts.map +1 -0
  191. package/dist/src/versioning/change-classifier.test.js +275 -0
  192. package/dist/src/versioning/change-classifier.test.js.map +1 -0
  193. package/dist/src/versioning/update-risk.d.ts +50 -0
  194. package/dist/src/versioning/update-risk.d.ts.map +1 -0
  195. package/dist/src/versioning/update-risk.js +80 -0
  196. package/dist/src/versioning/update-risk.js.map +1 -0
  197. package/dist/src/versioning/update-risk.test.d.ts +6 -0
  198. package/dist/src/versioning/update-risk.test.d.ts.map +1 -0
  199. package/dist/src/versioning/update-risk.test.js +200 -0
  200. package/dist/src/versioning/update-risk.test.js.map +1 -0
  201. package/dist/tests/AuditLogger.edge-cases.test.d.ts +10 -0
  202. package/dist/tests/AuditLogger.edge-cases.test.d.ts.map +1 -0
  203. package/dist/tests/AuditLogger.edge-cases.test.js +183 -0
  204. package/dist/tests/AuditLogger.edge-cases.test.js.map +1 -0
  205. package/dist/tests/CacheManager.test.d.ts +9 -0
  206. package/dist/tests/CacheManager.test.d.ts.map +1 -0
  207. package/dist/tests/CacheManager.test.js +163 -0
  208. package/dist/tests/CacheManager.test.js.map +1 -0
  209. package/dist/tests/DailyIndexPipeline.test.js +3 -3
  210. package/dist/tests/DailyIndexPipeline.test.js.map +1 -1
  211. package/dist/tests/GitHubIndexer.edge-cases.test.d.ts +10 -0
  212. package/dist/tests/GitHubIndexer.edge-cases.test.d.ts.map +1 -0
  213. package/dist/tests/GitHubIndexer.edge-cases.test.js +255 -0
  214. package/dist/tests/GitHubIndexer.edge-cases.test.js.map +1 -0
  215. package/dist/tests/GitHubIndexer.test.js +4 -2
  216. package/dist/tests/GitHubIndexer.test.js.map +1 -1
  217. package/dist/tests/SearchService.test.js +71 -0
  218. package/dist/tests/SearchService.test.js.map +1 -1
  219. package/dist/tests/SkillVersionRepository.test.d.ts +0 -11
  220. package/dist/tests/SkillVersionRepository.test.d.ts.map +1 -1
  221. package/dist/tests/SkillVersionRepository.test.js +131 -194
  222. package/dist/tests/SkillVersionRepository.test.js.map +1 -1
  223. package/dist/tests/api/client.health.test.d.ts +15 -0
  224. package/dist/tests/api/client.health.test.d.ts.map +1 -0
  225. package/dist/tests/api/client.health.test.js +111 -0
  226. package/dist/tests/api/client.health.test.js.map +1 -0
  227. package/dist/tests/db/betterSqlite3Driver.test.d.ts +8 -0
  228. package/dist/tests/db/betterSqlite3Driver.test.d.ts.map +1 -0
  229. package/dist/tests/db/betterSqlite3Driver.test.js +88 -0
  230. package/dist/tests/db/betterSqlite3Driver.test.js.map +1 -0
  231. package/dist/tests/db/database-abstraction.test.js +12 -0
  232. package/dist/tests/db/database-abstraction.test.js.map +1 -1
  233. package/dist/tests/db/schema-migrations.test.d.ts +10 -0
  234. package/dist/tests/db/schema-migrations.test.d.ts.map +1 -0
  235. package/dist/tests/db/schema-migrations.test.js +134 -0
  236. package/dist/tests/db/schema-migrations.test.js.map +1 -0
  237. package/dist/tests/db/sqljsDriver.test.d.ts +9 -0
  238. package/dist/tests/db/sqljsDriver.test.d.ts.map +1 -0
  239. package/dist/tests/db/sqljsDriver.test.js +75 -0
  240. package/dist/tests/db/sqljsDriver.test.js.map +1 -0
  241. package/dist/tests/edge-cases/EdgeCases.test.js +4 -1
  242. package/dist/tests/edge-cases/EdgeCases.test.js.map +1 -1
  243. package/dist/tests/helpers/database.d.ts +32 -0
  244. package/dist/tests/helpers/database.d.ts.map +1 -0
  245. package/dist/tests/helpers/database.js +54 -0
  246. package/dist/tests/helpers/database.js.map +1 -0
  247. package/dist/tests/learning/PatternStore.helpers.test.d.ts +17 -0
  248. package/dist/tests/learning/PatternStore.helpers.test.d.ts.map +1 -0
  249. package/dist/tests/learning/PatternStore.helpers.test.js +301 -0
  250. package/dist/tests/learning/PatternStore.helpers.test.js.map +1 -0
  251. package/dist/tests/repositories/CoInstallRepository.test.d.ts +9 -0
  252. package/dist/tests/repositories/CoInstallRepository.test.d.ts.map +1 -0
  253. package/dist/tests/repositories/CoInstallRepository.test.js +126 -0
  254. package/dist/tests/repositories/CoInstallRepository.test.js.map +1 -0
  255. package/dist/tests/repositories/SkillDependencyRepository.test.d.ts +10 -0
  256. package/dist/tests/repositories/SkillDependencyRepository.test.d.ts.map +1 -0
  257. package/dist/tests/repositories/SkillDependencyRepository.test.js +336 -0
  258. package/dist/tests/repositories/SkillDependencyRepository.test.js.map +1 -0
  259. package/dist/tests/routing/LanguageRouter.test.d.ts +9 -0
  260. package/dist/tests/routing/LanguageRouter.test.d.ts.map +1 -0
  261. package/dist/tests/routing/LanguageRouter.test.js +150 -0
  262. package/dist/tests/routing/LanguageRouter.test.js.map +1 -0
  263. package/dist/tests/session/SessionManager.memory.test.d.ts +11 -0
  264. package/dist/tests/session/SessionManager.memory.test.d.ts.map +1 -0
  265. package/dist/tests/session/SessionManager.memory.test.js +219 -0
  266. package/dist/tests/session/SessionManager.memory.test.js.map +1 -0
  267. package/dist/tests/sources.test.js +1 -1
  268. package/dist/tests/sources.test.js.map +1 -1
  269. package/dist/tests/sync/SyncEngine.test.js +32 -18
  270. package/dist/tests/sync/SyncEngine.test.js.map +1 -1
  271. package/dist/tests/testing/MultiLLMProvider.metrics.test.d.ts +13 -0
  272. package/dist/tests/testing/MultiLLMProvider.metrics.test.d.ts.map +1 -0
  273. package/dist/tests/testing/MultiLLMProvider.metrics.test.js +149 -0
  274. package/dist/tests/testing/MultiLLMProvider.metrics.test.js.map +1 -0
  275. package/dist/tests/testing/MultiLLMProvider.selection.test.d.ts +15 -0
  276. package/dist/tests/testing/MultiLLMProvider.selection.test.d.ts.map +1 -0
  277. package/dist/tests/testing/MultiLLMProvider.selection.test.js +249 -0
  278. package/dist/tests/testing/MultiLLMProvider.selection.test.js.map +1 -0
  279. package/dist/tests/unit/migrations/v10-dependencies.test.d.ts +6 -0
  280. package/dist/tests/unit/migrations/v10-dependencies.test.d.ts.map +1 -0
  281. package/dist/tests/unit/migrations/v10-dependencies.test.js +166 -0
  282. package/dist/tests/unit/migrations/v10-dependencies.test.js.map +1 -0
  283. package/dist/tests/unit/quarantine-query-builder.test.d.ts +10 -0
  284. package/dist/tests/unit/quarantine-query-builder.test.d.ts.map +1 -0
  285. package/dist/tests/unit/quarantine-query-builder.test.js +157 -0
  286. package/dist/tests/unit/quarantine-query-builder.test.js.map +1 -0
  287. package/package.json +1 -1
@@ -0,0 +1,149 @@
1
+ /**
2
+ * SMI-2754: MultiLLMProvider Metrics Tests
3
+ *
4
+ * Tests for aggregateMetrics and runCompatibilityTest:
5
+ * - aggregateMetrics with populated map
6
+ * - aggregateMetrics with empty map (providerCount === 0 branch)
7
+ * - runCompatibilityTest with all providers succeeding
8
+ * - runCompatibilityTest with one provider throwing
9
+ * - runCompatibilityTest result filtering (score >= 0.7)
10
+ * - runCompatibilityTest with no providers
11
+ */
12
+ import { describe, it, expect, vi } from 'vitest';
13
+ import { aggregateMetrics, runCompatibilityTest, } from '../../src/testing/MultiLLMProvider.metrics.js';
14
+ function makeMetrics(provider, overrides = {}) {
15
+ return {
16
+ provider,
17
+ timestamp: new Date(),
18
+ avgLatencyMs: 100,
19
+ errorRate: 0,
20
+ successRate: 1,
21
+ load: 0,
22
+ totalCost: 0.5,
23
+ totalRequests: 10,
24
+ availability: 1,
25
+ ...overrides,
26
+ };
27
+ }
28
+ function makeLLMResponse(provider, latencyMs = 200) {
29
+ return {
30
+ content: 'Test response content',
31
+ provider,
32
+ model: 'test-model',
33
+ usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 },
34
+ cost: 0.001,
35
+ latencyMs,
36
+ usedFallback: false,
37
+ };
38
+ }
39
+ // ============================================================================
40
+ // aggregateMetrics
41
+ // ============================================================================
42
+ describe('aggregateMetrics', () => {
43
+ it('aggregates totals correctly from a populated map', () => {
44
+ const metricsMap = new Map([
45
+ [
46
+ 'anthropic',
47
+ makeMetrics('anthropic', {
48
+ totalRequests: 10,
49
+ totalCost: 1.0,
50
+ avgLatencyMs: 200,
51
+ successRate: 0.9,
52
+ }),
53
+ ],
54
+ [
55
+ 'openai',
56
+ makeMetrics('openai', {
57
+ totalRequests: 5,
58
+ totalCost: 0.5,
59
+ avgLatencyMs: 100,
60
+ successRate: 0.8,
61
+ }),
62
+ ],
63
+ ]);
64
+ const result = aggregateMetrics(metricsMap);
65
+ expect(result.totalRequests).toBe(15);
66
+ expect(result.totalCost).toBeCloseTo(1.5);
67
+ // weighted avg: (200*10 + 100*5) / 15 = 2500/15 ≈ 166.67
68
+ expect(result.avgLatencyMs).toBeCloseTo(166.67, 1);
69
+ // avg success rate: (0.9 + 0.8) / 2 = 0.85
70
+ expect(result.avgSuccessRate).toBeCloseTo(0.85);
71
+ expect(result.providerBreakdown.anthropic).toBe(10);
72
+ expect(result.providerBreakdown.openai).toBe(5);
73
+ });
74
+ it('returns zero values when map is empty (providerCount === 0)', () => {
75
+ const metricsMap = new Map();
76
+ const result = aggregateMetrics(metricsMap);
77
+ expect(result.totalRequests).toBe(0);
78
+ expect(result.totalCost).toBe(0);
79
+ expect(result.avgLatencyMs).toBe(0);
80
+ expect(result.avgSuccessRate).toBe(0);
81
+ });
82
+ });
83
+ // ============================================================================
84
+ // runCompatibilityTest
85
+ // ============================================================================
86
+ describe('runCompatibilityTest', () => {
87
+ it('records compatible=true with positive score when all providers succeed', async () => {
88
+ const completer = vi
89
+ .fn()
90
+ .mockImplementation(async (req) => makeLLMResponse(req.provider, 100));
91
+ const result = await runCompatibilityTest('community/jest-helper', ['anthropic', 'openai'], completer);
92
+ expect(result.skillId).toBe('community/jest-helper');
93
+ expect(result.results.anthropic.compatible).toBe(true);
94
+ expect(result.results.anthropic.score).toBeGreaterThan(0);
95
+ expect(result.results.openai.compatible).toBe(true);
96
+ expect(result.overallScore).toBeGreaterThan(0);
97
+ });
98
+ it('records compatible=false with score 0 when a provider throws', async () => {
99
+ const completer = vi.fn().mockImplementation(async (req) => {
100
+ if (req.provider === 'openai') {
101
+ throw new Error('Provider unavailable');
102
+ }
103
+ return makeLLMResponse(req.provider, 200);
104
+ });
105
+ const result = await runCompatibilityTest('community/jest-helper', ['anthropic', 'openai'], completer);
106
+ expect(result.results.anthropic.compatible).toBe(true);
107
+ expect(result.results.openai.compatible).toBe(false);
108
+ expect(result.results.openai.score).toBe(0);
109
+ expect(result.results.openai.error).toBe('Provider unavailable');
110
+ });
111
+ it('includes only providers with score >= 0.7 in recommendedProviders', async () => {
112
+ const completer = vi.fn().mockImplementation(async (req) => {
113
+ // Low latency → high score (1 - 50/5000 + 0.3 = 1.29 → clamped to 1.0)
114
+ if (req.provider === 'anthropic') {
115
+ return makeLLMResponse('anthropic', 50);
116
+ }
117
+ // Very high latency → score of 0.3 (1 - 4900/5000 + 0.3 = 0.32)
118
+ return makeLLMResponse('openai', 4900);
119
+ });
120
+ const result = await runCompatibilityTest('community/test-skill', ['anthropic', 'openai'], completer);
121
+ // anthropic has a high score (>= 0.7); openai has score ~0.32 (< 0.7)
122
+ expect(result.recommendedProviders).toContain('anthropic');
123
+ expect(result.recommendedProviders).not.toContain('openai');
124
+ });
125
+ it('returns overallScore 0 and empty recommendedProviders when no providers given', async () => {
126
+ const completer = vi.fn();
127
+ const result = await runCompatibilityTest('skill/empty', [], completer);
128
+ expect(result.overallScore).toBe(0);
129
+ expect(result.recommendedProviders).toHaveLength(0);
130
+ expect(completer).not.toHaveBeenCalled();
131
+ });
132
+ it('sets testedAt to a Date close to now', async () => {
133
+ const before = new Date();
134
+ const completer = vi.fn().mockResolvedValue(makeLLMResponse('anthropic', 100));
135
+ const result = await runCompatibilityTest('skill/time-check', ['anthropic'], completer);
136
+ const after = new Date();
137
+ expect(result.testedAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
138
+ expect(result.testedAt.getTime()).toBeLessThanOrEqual(after.getTime() + 5);
139
+ });
140
+ it('handles non-Error throws by recording "Unknown error"', async () => {
141
+ const completer = vi.fn().mockImplementationOnce(() => {
142
+ throw 'string error thrown'; // non-Error object
143
+ });
144
+ const result = await runCompatibilityTest('skill/string-throw', ['anthropic'], completer);
145
+ expect(result.results.anthropic.compatible).toBe(false);
146
+ expect(result.results.anthropic.error).toBe('Unknown error');
147
+ });
148
+ });
149
+ //# sourceMappingURL=MultiLLMProvider.metrics.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiLLMProvider.metrics.test.js","sourceRoot":"","sources":["../../../tests/testing/MultiLLMProvider.metrics.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SMI-2754: MultiLLMProvider Selection Strategy Tests
3
+ *
4
+ * Tests for selectProvider and getFallbackProvider covering all uncovered branches:
5
+ * - cost-constraint branch
6
+ * - least-loaded strategy
7
+ * - latency-based strategy
8
+ * - cost-based strategy
9
+ * - default-provider fallback
10
+ * - first-available fallback
11
+ * - getFallbackProvider: no matching rule
12
+ * - getFallbackProvider: matching rule with excluded current provider
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=MultiLLMProvider.selection.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiLLMProvider.selection.test.d.ts","sourceRoot":"","sources":["../../../tests/testing/MultiLLMProvider.selection.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,249 @@
1
+ /**
2
+ * SMI-2754: MultiLLMProvider Selection Strategy Tests
3
+ *
4
+ * Tests for selectProvider and getFallbackProvider covering all uncovered branches:
5
+ * - cost-constraint branch
6
+ * - least-loaded strategy
7
+ * - latency-based strategy
8
+ * - cost-based strategy
9
+ * - default-provider fallback
10
+ * - first-available fallback
11
+ * - getFallbackProvider: no matching rule
12
+ * - getFallbackProvider: matching rule with excluded current provider
13
+ */
14
+ import { describe, it, expect } from 'vitest';
15
+ import { selectProvider, getFallbackProvider, selectRoundRobin, selectLeastLoaded, selectByLatency, selectByCost, } from '../../src/testing/MultiLLMProvider.selection.js';
16
+ // ============================================================================
17
+ // Shared test fixtures
18
+ // ============================================================================
19
+ function makeConfig(overrides = {}) {
20
+ return {
21
+ defaultProvider: 'anthropic',
22
+ providers: {
23
+ anthropic: {
24
+ provider: 'anthropic',
25
+ model: 'claude-sonnet',
26
+ enabled: true,
27
+ priority: 'quality',
28
+ costPerInputToken: 0.003,
29
+ costPerOutputToken: 0.015,
30
+ },
31
+ openai: {
32
+ provider: 'openai',
33
+ model: 'gpt-4',
34
+ enabled: true,
35
+ priority: 'speed',
36
+ costPerInputToken: 0.01,
37
+ costPerOutputToken: 0.03,
38
+ },
39
+ google: {
40
+ provider: 'google',
41
+ model: 'gemini-pro',
42
+ enabled: true,
43
+ priority: 'cost',
44
+ costPerInputToken: 0.00025,
45
+ costPerOutputToken: 0.0005,
46
+ },
47
+ },
48
+ loadBalancing: { enabled: false, strategy: 'round-robin' },
49
+ costOptimization: { enabled: false },
50
+ circuitBreaker: {
51
+ timeoutMs: 30000,
52
+ errorThresholdPercentage: 50,
53
+ resetTimeoutMs: 60000,
54
+ volumeThreshold: 10,
55
+ },
56
+ enableMetrics: false,
57
+ useV3Integration: false,
58
+ fallbackStrategy: {
59
+ name: 'default',
60
+ enabled: true,
61
+ maxAttempts: 3,
62
+ rules: [],
63
+ },
64
+ ...overrides,
65
+ };
66
+ }
67
+ function makeStatus(currentLoad = 0) {
68
+ return {
69
+ available: true,
70
+ currentLoad,
71
+ queueLength: 0,
72
+ activeRequests: 0,
73
+ circuitState: 'closed',
74
+ };
75
+ }
76
+ function makeMetrics(avgLatencyMs = 100) {
77
+ return {
78
+ provider: 'anthropic',
79
+ timestamp: new Date(),
80
+ avgLatencyMs,
81
+ errorRate: 0,
82
+ successRate: 1,
83
+ load: 0,
84
+ totalCost: 0,
85
+ totalRequests: 10,
86
+ availability: 1,
87
+ };
88
+ }
89
+ // ============================================================================
90
+ // selectRoundRobin
91
+ // ============================================================================
92
+ describe('selectRoundRobin', () => {
93
+ it('selects provider at roundRobinIndex % providers.length and increments index', () => {
94
+ const providers = ['anthropic', 'openai', 'google'];
95
+ const result = selectRoundRobin(providers, 1);
96
+ expect(result.provider).toBe('openai');
97
+ expect(result.nextIndex).toBe(2);
98
+ });
99
+ it('wraps around using modulo', () => {
100
+ const providers = ['anthropic', 'openai'];
101
+ const result = selectRoundRobin(providers, 4);
102
+ expect(result.provider).toBe('anthropic'); // 4 % 2 = 0
103
+ });
104
+ });
105
+ // ============================================================================
106
+ // selectLeastLoaded
107
+ // ============================================================================
108
+ describe('selectLeastLoaded', () => {
109
+ it('selects the provider with the lowest currentLoad', () => {
110
+ const providers = ['anthropic', 'openai', 'google'];
111
+ const loads = { anthropic: 0.8, openai: 0.2, google: 0.5 };
112
+ const getStatus = (p) => makeStatus(loads[p]);
113
+ expect(selectLeastLoaded(providers, getStatus)).toBe('openai');
114
+ });
115
+ });
116
+ // ============================================================================
117
+ // selectByLatency
118
+ // ============================================================================
119
+ describe('selectByLatency', () => {
120
+ it('selects the provider with the lowest avgLatencyMs', () => {
121
+ const providers = ['anthropic', 'openai', 'google'];
122
+ const latencies = { anthropic: 300, openai: 100, google: 200 };
123
+ const getMetrics = (p) => makeMetrics(latencies[p] ?? Infinity);
124
+ expect(selectByLatency(providers, getMetrics)).toBe('openai');
125
+ });
126
+ it('treats missing metrics as Infinity latency', () => {
127
+ const providers = ['anthropic', 'openai'];
128
+ const getMetrics = (p) => (p === 'openai' ? makeMetrics(50) : undefined);
129
+ expect(selectByLatency(providers, getMetrics)).toBe('openai');
130
+ });
131
+ });
132
+ // ============================================================================
133
+ // selectByCost
134
+ // ============================================================================
135
+ describe('selectByCost', () => {
136
+ it('selects the provider with the lowest combined cost per token', () => {
137
+ const providers = ['anthropic', 'openai', 'google'];
138
+ const config = makeConfig();
139
+ // google has 0.00025 + 0.0005 = 0.00075 (cheapest)
140
+ expect(selectByCost(providers, config.providers)).toBe('google');
141
+ });
142
+ });
143
+ // ============================================================================
144
+ // selectProvider
145
+ // ============================================================================
146
+ describe('selectProvider', () => {
147
+ const available = ['anthropic', 'openai', 'google'];
148
+ const getStatus = (p) => makeStatus(p === 'openai' ? 0.1 : 0.9);
149
+ const getMetrics = (p) => makeMetrics(p === 'google' ? 50 : 300);
150
+ it('throws when available list is empty', () => {
151
+ const config = makeConfig();
152
+ expect(() => selectProvider({ messages: [] }, [], config, 0, getStatus, getMetrics)).toThrow('No providers available');
153
+ });
154
+ it('uses cost constraint branch when costConstraints.maxCost is set and costOptimization is enabled', () => {
155
+ const config = makeConfig({ costOptimization: { enabled: true } });
156
+ const result = selectProvider({ messages: [], costConstraints: { maxCost: 0.1 } }, available, config, 0, getStatus, getMetrics);
157
+ // google is cheapest (0.00075 per token total)
158
+ expect(result.provider).toBe('google');
159
+ // round-robin index should be unchanged
160
+ expect(result.nextRoundRobinIndex).toBe(0);
161
+ });
162
+ it('uses least-loaded strategy when load balancing is enabled with least-loaded', () => {
163
+ const config = makeConfig({
164
+ loadBalancing: { enabled: true, strategy: 'least-loaded' },
165
+ });
166
+ const result = selectProvider({ messages: [] }, available, config, 0, getStatus, getMetrics);
167
+ expect(result.provider).toBe('openai'); // lowest load (0.1)
168
+ });
169
+ it('uses latency-based strategy when load balancing is enabled with latency-based', () => {
170
+ const config = makeConfig({
171
+ loadBalancing: { enabled: true, strategy: 'latency-based' },
172
+ });
173
+ const result = selectProvider({ messages: [] }, available, config, 0, getStatus, getMetrics);
174
+ expect(result.provider).toBe('google'); // lowest latency (50ms)
175
+ });
176
+ it('uses cost-based strategy when load balancing is enabled with cost-based', () => {
177
+ const config = makeConfig({
178
+ loadBalancing: { enabled: true, strategy: 'cost-based' },
179
+ });
180
+ const result = selectProvider({ messages: [] }, available, config, 0, getStatus, getMetrics);
181
+ expect(result.provider).toBe('google'); // lowest cost
182
+ });
183
+ it('falls back to configured defaultProvider when load balancing is disabled', () => {
184
+ const config = makeConfig({ loadBalancing: { enabled: false, strategy: 'round-robin' } });
185
+ const result = selectProvider({ messages: [] }, available, config, 0, getStatus, getMetrics);
186
+ expect(result.provider).toBe('anthropic'); // default
187
+ });
188
+ it('falls back to first-available when defaultProvider is not in available list', () => {
189
+ const config = makeConfig({
190
+ defaultProvider: 'cohere',
191
+ loadBalancing: { enabled: false, strategy: 'round-robin' },
192
+ });
193
+ const result = selectProvider({ messages: [] }, ['openai', 'google'], config, 0, getStatus, getMetrics);
194
+ expect(result.provider).toBe('openai'); // first available
195
+ });
196
+ });
197
+ // ============================================================================
198
+ // getFallbackProvider
199
+ // ============================================================================
200
+ describe('getFallbackProvider', () => {
201
+ const available = ['anthropic', 'openai', 'google'];
202
+ it('returns null when no rule matches the error condition', () => {
203
+ const rules = [
204
+ {
205
+ condition: 'rate_limit',
206
+ fallbackProviders: ['openai'],
207
+ retryOriginal: false,
208
+ },
209
+ ];
210
+ // A generic error produces 'error' condition, no rule for that
211
+ const result = getFallbackProvider('anthropic', new Error('generic fail'), rules, available);
212
+ expect(result).toBeNull();
213
+ });
214
+ it('returns the first fallback provider that is not the current provider', () => {
215
+ const rules = [
216
+ {
217
+ condition: 'error',
218
+ fallbackProviders: ['anthropic', 'openai', 'google'],
219
+ retryOriginal: false,
220
+ },
221
+ ];
222
+ // current=anthropic is excluded; openai is next available
223
+ const result = getFallbackProvider('anthropic', new Error('provider error'), rules, available);
224
+ expect(result).toBe('openai');
225
+ });
226
+ it('returns null when all fallback providers are either current or unavailable', () => {
227
+ const rules = [
228
+ {
229
+ condition: 'error',
230
+ fallbackProviders: ['anthropic'], // only option is the current one
231
+ retryOriginal: false,
232
+ },
233
+ ];
234
+ const result = getFallbackProvider('anthropic', new Error('provider error'), rules, available);
235
+ expect(result).toBeNull();
236
+ });
237
+ it('uses rate_limit condition when error message contains "rate limit"', () => {
238
+ const rules = [
239
+ {
240
+ condition: 'rate_limit',
241
+ fallbackProviders: ['google'],
242
+ retryOriginal: true,
243
+ },
244
+ ];
245
+ const result = getFallbackProvider('anthropic', new Error('rate limit exceeded'), rules, available);
246
+ expect(result).toBe('google');
247
+ });
248
+ });
249
+ //# sourceMappingURL=MultiLLMProvider.selection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiLLMProvider.selection.test.js","sourceRoot":"","sources":["../../../tests/testing/MultiLLMProvider.selection.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @fileoverview Tests for migration v10 — skill_dependencies table
3
+ * @see SMI-3134: Database Schema — skill_dependencies Table
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=v10-dependencies.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v10-dependencies.test.d.ts","sourceRoot":"","sources":["../../../../tests/unit/migrations/v10-dependencies.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @fileoverview Tests for migration v10 — skill_dependencies table
3
+ * @see SMI-3134: Database Schema — skill_dependencies Table
4
+ */
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { createTestDatabase, closeDatabase } from '../../helpers/database.js';
7
+ import { getSchemaVersion, SCHEMA_VERSION } from '../../../src/db/schema.js';
8
+ import { MIGRATION_V10_SQL } from '../../../src/db/migrations/v10-dependencies.js';
9
+ describe('Migration v10: skill_dependencies table', () => {
10
+ let db;
11
+ beforeEach(() => {
12
+ db = createTestDatabase();
13
+ });
14
+ afterEach(() => {
15
+ closeDatabase(db);
16
+ });
17
+ it('creates the skill_dependencies table', () => {
18
+ const table = db
19
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='skill_dependencies'")
20
+ .get();
21
+ expect(table).toBeDefined();
22
+ expect(table.name).toBe('skill_dependencies');
23
+ });
24
+ it('has all expected columns with correct types', () => {
25
+ const columns = db.prepare('PRAGMA table_info(skill_dependencies)').all();
26
+ const columnMap = new Map(columns.map((c) => [c.name, c]));
27
+ // Verify all expected columns exist
28
+ const expectedColumns = [
29
+ 'id',
30
+ 'skill_id',
31
+ 'dep_type',
32
+ 'dep_target',
33
+ 'dep_version',
34
+ 'dep_source',
35
+ 'confidence',
36
+ 'metadata',
37
+ 'created_at',
38
+ 'updated_at',
39
+ ];
40
+ for (const col of expectedColumns) {
41
+ expect(columnMap.has(col), `column '${col}' should exist`).toBe(true);
42
+ }
43
+ // Check NOT NULL constraints
44
+ expect(columnMap.get('skill_id').notnull).toBe(1);
45
+ expect(columnMap.get('dep_type').notnull).toBe(1);
46
+ expect(columnMap.get('dep_target').notnull).toBe(1);
47
+ expect(columnMap.get('dep_source').notnull).toBe(1);
48
+ expect(columnMap.get('created_at').notnull).toBe(1);
49
+ expect(columnMap.get('updated_at').notnull).toBe(1);
50
+ // Optional columns
51
+ expect(columnMap.get('dep_version').notnull).toBe(0);
52
+ expect(columnMap.get('confidence').notnull).toBe(0);
53
+ expect(columnMap.get('metadata').notnull).toBe(0);
54
+ // Primary key
55
+ expect(columnMap.get('id').pk).toBe(1);
56
+ });
57
+ it('migration is idempotent — running twice does not error', () => {
58
+ // createTestDatabase already ran the migration once.
59
+ // Running the SQL again should not throw (IF NOT EXISTS).
60
+ expect(() => db.exec(MIGRATION_V10_SQL)).not.toThrow();
61
+ });
62
+ it('bumps schema version to 10', () => {
63
+ expect(getSchemaVersion(db)).toBe(10);
64
+ expect(SCHEMA_VERSION).toBe(10);
65
+ });
66
+ it('unique index prevents duplicate (skill_id, dep_type, dep_target, dep_source)', () => {
67
+ const insert = db.prepare(`
68
+ INSERT INTO skill_dependencies (skill_id, dep_type, dep_target, dep_source)
69
+ VALUES (?, ?, ?, ?)
70
+ `);
71
+ // First insert succeeds
72
+ insert.run('author/skill-a', 'mcp_server', '@modelcontextprotocol/server-github', 'declared');
73
+ // Same combination fails with constraint error
74
+ expect(() => insert.run('author/skill-a', 'mcp_server', '@modelcontextprotocol/server-github', 'declared')).toThrow(/UNIQUE constraint failed/);
75
+ });
76
+ it('allows same skill_id + dep_target with different dep_source', () => {
77
+ const insert = db.prepare(`
78
+ INSERT INTO skill_dependencies (skill_id, dep_type, dep_target, dep_source)
79
+ VALUES (?, ?, ?, ?)
80
+ `);
81
+ // Same target, different source — both should succeed
82
+ insert.run('author/skill-a', 'mcp_server', 'server-github', 'declared');
83
+ insert.run('author/skill-a', 'mcp_server', 'server-github', 'inferred_static');
84
+ const count = db.prepare('SELECT COUNT(*) as cnt FROM skill_dependencies').get();
85
+ expect(count.cnt).toBe(2);
86
+ });
87
+ describe('CHECK constraints', () => {
88
+ it('rejects invalid dep_type', () => {
89
+ const insert = db.prepare(`
90
+ INSERT INTO skill_dependencies (skill_id, dep_type, dep_target, dep_source)
91
+ VALUES (?, ?, ?, ?)
92
+ `);
93
+ expect(() => insert.run('author/skill-a', 'invalid_type', 'target', 'declared')).toThrow(/CHECK constraint failed/);
94
+ });
95
+ it('rejects invalid dep_source', () => {
96
+ const insert = db.prepare(`
97
+ INSERT INTO skill_dependencies (skill_id, dep_type, dep_target, dep_source)
98
+ VALUES (?, ?, ?, ?)
99
+ `);
100
+ expect(() => insert.run('author/skill-a', 'mcp_server', 'target', 'invalid_source')).toThrow(/CHECK constraint failed/);
101
+ });
102
+ it('accepts all valid dep_type values', () => {
103
+ const validTypes = [
104
+ 'skill_hard',
105
+ 'skill_soft',
106
+ 'skill_peer',
107
+ 'mcp_server',
108
+ 'model_minimum',
109
+ 'model_capability',
110
+ 'env_tool',
111
+ 'env_os',
112
+ 'env_node',
113
+ 'cli_version',
114
+ 'conflict',
115
+ ];
116
+ const insert = db.prepare(`
117
+ INSERT INTO skill_dependencies (skill_id, dep_type, dep_target, dep_source)
118
+ VALUES (?, ?, ?, ?)
119
+ `);
120
+ for (const depType of validTypes) {
121
+ expect(() => insert.run(`author/skill-${depType}`, depType, `target-${depType}`, 'declared')).not.toThrow();
122
+ }
123
+ });
124
+ it('accepts all valid dep_source values', () => {
125
+ const validSources = ['declared', 'inferred_static', 'inferred_coinstall'];
126
+ const insert = db.prepare(`
127
+ INSERT INTO skill_dependencies (skill_id, dep_type, dep_target, dep_source)
128
+ VALUES (?, ?, ?, ?)
129
+ `);
130
+ for (const source of validSources) {
131
+ expect(() => insert.run(`author/skill-${source}`, 'mcp_server', `target-${source}`, source)).not.toThrow();
132
+ }
133
+ });
134
+ it('rejects confidence outside 0.0-1.0 range', () => {
135
+ const insert = db.prepare(`
136
+ INSERT INTO skill_dependencies (skill_id, dep_type, dep_target, dep_source, confidence)
137
+ VALUES (?, ?, ?, ?, ?)
138
+ `);
139
+ expect(() => insert.run('author/skill-a', 'mcp_server', 'target', 'declared', 1.5)).toThrow(/CHECK constraint failed/);
140
+ expect(() => insert.run('author/skill-b', 'mcp_server', 'target', 'declared', -0.1)).toThrow(/CHECK constraint failed/);
141
+ });
142
+ it('accepts confidence within 0.0-1.0 range and NULL', () => {
143
+ const insert = db.prepare(`
144
+ INSERT INTO skill_dependencies (skill_id, dep_type, dep_target, dep_source, confidence)
145
+ VALUES (?, ?, ?, ?, ?)
146
+ `);
147
+ // Valid values
148
+ expect(() => insert.run('author/skill-c0', 'mcp_server', 'target-0', 'declared', 0.0)).not.toThrow();
149
+ expect(() => insert.run('author/skill-c1', 'mcp_server', 'target-1', 'declared', 1.0)).not.toThrow();
150
+ expect(() => insert.run('author/skill-c5', 'mcp_server', 'target-5', 'declared', 0.5)).not.toThrow();
151
+ expect(() => insert.run('author/skill-cn', 'mcp_server', 'target-n', 'declared', null)).not.toThrow();
152
+ });
153
+ });
154
+ it('creates expected indexes', () => {
155
+ const indexes = db
156
+ .prepare("SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='skill_dependencies'")
157
+ .all();
158
+ const indexNames = indexes.map((i) => i.name);
159
+ expect(indexNames).toContain('idx_skill_deps_skill');
160
+ expect(indexNames).toContain('idx_skill_deps_target');
161
+ expect(indexNames).toContain('idx_skill_deps_type');
162
+ expect(indexNames).toContain('idx_skill_deps_source');
163
+ expect(indexNames).toContain('idx_skill_deps_unique');
164
+ });
165
+ });
166
+ //# sourceMappingURL=v10-dependencies.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v10-dependencies.test.js","sourceRoot":"","sources":["../../../../tests/unit/migrations/v10-dependencies.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SMI-2755 Wave 2: Quarantine query builder pure-function tests
3
+ *
4
+ * Tests for buildFilteredQuery and rowToEntry from
5
+ * packages/core/src/repositories/quarantine/query-builder.ts.
6
+ *
7
+ * No mocking needed — both are pure functions.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=quarantine-query-builder.test.d.ts.map