@sonde/packs 0.1.0 → 0.1.1

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 (359) hide show
  1. package/.turbo/turbo-build.log +6 -0
  2. package/.turbo/turbo-test.log +814 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/CHANGELOG.md +10 -0
  5. package/dist/index.d.ts +16 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +40 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/integrations/citrix.d.ts +13 -0
  10. package/dist/integrations/citrix.d.ts.map +1 -0
  11. package/dist/integrations/citrix.js +420 -0
  12. package/dist/integrations/citrix.js.map +1 -0
  13. package/dist/integrations/citrix.test.d.ts +2 -0
  14. package/dist/integrations/citrix.test.d.ts.map +1 -0
  15. package/dist/integrations/citrix.test.js +464 -0
  16. package/dist/integrations/citrix.test.js.map +1 -0
  17. package/dist/integrations/graph.d.ts +9 -0
  18. package/dist/integrations/graph.d.ts.map +1 -0
  19. package/dist/integrations/graph.js +290 -0
  20. package/dist/integrations/graph.js.map +1 -0
  21. package/dist/integrations/graph.test.d.ts +2 -0
  22. package/dist/integrations/graph.test.d.ts.map +1 -0
  23. package/dist/integrations/graph.test.js +356 -0
  24. package/dist/integrations/graph.test.js.map +1 -0
  25. package/dist/integrations/httpbin.d.ts +3 -0
  26. package/dist/integrations/httpbin.d.ts.map +1 -0
  27. package/dist/integrations/httpbin.js +70 -0
  28. package/dist/integrations/httpbin.js.map +1 -0
  29. package/dist/integrations/nutanix.d.ts +18 -0
  30. package/dist/integrations/nutanix.d.ts.map +1 -0
  31. package/dist/integrations/nutanix.js +1121 -0
  32. package/dist/integrations/nutanix.js.map +1 -0
  33. package/dist/integrations/nutanix.test.d.ts +2 -0
  34. package/dist/integrations/nutanix.test.d.ts.map +1 -0
  35. package/dist/integrations/nutanix.test.js +978 -0
  36. package/dist/integrations/nutanix.test.js.map +1 -0
  37. package/dist/integrations/proxmox.d.ts +12 -0
  38. package/dist/integrations/proxmox.d.ts.map +1 -0
  39. package/dist/integrations/proxmox.js +733 -0
  40. package/dist/integrations/proxmox.js.map +1 -0
  41. package/dist/integrations/proxmox.test.d.ts +2 -0
  42. package/dist/integrations/proxmox.test.d.ts.map +1 -0
  43. package/dist/integrations/proxmox.test.js +697 -0
  44. package/dist/integrations/proxmox.test.js.map +1 -0
  45. package/dist/integrations/servicenow.d.ts +3 -0
  46. package/dist/integrations/servicenow.d.ts.map +1 -0
  47. package/dist/integrations/servicenow.js +257 -0
  48. package/dist/integrations/servicenow.js.map +1 -0
  49. package/dist/integrations/servicenow.test.d.ts +2 -0
  50. package/dist/integrations/servicenow.test.d.ts.map +1 -0
  51. package/dist/integrations/servicenow.test.js +217 -0
  52. package/dist/integrations/servicenow.test.js.map +1 -0
  53. package/dist/integrations/splunk.d.ts +9 -0
  54. package/dist/integrations/splunk.d.ts.map +1 -0
  55. package/dist/integrations/splunk.js +242 -0
  56. package/dist/integrations/splunk.js.map +1 -0
  57. package/dist/integrations/splunk.test.d.ts +2 -0
  58. package/dist/integrations/splunk.test.d.ts.map +1 -0
  59. package/dist/integrations/splunk.test.js +323 -0
  60. package/dist/integrations/splunk.test.js.map +1 -0
  61. package/dist/mysql/index.d.ts +3 -0
  62. package/dist/mysql/index.d.ts.map +1 -0
  63. package/dist/mysql/index.js +13 -0
  64. package/dist/mysql/index.js.map +1 -0
  65. package/dist/mysql/manifest.d.ts +3 -0
  66. package/dist/mysql/manifest.d.ts.map +1 -0
  67. package/dist/mysql/manifest.js +69 -0
  68. package/dist/mysql/manifest.js.map +1 -0
  69. package/dist/mysql/probes/databases-list.d.ts +13 -0
  70. package/dist/mysql/probes/databases-list.d.ts.map +1 -0
  71. package/dist/mysql/probes/databases-list.js +31 -0
  72. package/dist/mysql/probes/databases-list.js.map +1 -0
  73. package/dist/mysql/probes/databases-list.test.d.ts +2 -0
  74. package/dist/mysql/probes/databases-list.test.d.ts.map +1 -0
  75. package/dist/mysql/probes/databases-list.test.js +54 -0
  76. package/dist/mysql/probes/databases-list.test.js.map +1 -0
  77. package/dist/mysql/probes/processlist.d.ts +18 -0
  78. package/dist/mysql/probes/processlist.d.ts.map +1 -0
  79. package/dist/mysql/probes/processlist.js +36 -0
  80. package/dist/mysql/probes/processlist.js.map +1 -0
  81. package/dist/mysql/probes/processlist.test.d.ts +2 -0
  82. package/dist/mysql/probes/processlist.test.d.ts.map +1 -0
  83. package/dist/mysql/probes/processlist.test.js +41 -0
  84. package/dist/mysql/probes/processlist.test.js.map +1 -0
  85. package/dist/mysql/probes/status.d.ts +14 -0
  86. package/dist/mysql/probes/status.d.ts.map +1 -0
  87. package/dist/mysql/probes/status.js +40 -0
  88. package/dist/mysql/probes/status.js.map +1 -0
  89. package/dist/mysql/probes/status.test.d.ts +2 -0
  90. package/dist/mysql/probes/status.test.d.ts.map +1 -0
  91. package/dist/mysql/probes/status.test.js +43 -0
  92. package/dist/mysql/probes/status.test.js.map +1 -0
  93. package/dist/nginx/index.d.ts +3 -0
  94. package/dist/nginx/index.d.ts.map +1 -0
  95. package/dist/nginx/index.js +13 -0
  96. package/dist/nginx/index.js.map +1 -0
  97. package/dist/nginx/manifest.d.ts +3 -0
  98. package/dist/nginx/manifest.d.ts.map +1 -0
  99. package/dist/nginx/manifest.js +68 -0
  100. package/dist/nginx/manifest.js.map +1 -0
  101. package/dist/nginx/probes/access-log-tail.d.ts +9 -0
  102. package/dist/nginx/probes/access-log-tail.d.ts.map +1 -0
  103. package/dist/nginx/probes/access-log-tail.js +14 -0
  104. package/dist/nginx/probes/access-log-tail.js.map +1 -0
  105. package/dist/nginx/probes/access-log-tail.test.d.ts +2 -0
  106. package/dist/nginx/probes/access-log-tail.test.d.ts.map +1 -0
  107. package/dist/nginx/probes/access-log-tail.test.js +40 -0
  108. package/dist/nginx/probes/access-log-tail.test.js.map +1 -0
  109. package/dist/nginx/probes/config-test.d.ts +8 -0
  110. package/dist/nginx/probes/config-test.d.ts.map +1 -0
  111. package/dist/nginx/probes/config-test.js +18 -0
  112. package/dist/nginx/probes/config-test.js.map +1 -0
  113. package/dist/nginx/probes/config-test.test.d.ts +2 -0
  114. package/dist/nginx/probes/config-test.test.d.ts.map +1 -0
  115. package/dist/nginx/probes/config-test.test.js +35 -0
  116. package/dist/nginx/probes/config-test.test.js.map +1 -0
  117. package/dist/nginx/probes/error-log-tail.d.ts +9 -0
  118. package/dist/nginx/probes/error-log-tail.d.ts.map +1 -0
  119. package/dist/nginx/probes/error-log-tail.js +14 -0
  120. package/dist/nginx/probes/error-log-tail.js.map +1 -0
  121. package/dist/nginx/probes/error-log-tail.test.d.ts +2 -0
  122. package/dist/nginx/probes/error-log-tail.test.d.ts.map +1 -0
  123. package/dist/nginx/probes/error-log-tail.test.js +34 -0
  124. package/dist/nginx/probes/error-log-tail.test.js.map +1 -0
  125. package/dist/postgres/index.d.ts +3 -0
  126. package/dist/postgres/index.d.ts.map +1 -0
  127. package/dist/postgres/index.js +13 -0
  128. package/dist/postgres/index.js.map +1 -0
  129. package/dist/postgres/manifest.d.ts +3 -0
  130. package/dist/postgres/manifest.d.ts.map +1 -0
  131. package/dist/postgres/manifest.js +90 -0
  132. package/dist/postgres/manifest.js.map +1 -0
  133. package/dist/postgres/probes/connections-active.d.ts +17 -0
  134. package/dist/postgres/probes/connections-active.d.ts.map +1 -0
  135. package/dist/postgres/probes/connections-active.js +37 -0
  136. package/dist/postgres/probes/connections-active.js.map +1 -0
  137. package/dist/postgres/probes/connections-active.test.d.ts +2 -0
  138. package/dist/postgres/probes/connections-active.test.d.ts.map +1 -0
  139. package/dist/postgres/probes/connections-active.test.js +36 -0
  140. package/dist/postgres/probes/connections-active.test.js.map +1 -0
  141. package/dist/postgres/probes/databases-list.d.ts +14 -0
  142. package/dist/postgres/probes/databases-list.d.ts.map +1 -0
  143. package/dist/postgres/probes/databases-list.js +34 -0
  144. package/dist/postgres/probes/databases-list.js.map +1 -0
  145. package/dist/postgres/probes/databases-list.test.d.ts +2 -0
  146. package/dist/postgres/probes/databases-list.test.d.ts.map +1 -0
  147. package/dist/postgres/probes/databases-list.test.js +49 -0
  148. package/dist/postgres/probes/databases-list.test.js.map +1 -0
  149. package/dist/postgres/probes/query-slow.d.ts +17 -0
  150. package/dist/postgres/probes/query-slow.d.ts.map +1 -0
  151. package/dist/postgres/probes/query-slow.js +37 -0
  152. package/dist/postgres/probes/query-slow.js.map +1 -0
  153. package/dist/postgres/probes/query-slow.test.d.ts +2 -0
  154. package/dist/postgres/probes/query-slow.test.d.ts.map +1 -0
  155. package/dist/postgres/probes/query-slow.test.js +30 -0
  156. package/dist/postgres/probes/query-slow.test.js.map +1 -0
  157. package/dist/proxmox/index.d.ts +3 -0
  158. package/dist/proxmox/index.d.ts.map +1 -0
  159. package/dist/proxmox/index.js +23 -0
  160. package/dist/proxmox/index.js.map +1 -0
  161. package/dist/proxmox/manifest.d.ts +3 -0
  162. package/dist/proxmox/manifest.d.ts.map +1 -0
  163. package/dist/proxmox/manifest.js +75 -0
  164. package/dist/proxmox/manifest.js.map +1 -0
  165. package/dist/proxmox/probes/ceph-status.d.ts +36 -0
  166. package/dist/proxmox/probes/ceph-status.d.ts.map +1 -0
  167. package/dist/proxmox/probes/ceph-status.js +71 -0
  168. package/dist/proxmox/probes/ceph-status.js.map +1 -0
  169. package/dist/proxmox/probes/ceph-status.test.d.ts +2 -0
  170. package/dist/proxmox/probes/ceph-status.test.d.ts.map +1 -0
  171. package/dist/proxmox/probes/ceph-status.test.js +115 -0
  172. package/dist/proxmox/probes/ceph-status.test.js.map +1 -0
  173. package/dist/proxmox/probes/cluster-config.d.ts +31 -0
  174. package/dist/proxmox/probes/cluster-config.d.ts.map +1 -0
  175. package/dist/proxmox/probes/cluster-config.js +72 -0
  176. package/dist/proxmox/probes/cluster-config.js.map +1 -0
  177. package/dist/proxmox/probes/cluster-config.test.d.ts +2 -0
  178. package/dist/proxmox/probes/cluster-config.test.d.ts.map +1 -0
  179. package/dist/proxmox/probes/cluster-config.test.js +107 -0
  180. package/dist/proxmox/probes/cluster-config.test.js.map +1 -0
  181. package/dist/proxmox/probes/ha-status.d.ts +18 -0
  182. package/dist/proxmox/probes/ha-status.d.ts.map +1 -0
  183. package/dist/proxmox/probes/ha-status.js +38 -0
  184. package/dist/proxmox/probes/ha-status.js.map +1 -0
  185. package/dist/proxmox/probes/ha-status.test.d.ts +2 -0
  186. package/dist/proxmox/probes/ha-status.test.d.ts.map +1 -0
  187. package/dist/proxmox/probes/ha-status.test.js +66 -0
  188. package/dist/proxmox/probes/ha-status.test.js.map +1 -0
  189. package/dist/proxmox/probes/lvm.d.ts +35 -0
  190. package/dist/proxmox/probes/lvm.d.ts.map +1 -0
  191. package/dist/proxmox/probes/lvm.js +75 -0
  192. package/dist/proxmox/probes/lvm.js.map +1 -0
  193. package/dist/proxmox/probes/lvm.test.d.ts +2 -0
  194. package/dist/proxmox/probes/lvm.test.d.ts.map +1 -0
  195. package/dist/proxmox/probes/lvm.test.js +128 -0
  196. package/dist/proxmox/probes/lvm.test.js.map +1 -0
  197. package/dist/proxmox/probes/lxc-config.d.ts +29 -0
  198. package/dist/proxmox/probes/lxc-config.d.ts.map +1 -0
  199. package/dist/proxmox/probes/lxc-config.js +67 -0
  200. package/dist/proxmox/probes/lxc-config.js.map +1 -0
  201. package/dist/proxmox/probes/lxc-config.test.d.ts +2 -0
  202. package/dist/proxmox/probes/lxc-config.test.d.ts.map +1 -0
  203. package/dist/proxmox/probes/lxc-config.test.js +77 -0
  204. package/dist/proxmox/probes/lxc-config.test.js.map +1 -0
  205. package/dist/proxmox/probes/lxc-list.d.ts +20 -0
  206. package/dist/proxmox/probes/lxc-list.d.ts.map +1 -0
  207. package/dist/proxmox/probes/lxc-list.js +49 -0
  208. package/dist/proxmox/probes/lxc-list.js.map +1 -0
  209. package/dist/proxmox/probes/lxc-list.test.d.ts +2 -0
  210. package/dist/proxmox/probes/lxc-list.test.d.ts.map +1 -0
  211. package/dist/proxmox/probes/lxc-list.test.js +51 -0
  212. package/dist/proxmox/probes/lxc-list.test.js.map +1 -0
  213. package/dist/proxmox/probes/vm-config.d.ts +21 -0
  214. package/dist/proxmox/probes/vm-config.d.ts.map +1 -0
  215. package/dist/proxmox/probes/vm-config.js +58 -0
  216. package/dist/proxmox/probes/vm-config.js.map +1 -0
  217. package/dist/proxmox/probes/vm-config.test.d.ts +2 -0
  218. package/dist/proxmox/probes/vm-config.test.d.ts.map +1 -0
  219. package/dist/proxmox/probes/vm-config.test.js +80 -0
  220. package/dist/proxmox/probes/vm-config.test.js.map +1 -0
  221. package/dist/proxmox/probes/vm-locks.d.ts +16 -0
  222. package/dist/proxmox/probes/vm-locks.d.ts.map +1 -0
  223. package/dist/proxmox/probes/vm-locks.js +35 -0
  224. package/dist/proxmox/probes/vm-locks.js.map +1 -0
  225. package/dist/proxmox/probes/vm-locks.test.d.ts +2 -0
  226. package/dist/proxmox/probes/vm-locks.test.d.ts.map +1 -0
  227. package/dist/proxmox/probes/vm-locks.test.js +54 -0
  228. package/dist/proxmox/probes/vm-locks.test.js.map +1 -0
  229. package/dist/redis/index.d.ts +3 -0
  230. package/dist/redis/index.d.ts.map +1 -0
  231. package/dist/redis/index.js +13 -0
  232. package/dist/redis/index.js.map +1 -0
  233. package/dist/redis/manifest.d.ts +3 -0
  234. package/dist/redis/manifest.d.ts.map +1 -0
  235. package/dist/redis/manifest.js +51 -0
  236. package/dist/redis/manifest.js.map +1 -0
  237. package/dist/redis/probes/info.d.ts +15 -0
  238. package/dist/redis/probes/info.d.ts.map +1 -0
  239. package/dist/redis/probes/info.js +32 -0
  240. package/dist/redis/probes/info.js.map +1 -0
  241. package/dist/redis/probes/info.test.d.ts +2 -0
  242. package/dist/redis/probes/info.test.d.ts.map +1 -0
  243. package/dist/redis/probes/info.test.js +64 -0
  244. package/dist/redis/probes/info.test.js.map +1 -0
  245. package/dist/redis/probes/keys-count.d.ts +13 -0
  246. package/dist/redis/probes/keys-count.d.ts.map +1 -0
  247. package/dist/redis/probes/keys-count.js +24 -0
  248. package/dist/redis/probes/keys-count.js.map +1 -0
  249. package/dist/redis/probes/keys-count.test.d.ts +2 -0
  250. package/dist/redis/probes/keys-count.test.d.ts.map +1 -0
  251. package/dist/redis/probes/keys-count.test.js +37 -0
  252. package/dist/redis/probes/keys-count.test.js.map +1 -0
  253. package/dist/redis/probes/memory-usage.d.ts +16 -0
  254. package/dist/redis/probes/memory-usage.d.ts.map +1 -0
  255. package/dist/redis/probes/memory-usage.js +31 -0
  256. package/dist/redis/probes/memory-usage.js.map +1 -0
  257. package/dist/redis/probes/memory-usage.test.d.ts +2 -0
  258. package/dist/redis/probes/memory-usage.test.d.ts.map +1 -0
  259. package/dist/redis/probes/memory-usage.test.js +48 -0
  260. package/dist/redis/probes/memory-usage.test.js.map +1 -0
  261. package/dist/runbooks/nutanix.d.ts +3 -0
  262. package/dist/runbooks/nutanix.d.ts.map +1 -0
  263. package/dist/runbooks/nutanix.js +619 -0
  264. package/dist/runbooks/nutanix.js.map +1 -0
  265. package/dist/runbooks/nutanix.test.d.ts +2 -0
  266. package/dist/runbooks/nutanix.test.d.ts.map +1 -0
  267. package/dist/runbooks/nutanix.test.js +971 -0
  268. package/dist/runbooks/nutanix.test.js.map +1 -0
  269. package/dist/runbooks/proxmox.d.ts +3 -0
  270. package/dist/runbooks/proxmox.d.ts.map +1 -0
  271. package/dist/runbooks/proxmox.js +451 -0
  272. package/dist/runbooks/proxmox.js.map +1 -0
  273. package/dist/runbooks/proxmox.test.d.ts +2 -0
  274. package/dist/runbooks/proxmox.test.d.ts.map +1 -0
  275. package/dist/runbooks/proxmox.test.js +700 -0
  276. package/dist/runbooks/proxmox.test.js.map +1 -0
  277. package/dist/signatures.d.ts +2 -0
  278. package/dist/signatures.d.ts.map +1 -0
  279. package/dist/signatures.js +2 -0
  280. package/dist/signatures.js.map +1 -0
  281. package/dist/types.d.ts +53 -0
  282. package/dist/types.d.ts.map +1 -1
  283. package/dist/validation.d.ts +6 -1
  284. package/dist/validation.d.ts.map +1 -1
  285. package/dist/validation.js +10 -1
  286. package/dist/validation.js.map +1 -1
  287. package/package.json +1 -1
  288. package/src/index.ts +60 -6
  289. package/src/integrations/citrix.test.ts +592 -0
  290. package/src/integrations/citrix.ts +557 -0
  291. package/src/integrations/graph.test.ts +478 -0
  292. package/src/integrations/graph.ts +413 -0
  293. package/src/integrations/httpbin.ts +72 -0
  294. package/src/integrations/nutanix.test.ts +1508 -0
  295. package/src/integrations/nutanix.ts +1460 -0
  296. package/src/integrations/proxmox.test.ts +1020 -0
  297. package/src/integrations/proxmox.ts +989 -0
  298. package/src/integrations/servicenow.test.ts +314 -0
  299. package/src/integrations/servicenow.ts +285 -0
  300. package/src/integrations/splunk.test.ts +440 -0
  301. package/src/integrations/splunk.ts +356 -0
  302. package/src/mysql/index.ts +14 -0
  303. package/src/mysql/manifest.ts +70 -0
  304. package/src/mysql/probes/databases-list.test.ts +62 -0
  305. package/src/mysql/probes/databases-list.ts +45 -0
  306. package/src/mysql/probes/processlist.test.ts +47 -0
  307. package/src/mysql/probes/processlist.ts +55 -0
  308. package/src/mysql/probes/status.test.ts +50 -0
  309. package/src/mysql/probes/status.ts +56 -0
  310. package/src/nginx/index.ts +14 -0
  311. package/src/nginx/manifest.ts +69 -0
  312. package/src/nginx/probes/access-log-tail.test.ts +51 -0
  313. package/src/nginx/probes/access-log-tail.ts +23 -0
  314. package/src/nginx/probes/config-test.test.ts +47 -0
  315. package/src/nginx/probes/config-test.ts +24 -0
  316. package/src/nginx/probes/error-log-tail.test.ts +44 -0
  317. package/src/nginx/probes/error-log-tail.ts +23 -0
  318. package/src/postgres/index.ts +14 -0
  319. package/src/postgres/manifest.ts +91 -0
  320. package/src/postgres/probes/connections-active.test.ts +42 -0
  321. package/src/postgres/probes/connections-active.ts +55 -0
  322. package/src/postgres/probes/databases-list.test.ts +57 -0
  323. package/src/postgres/probes/databases-list.ts +49 -0
  324. package/src/postgres/probes/query-slow.test.ts +37 -0
  325. package/src/postgres/probes/query-slow.ts +55 -0
  326. package/src/proxmox/index.ts +24 -0
  327. package/src/proxmox/manifest.ts +76 -0
  328. package/src/proxmox/probes/ceph-status.test.ts +126 -0
  329. package/src/proxmox/probes/ceph-status.ts +116 -0
  330. package/src/proxmox/probes/cluster-config.test.ts +118 -0
  331. package/src/proxmox/probes/cluster-config.ts +97 -0
  332. package/src/proxmox/probes/ha-status.test.ts +76 -0
  333. package/src/proxmox/probes/ha-status.ts +56 -0
  334. package/src/proxmox/probes/lvm.test.ts +140 -0
  335. package/src/proxmox/probes/lvm.ts +121 -0
  336. package/src/proxmox/probes/lxc-config.test.ts +89 -0
  337. package/src/proxmox/probes/lxc-config.ts +90 -0
  338. package/src/proxmox/probes/lxc-list.test.ts +60 -0
  339. package/src/proxmox/probes/lxc-list.ts +67 -0
  340. package/src/proxmox/probes/vm-config.test.ts +93 -0
  341. package/src/proxmox/probes/vm-config.ts +77 -0
  342. package/src/proxmox/probes/vm-locks.test.ts +63 -0
  343. package/src/proxmox/probes/vm-locks.ts +49 -0
  344. package/src/redis/index.ts +14 -0
  345. package/src/redis/manifest.ts +52 -0
  346. package/src/redis/probes/info.test.ts +73 -0
  347. package/src/redis/probes/info.ts +46 -0
  348. package/src/redis/probes/keys-count.test.ts +44 -0
  349. package/src/redis/probes/keys-count.ts +38 -0
  350. package/src/redis/probes/memory-usage.test.ts +54 -0
  351. package/src/redis/probes/memory-usage.ts +46 -0
  352. package/src/runbooks/nutanix.test.ts +1138 -0
  353. package/src/runbooks/nutanix.ts +941 -0
  354. package/src/runbooks/proxmox.test.ts +838 -0
  355. package/src/runbooks/proxmox.ts +626 -0
  356. package/src/signatures.ts +1 -0
  357. package/src/types.ts +62 -0
  358. package/src/validation.ts +21 -1
  359. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,697 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { buildAuthHeaders, proxmoxGet, proxmoxPack, resolveNode } from './proxmox.js';
3
+ const pveConfig = {
4
+ endpoint: 'https://pve01.local:8006',
5
+ };
6
+ const pveCreds = {
7
+ packName: 'proxmox',
8
+ authMethod: 'api_key',
9
+ credentials: { tokenId: 'sonde@pve!sonde-token', tokenSecret: 'secret-uuid' },
10
+ };
11
+ const handler = (name) => {
12
+ const h = proxmoxPack.handlers[name];
13
+ if (!h)
14
+ throw new Error(`Handler ${name} not found`);
15
+ return h;
16
+ };
17
+ function callArgs(fn, index) {
18
+ const args = fn.mock.calls[index];
19
+ if (!args)
20
+ throw new Error(`No call at index ${index}`);
21
+ return args;
22
+ }
23
+ function mockPveResponse(body, status = 200) {
24
+ return vi.fn().mockResolvedValue(new Response(JSON.stringify(body), {
25
+ status,
26
+ headers: { 'Content-Type': 'application/json' },
27
+ }));
28
+ }
29
+ function mockFetchError(status) {
30
+ return vi.fn().mockResolvedValue(new Response('Error', { status, statusText: 'Error' }));
31
+ }
32
+ describe('proxmox pack', () => {
33
+ describe('auth headers', () => {
34
+ it('builds correct PVEAPIToken header', () => {
35
+ const headers = buildAuthHeaders(pveCreds);
36
+ expect(headers.Authorization).toBe('PVEAPIToken=sonde@pve!sonde-token=secret-uuid');
37
+ });
38
+ it('handles missing credentials gracefully', () => {
39
+ const emptyCreds = {
40
+ packName: 'proxmox',
41
+ authMethod: 'api_key',
42
+ credentials: {},
43
+ };
44
+ const headers = buildAuthHeaders(emptyCreds);
45
+ expect(headers.Authorization).toBe('PVEAPIToken==');
46
+ });
47
+ });
48
+ describe('proxmoxGet', () => {
49
+ it('constructs correct URL with /api2/json prefix', async () => {
50
+ const fetchFn = mockPveResponse({ data: {} });
51
+ await proxmoxGet('/cluster/status', pveConfig, pveCreds, fetchFn);
52
+ const [url] = callArgs(fetchFn, 0);
53
+ expect(url).toContain('https://pve01.local:8006/api2/json/cluster/status');
54
+ });
55
+ it('includes query params', async () => {
56
+ const fetchFn = mockPveResponse({ data: [] });
57
+ await proxmoxGet('/cluster/resources', pveConfig, pveCreds, fetchFn, { type: 'vm' });
58
+ const [url] = callArgs(fetchFn, 0);
59
+ expect(url).toContain('type=vm');
60
+ });
61
+ it('throws on non-200 response', async () => {
62
+ const fetchFn = mockFetchError(401);
63
+ await expect(proxmoxGet('/version', pveConfig, pveCreds, fetchFn)).rejects.toThrow('Proxmox API returned 401');
64
+ });
65
+ });
66
+ describe('testConnection', () => {
67
+ it('returns true on GET /version success', async () => {
68
+ const fetchFn = mockPveResponse({ data: { version: '8.1', release: '1', repoid: 'abc' } });
69
+ const result = await proxmoxPack.testConnection(pveConfig, pveCreds, fetchFn);
70
+ expect(result).toBe(true);
71
+ const [url] = callArgs(fetchFn, 0);
72
+ expect(url).toContain('/api2/json/version');
73
+ });
74
+ it('returns false on non-200', async () => {
75
+ const fetchFn = mockFetchError(401);
76
+ const result = await proxmoxPack.testConnection(pveConfig, pveCreds, fetchFn);
77
+ expect(result).toBe(false);
78
+ });
79
+ it('returns false on network error', async () => {
80
+ const fetchFn = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
81
+ const result = await proxmoxPack.testConnection(pveConfig, pveCreds, fetchFn);
82
+ expect(result).toBe(false);
83
+ });
84
+ });
85
+ describe('resolveNode', () => {
86
+ it('finds a qemu VM', async () => {
87
+ const fetchFn = mockPveResponse({
88
+ data: [
89
+ { vmid: 100, node: 'pve01', type: 'qemu' },
90
+ { vmid: 200, node: 'pve02', type: 'lxc' },
91
+ ],
92
+ });
93
+ const result = await resolveNode(100, pveConfig, pveCreds, fetchFn);
94
+ expect(result).toEqual({ node: 'pve01', type: 'qemu' });
95
+ });
96
+ it('finds an LXC container', async () => {
97
+ const fetchFn = mockPveResponse({
98
+ data: [
99
+ { vmid: 100, node: 'pve01', type: 'qemu' },
100
+ { vmid: 200, node: 'pve02', type: 'lxc' },
101
+ ],
102
+ });
103
+ const result = await resolveNode(200, pveConfig, pveCreds, fetchFn);
104
+ expect(result).toEqual({ node: 'pve02', type: 'lxc' });
105
+ });
106
+ it('throws when VMID not found', async () => {
107
+ const fetchFn = mockPveResponse({ data: [] });
108
+ await expect(resolveNode(999, pveConfig, pveCreds, fetchFn)).rejects.toThrow('VM/container 999 not found in cluster');
109
+ });
110
+ });
111
+ describe('cluster.status', () => {
112
+ it('returns cluster info with quorum', async () => {
113
+ const fetchFn = mockPveResponse({
114
+ data: [
115
+ { name: 'mycluster', type: 'cluster', quorate: 1 },
116
+ { name: 'pve01', type: 'node', online: 1, ip: '10.0.0.1' },
117
+ { name: 'pve02', type: 'node', online: 1, ip: '10.0.0.2' },
118
+ ],
119
+ });
120
+ const result = (await handler('cluster.status')({}, pveConfig, pveCreds, fetchFn));
121
+ expect(result.clusterName).toBe('mycluster');
122
+ expect(result.quorate).toBe(true);
123
+ expect(result.nodes).toHaveLength(2);
124
+ expect(result.warnings).toHaveLength(0);
125
+ });
126
+ it('flags offline nodes and lost quorum', async () => {
127
+ const fetchFn = mockPveResponse({
128
+ data: [
129
+ { name: 'mycluster', type: 'cluster', quorate: 0 },
130
+ { name: 'pve01', type: 'node', online: 1 },
131
+ { name: 'pve02', type: 'node', online: 0 },
132
+ ],
133
+ });
134
+ const result = (await handler('cluster.status')({}, pveConfig, pveCreds, fetchFn));
135
+ expect(result.warnings).toContain('Cluster has lost quorum');
136
+ expect(result.warnings).toContain('Node pve02 is offline');
137
+ });
138
+ });
139
+ describe('cluster.ha.status', () => {
140
+ it('returns HA status and resources', async () => {
141
+ let callCount = 0;
142
+ const fetchFn = vi.fn().mockImplementation(() => {
143
+ callCount++;
144
+ if (callCount === 1) {
145
+ return Promise.resolve(new Response(JSON.stringify({
146
+ data: [{ id: 'manager', type: 'manager', status: 'active' }],
147
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
148
+ }
149
+ return Promise.resolve(new Response(JSON.stringify({
150
+ data: [{ sid: 'vm:100', state: 'started', node: 'pve01', type: 'vm' }],
151
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
152
+ });
153
+ const result = (await handler('cluster.ha.status')({}, pveConfig, pveCreds, fetchFn));
154
+ expect(result.managerStatus).toBe('active');
155
+ expect(result.resources).toHaveLength(1);
156
+ expect(result.warnings).toHaveLength(0);
157
+ });
158
+ it('flags error/fence states', async () => {
159
+ let callCount = 0;
160
+ const fetchFn = vi.fn().mockImplementation(() => {
161
+ callCount++;
162
+ if (callCount === 1) {
163
+ return Promise.resolve(new Response(JSON.stringify({ data: [{ id: 'manager', type: 'manager', status: 'active' }] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
164
+ }
165
+ return Promise.resolve(new Response(JSON.stringify({
166
+ data: [
167
+ { sid: 'vm:100', state: 'error', node: 'pve01', type: 'vm' },
168
+ { sid: 'vm:101', state: 'fence', node: 'pve02', type: 'vm' },
169
+ ],
170
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
171
+ });
172
+ const result = (await handler('cluster.ha.status')({}, pveConfig, pveCreds, fetchFn));
173
+ expect(result.warnings).toContain('HA resource vm:100 in error state');
174
+ expect(result.warnings).toContain('HA resource vm:101 in fence state');
175
+ });
176
+ });
177
+ describe('nodes.list', () => {
178
+ it('returns node data', async () => {
179
+ const fetchFn = mockPveResponse({
180
+ data: [
181
+ {
182
+ node: 'pve01',
183
+ status: 'online',
184
+ uptime: 86400,
185
+ cpu: 0.25,
186
+ maxcpu: 8,
187
+ mem: 4e9,
188
+ maxmem: 16e9,
189
+ },
190
+ {
191
+ node: 'pve02',
192
+ status: 'online',
193
+ uptime: 172800,
194
+ cpu: 0.5,
195
+ maxcpu: 8,
196
+ mem: 8e9,
197
+ maxmem: 16e9,
198
+ },
199
+ ],
200
+ });
201
+ const result = (await handler('nodes.list')({}, pveConfig, pveCreds, fetchFn));
202
+ expect(result.nodes).toHaveLength(2);
203
+ expect(result.warnings).toHaveLength(0);
204
+ });
205
+ it('flags offline nodes and high resource usage', async () => {
206
+ const fetchFn = mockPveResponse({
207
+ data: [
208
+ { node: 'pve01', status: 'offline', cpu: 0.1, mem: 1e9, maxmem: 16e9 },
209
+ { node: 'pve02', status: 'online', cpu: 0.95, mem: 15e9, maxmem: 16e9 },
210
+ ],
211
+ });
212
+ const result = (await handler('nodes.list')({}, pveConfig, pveCreds, fetchFn));
213
+ expect(result.warnings).toContain('Node pve01 is offline');
214
+ expect(result.warnings.some((w) => w.includes('pve02 CPU'))).toBe(true);
215
+ expect(result.warnings.some((w) => w.includes('pve02 memory'))).toBe(true);
216
+ });
217
+ });
218
+ describe('node.storage', () => {
219
+ it('returns storage pools', async () => {
220
+ const fetchFn = mockPveResponse({
221
+ data: [
222
+ {
223
+ storage: 'local',
224
+ type: 'dir',
225
+ total: 100e9,
226
+ used: 40e9,
227
+ avail: 60e9,
228
+ enabled: 1,
229
+ active: 1,
230
+ },
231
+ {
232
+ storage: 'ceph-pool',
233
+ type: 'rbd',
234
+ total: 1e12,
235
+ used: 200e9,
236
+ avail: 800e9,
237
+ shared: 1,
238
+ enabled: 1,
239
+ active: 1,
240
+ },
241
+ ],
242
+ });
243
+ const result = (await handler('node.storage')({ node: 'pve01' }, pveConfig, pveCreds, fetchFn));
244
+ expect(result.storages).toHaveLength(2);
245
+ expect(result.storages[1]?.shared).toBe(true);
246
+ expect(result.warnings).toHaveLength(0);
247
+ });
248
+ it('requires node parameter', async () => {
249
+ const fetchFn = mockPveResponse({});
250
+ await expect(handler('node.storage')({}, pveConfig, pveCreds, fetchFn)).rejects.toThrow('node parameter is required');
251
+ });
252
+ it('flags high usage and disabled storage', async () => {
253
+ const fetchFn = mockPveResponse({
254
+ data: [
255
+ {
256
+ storage: 'local',
257
+ type: 'dir',
258
+ total: 100e9,
259
+ used: 90e9,
260
+ avail: 10e9,
261
+ enabled: 1,
262
+ active: 1,
263
+ },
264
+ {
265
+ storage: 'nfs',
266
+ type: 'nfs',
267
+ total: 100e9,
268
+ used: 10e9,
269
+ avail: 90e9,
270
+ enabled: 0,
271
+ active: 0,
272
+ },
273
+ ],
274
+ });
275
+ const result = (await handler('node.storage')({ node: 'pve01' }, pveConfig, pveCreds, fetchFn));
276
+ expect(result.warnings.some((w) => w.includes('local') && w.includes('90%'))).toBe(true);
277
+ expect(result.warnings).toContain('Storage nfs is disabled');
278
+ });
279
+ });
280
+ describe('vm.status', () => {
281
+ it('returns VM status', async () => {
282
+ const fetchFn = mockPveResponse({
283
+ data: [
284
+ {
285
+ vmid: 100,
286
+ name: 'web-01',
287
+ status: 'running',
288
+ node: 'pve01',
289
+ type: 'qemu',
290
+ uptime: 3600,
291
+ cpu: 0.1,
292
+ mem: 2e9,
293
+ maxmem: 4e9,
294
+ },
295
+ ],
296
+ });
297
+ const result = (await handler('vm.status')({ vmid: 100 }, pveConfig, pveCreds, fetchFn));
298
+ expect(result.vmid).toBe(100);
299
+ expect(result.name).toBe('web-01');
300
+ expect(result.warnings).toHaveLength(0);
301
+ });
302
+ it('throws when VM not found', async () => {
303
+ const fetchFn = mockPveResponse({ data: [] });
304
+ await expect(handler('vm.status')({ vmid: 999 }, pveConfig, pveCreds, fetchFn)).rejects.toThrow('VM/container 999 not found');
305
+ });
306
+ it('requires vmid parameter', async () => {
307
+ const fetchFn = mockPveResponse({});
308
+ await expect(handler('vm.status')({}, pveConfig, pveCreds, fetchFn)).rejects.toThrow('vmid parameter is required');
309
+ });
310
+ it('flags stopped, locked, and HA error VMs', async () => {
311
+ const fetchFn = mockPveResponse({
312
+ data: [
313
+ {
314
+ vmid: 100,
315
+ name: 'web-01',
316
+ status: 'stopped',
317
+ node: 'pve01',
318
+ type: 'qemu',
319
+ lock: 'backup',
320
+ hastate: 'error',
321
+ },
322
+ ],
323
+ });
324
+ const result = (await handler('vm.status')({ vmid: 100 }, pveConfig, pveCreds, fetchFn));
325
+ expect(result.warnings).toContain('VM is stopped');
326
+ expect(result.warnings).toContain('VM has lock: backup');
327
+ expect(result.warnings).toContain('HA state: error');
328
+ });
329
+ });
330
+ describe('vm.config', () => {
331
+ it('parses disk entries from config', async () => {
332
+ // First call: resolveNode
333
+ // Second call: config
334
+ let callCount = 0;
335
+ const fetchFn = vi.fn().mockImplementation(() => {
336
+ callCount++;
337
+ if (callCount === 1) {
338
+ return Promise.resolve(new Response(JSON.stringify({ data: [{ vmid: 100, node: 'pve01', type: 'qemu' }] }), {
339
+ status: 200,
340
+ headers: { 'Content-Type': 'application/json' },
341
+ }));
342
+ }
343
+ return Promise.resolve(new Response(JSON.stringify({
344
+ data: {
345
+ name: 'web-01',
346
+ memory: 4096,
347
+ cores: 2,
348
+ scsi0: 'local-lvm:vm-100-disk-0,size=32G',
349
+ ide2: 'none,media=cdrom',
350
+ net0: 'virtio=AA:BB:CC:DD:EE:FF,bridge=vmbr0',
351
+ },
352
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
353
+ });
354
+ const result = (await handler('vm.config')({ vmid: 100 }, pveConfig, pveCreds, fetchFn));
355
+ expect(result.vmid).toBe(100);
356
+ expect(result.node).toBe('pve01');
357
+ expect(result.disks).toHaveLength(1);
358
+ expect(result.disks[0]?.storage).toBe('local-lvm');
359
+ expect(result.disks[0]?.size).toBe('32G');
360
+ expect(result.warnings).toContain('Disk scsi0 uses local storage (local-lvm) — not shared for HA');
361
+ });
362
+ it('uses provided node without resolving', async () => {
363
+ const fetchFn = mockPveResponse({
364
+ data: { name: 'web-01', memory: 4096 },
365
+ });
366
+ const result = (await handler('vm.config')({ vmid: 100, node: 'pve01' }, pveConfig, pveCreds, fetchFn));
367
+ expect(result.node).toBe('pve01');
368
+ // Only 1 call (no resolveNode)
369
+ expect(fetchFn).toHaveBeenCalledTimes(1);
370
+ });
371
+ });
372
+ describe('vm.snapshots', () => {
373
+ it('returns snapshots and filters "current"', async () => {
374
+ const now = Math.floor(Date.now() / 1000);
375
+ const fetchFn = mockPveResponse({
376
+ data: [
377
+ { name: 'current', description: 'You are here!' },
378
+ {
379
+ name: 'pre-upgrade',
380
+ description: 'Before upgrade',
381
+ snaptime: now - 3600,
382
+ parent: 'current',
383
+ },
384
+ ],
385
+ });
386
+ const result = (await handler('vm.snapshots')({ vmid: 100, node: 'pve01' }, pveConfig, pveCreds, fetchFn));
387
+ expect(result.snapshots).toHaveLength(1);
388
+ expect(result.snapshots[0]?.name).toBe('pre-upgrade');
389
+ expect(result.warnings).toHaveLength(0);
390
+ });
391
+ it('flags old snapshots', async () => {
392
+ const eightDaysAgo = Math.floor(Date.now() / 1000) - 8 * 24 * 60 * 60;
393
+ const fetchFn = mockPveResponse({
394
+ data: [{ name: 'old-snap', description: 'Old one', snaptime: eightDaysAgo }],
395
+ });
396
+ const result = (await handler('vm.snapshots')({ vmid: 100, node: 'pve01' }, pveConfig, pveCreds, fetchFn));
397
+ expect(result.warnings.some((w) => w.includes('older than 7 days'))).toBe(true);
398
+ });
399
+ });
400
+ describe('storage.content', () => {
401
+ it('returns volumes', async () => {
402
+ const fetchFn = mockPveResponse({
403
+ data: [
404
+ { volid: 'local-lvm:vm-100-disk-0', vmid: 100, size: 32e9, format: 'raw' },
405
+ { volid: 'local-lvm:vm-101-disk-0', vmid: 101, size: 64e9, format: 'raw' },
406
+ ],
407
+ });
408
+ const result = (await handler('storage.content')({ node: 'pve01', storage: 'local-lvm' }, pveConfig, pveCreds, fetchFn));
409
+ expect(result.count).toBe(2);
410
+ });
411
+ it('filters by vmid', async () => {
412
+ const fetchFn = mockPveResponse({
413
+ data: [
414
+ { volid: 'local-lvm:vm-100-disk-0', vmid: 100, size: 32e9 },
415
+ { volid: 'local-lvm:vm-101-disk-0', vmid: 101, size: 64e9 },
416
+ ],
417
+ });
418
+ const result = (await handler('storage.content')({ node: 'pve01', storage: 'local-lvm', vmid: 100 }, pveConfig, pveCreds, fetchFn));
419
+ expect(result.count).toBe(1);
420
+ });
421
+ it('requires node and storage', async () => {
422
+ const fetchFn = mockPveResponse({});
423
+ await expect(handler('storage.content')({}, pveConfig, pveCreds, fetchFn)).rejects.toThrow('node parameter is required');
424
+ await expect(handler('storage.content')({ node: 'pve01' }, pveConfig, pveCreds, fetchFn)).rejects.toThrow('storage parameter is required');
425
+ });
426
+ });
427
+ describe('cluster.tasks', () => {
428
+ it('returns tasks', async () => {
429
+ const fetchFn = mockPveResponse({
430
+ data: [
431
+ {
432
+ upid: 'UPID:pve01:001',
433
+ type: 'qmstart',
434
+ status: 'OK',
435
+ starttime: 1000,
436
+ endtime: 1010,
437
+ node: 'pve01',
438
+ user: 'root@pam',
439
+ id: '100',
440
+ },
441
+ ],
442
+ });
443
+ const result = (await handler('cluster.tasks')({}, pveConfig, pveCreds, fetchFn));
444
+ expect(result.tasks).toHaveLength(1);
445
+ expect(result.warnings).toHaveLength(0);
446
+ });
447
+ it('filters by vmid', async () => {
448
+ const fetchFn = mockPveResponse({
449
+ data: [
450
+ { upid: 'UPID:1', type: 'qmstart', status: 'OK', node: 'pve01', id: '100', endtime: 1 },
451
+ { upid: 'UPID:2', type: 'qmstart', status: 'OK', node: 'pve01', id: '101', endtime: 2 },
452
+ ],
453
+ });
454
+ const result = (await handler('cluster.tasks')({ vmid: 100 }, pveConfig, pveCreds, fetchFn));
455
+ expect(result.tasks).toHaveLength(1);
456
+ });
457
+ it('flags failed tasks and running migrations', async () => {
458
+ const fetchFn = mockPveResponse({
459
+ data: [
460
+ {
461
+ upid: 'UPID:1',
462
+ type: 'qmstart',
463
+ status: 'WARNINGS: something',
464
+ node: 'pve01',
465
+ endtime: 1,
466
+ },
467
+ { upid: 'UPID:2', type: 'qmigrate', status: '', node: 'pve02' },
468
+ ],
469
+ });
470
+ const result = (await handler('cluster.tasks')({}, pveConfig, pveCreds, fetchFn));
471
+ expect(result.warnings.some((w) => w.includes('failed'))).toBe(true);
472
+ expect(result.warnings.some((w) => w.includes('Migration in progress'))).toBe(true);
473
+ });
474
+ });
475
+ describe('node.lvm', () => {
476
+ it('returns volume groups', async () => {
477
+ const fetchFn = mockPveResponse({
478
+ data: [{ name: 'pve', size: 500e9, free: 200e9, pvs: 1, lvs: 3 }],
479
+ });
480
+ const result = (await handler('node.lvm')({ node: 'pve01' }, pveConfig, pveCreds, fetchFn));
481
+ expect(result.volumeGroups).toHaveLength(1);
482
+ expect(result.warnings).toHaveLength(0);
483
+ });
484
+ it('flags VGs with no free space', async () => {
485
+ const fetchFn = mockPveResponse({
486
+ data: [{ name: 'pve', size: 500e9, free: 0, pvs: 1, lvs: 5 }],
487
+ });
488
+ const result = (await handler('node.lvm')({ node: 'pve01' }, pveConfig, pveCreds, fetchFn));
489
+ expect(result.warnings).toContain('Volume group pve has no free space');
490
+ });
491
+ it('requires node parameter', async () => {
492
+ const fetchFn = mockPveResponse({});
493
+ await expect(handler('node.lvm')({}, pveConfig, pveCreds, fetchFn)).rejects.toThrow('node parameter is required');
494
+ });
495
+ });
496
+ describe('lxc.status', () => {
497
+ it('returns container status', async () => {
498
+ // resolveNode call
499
+ let callCount = 0;
500
+ const fetchFn = vi.fn().mockImplementation(() => {
501
+ callCount++;
502
+ if (callCount === 1) {
503
+ return Promise.resolve(new Response(JSON.stringify({ data: [{ vmid: 200, node: 'pve02', type: 'lxc' }] }), {
504
+ status: 200,
505
+ headers: { 'Content-Type': 'application/json' },
506
+ }));
507
+ }
508
+ return Promise.resolve(new Response(JSON.stringify({
509
+ data: {
510
+ vmid: 200,
511
+ name: 'ct-nginx',
512
+ status: 'running',
513
+ uptime: 7200,
514
+ cpu: 0.05,
515
+ mem: 256e6,
516
+ maxmem: 512e6,
517
+ disk: 1e9,
518
+ maxdisk: 8e9,
519
+ swap: 0,
520
+ maxswap: 512e6,
521
+ },
522
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
523
+ });
524
+ const result = (await handler('lxc.status')({ vmid: 200 }, pveConfig, pveCreds, fetchFn));
525
+ expect(result.vmid).toBe(200);
526
+ expect(result.name).toBe('ct-nginx');
527
+ expect(result.node).toBe('pve02');
528
+ expect(result.warnings).toHaveLength(0);
529
+ });
530
+ it('rejects qemu VMID', async () => {
531
+ const fetchFn = mockPveResponse({
532
+ data: [{ vmid: 100, node: 'pve01', type: 'qemu' }],
533
+ });
534
+ await expect(handler('lxc.status')({ vmid: 100 }, pveConfig, pveCreds, fetchFn)).rejects.toThrow('not an LXC container');
535
+ });
536
+ });
537
+ describe('lxc.config', () => {
538
+ it('parses rootfs and mountpoints', async () => {
539
+ const fetchFn = mockPveResponse({
540
+ data: {
541
+ hostname: 'ct-nginx',
542
+ rootfs: 'local-lvm:subvol-200-disk-0,size=8G',
543
+ mp0: 'local-lvm:subvol-200-disk-1,mp=/data,size=20G',
544
+ cores: 2,
545
+ memory: 512,
546
+ },
547
+ });
548
+ const result = (await handler('lxc.config')({ vmid: 200, node: 'pve02' }, pveConfig, pveCreds, fetchFn));
549
+ expect(result.rootfs).toEqual({ storage: 'local-lvm', size: '8G' });
550
+ expect(result.mountpoints).toHaveLength(1);
551
+ expect(result.mountpoints[0]?.mountpoint).toBe('/data');
552
+ expect(result.mountpoints[0]?.size).toBe('20G');
553
+ });
554
+ });
555
+ describe('ceph.status', () => {
556
+ it('returns ceph health and OSDs', async () => {
557
+ let callCount = 0;
558
+ const fetchFn = vi.fn().mockImplementation(() => {
559
+ callCount++;
560
+ if (callCount === 1) {
561
+ // /cluster/ceph/status
562
+ return Promise.resolve(new Response(JSON.stringify({
563
+ data: {
564
+ health: { status: 'HEALTH_OK' },
565
+ osdmap: { osdmap: { num_osds: 6, num_up_osds: 6, num_in_osds: 6 } },
566
+ pgmap: {
567
+ pgs_by_state: [{ state_name: 'active+clean', count: 256 }],
568
+ bytes_total: 6e12,
569
+ bytes_used: 2e12,
570
+ bytes_avail: 4e12,
571
+ },
572
+ },
573
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
574
+ }
575
+ if (callCount === 2) {
576
+ // /nodes
577
+ return Promise.resolve(new Response(JSON.stringify({ data: [{ node: 'pve01', status: 'online' }] }), {
578
+ status: 200,
579
+ headers: { 'Content-Type': 'application/json' },
580
+ }));
581
+ }
582
+ // /nodes/pve01/ceph/osd
583
+ return Promise.resolve(new Response(JSON.stringify({
584
+ data: [
585
+ { id: 0, name: 'osd.0', up: 1 },
586
+ { id: 1, name: 'osd.1', up: 1 },
587
+ ],
588
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
589
+ });
590
+ const result = (await handler('ceph.status')({}, pveConfig, pveCreds, fetchFn));
591
+ expect(result.available).toBe(true);
592
+ expect(result.health).toBe('HEALTH_OK');
593
+ expect(result.osdCount).toBe(6);
594
+ expect(result.osds).toHaveLength(2);
595
+ expect(result.warnings).toHaveLength(0);
596
+ });
597
+ it('handles degraded ceph', async () => {
598
+ const fetchFn = vi.fn().mockImplementation(() => {
599
+ return Promise.resolve(new Response(JSON.stringify({
600
+ data: {
601
+ health: { status: 'HEALTH_WARN' },
602
+ osdmap: { osdmap: { num_osds: 6, num_up_osds: 4, num_in_osds: 6 } },
603
+ pgmap: { bytes_total: 6e12, bytes_used: 2e12, bytes_avail: 4e12 },
604
+ },
605
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
606
+ });
607
+ const result = (await handler('ceph.status')({}, pveConfig, pveCreds, fetchFn));
608
+ expect(result.warnings.some((w) => w.includes('HEALTH_WARN'))).toBe(true);
609
+ expect(result.warnings.some((w) => w.includes('2 OSD(s) down'))).toBe(true);
610
+ });
611
+ it('handles no ceph (404)', async () => {
612
+ const fetchFn = vi
613
+ .fn()
614
+ .mockResolvedValue(new Response('Not Found', { status: 404, statusText: 'Not Found' }));
615
+ const result = (await handler('ceph.status')({}, pveConfig, pveCreds, fetchFn));
616
+ expect(result.available).toBe(false);
617
+ expect(result.warnings).toContain('Ceph is not configured on this cluster');
618
+ });
619
+ });
620
+ describe('cluster.resources', () => {
621
+ it('returns all VMs and containers', async () => {
622
+ const fetchFn = mockPveResponse({
623
+ data: [
624
+ {
625
+ vmid: 100,
626
+ name: 'web-01',
627
+ node: 'pve01',
628
+ type: 'qemu',
629
+ status: 'running',
630
+ uptime: 3600,
631
+ cpu: 0.1,
632
+ mem: 2e9,
633
+ maxmem: 4e9,
634
+ },
635
+ {
636
+ vmid: 200,
637
+ name: 'ct-db',
638
+ node: 'pve02',
639
+ type: 'lxc',
640
+ status: 'running',
641
+ uptime: 7200,
642
+ cpu: 0.05,
643
+ mem: 512e6,
644
+ maxmem: 1e9,
645
+ },
646
+ { vmid: 101, name: 'stopped-vm', node: 'pve01', type: 'qemu', status: 'stopped' },
647
+ ],
648
+ });
649
+ const result = (await handler('cluster.resources')({}, pveConfig, pveCreds, fetchFn));
650
+ expect(result.resources).toHaveLength(3);
651
+ expect(result.resources[0]?.vmid).toBe(100);
652
+ expect(result.resources[0]?.type).toBe('qemu');
653
+ expect(result.resources[1]?.type).toBe('lxc');
654
+ });
655
+ it('calls /cluster/resources?type=vm', async () => {
656
+ const fetchFn = mockPveResponse({ data: [] });
657
+ await handler('cluster.resources')({}, pveConfig, pveCreds, fetchFn);
658
+ const [url] = callArgs(fetchFn, 0);
659
+ expect(url).toContain('/cluster/resources');
660
+ expect(url).toContain('type=vm');
661
+ });
662
+ });
663
+ describe('manifest', () => {
664
+ it('has correct name and 14 probes', () => {
665
+ expect(proxmoxPack.manifest.name).toBe('proxmox');
666
+ expect(proxmoxPack.manifest.probes).toHaveLength(14);
667
+ });
668
+ it('all handlers match manifest probes', () => {
669
+ const probeNames = proxmoxPack.manifest.probes.map((p) => p.name);
670
+ const handlerNames = Object.keys(proxmoxPack.handlers);
671
+ expect(handlerNames.sort()).toEqual(probeNames.sort());
672
+ });
673
+ it('has correct timeouts (30s for ceph, 15s for others)', () => {
674
+ const probeMap = new Map(proxmoxPack.manifest.probes.map((p) => [p.name, p.timeout]));
675
+ expect(probeMap.get('ceph.status')).toBe(30000);
676
+ for (const [name, timeout] of probeMap) {
677
+ if (name !== 'ceph.status') {
678
+ expect(timeout).toBe(15000);
679
+ }
680
+ }
681
+ });
682
+ it('has virtualization runbook', () => {
683
+ expect(proxmoxPack.manifest.runbook).toEqual({
684
+ category: 'virtualization',
685
+ probes: ['cluster.status', 'nodes.list', 'ceph.status'],
686
+ parallel: true,
687
+ });
688
+ });
689
+ });
690
+ describe('error handling', () => {
691
+ it('throws on non-200 API response for probes', async () => {
692
+ const fetchFn = mockFetchError(403);
693
+ await expect(handler('nodes.list')({}, pveConfig, pveCreds, fetchFn)).rejects.toThrow('Proxmox API returned 403');
694
+ });
695
+ });
696
+ });
697
+ //# sourceMappingURL=proxmox.test.js.map