@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,1138 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { RunProbe, RunbookContext, RunbookProbeResult } from '../types.js';
3
+ import { nutanixDiagnosticRunbooks } from './nutanix.js';
4
+
5
+ // --- Test helpers ---
6
+
7
+ function getHandler(category: string) {
8
+ const def = nutanixDiagnosticRunbooks.find((r) => r.category === category);
9
+ if (!def) throw new Error(`No runbook for category "${category}"`);
10
+ return def.handler;
11
+ }
12
+
13
+ function mockResult(
14
+ probe: string,
15
+ data: unknown,
16
+ status: 'success' | 'error' | 'timeout' = 'success',
17
+ error?: string,
18
+ ): RunbookProbeResult {
19
+ return { probe, status, data, durationMs: 10, error };
20
+ }
21
+
22
+ function createMockRunProbe(
23
+ responses: Record<
24
+ string,
25
+ { data?: unknown; status?: 'success' | 'error' | 'timeout'; error?: string }
26
+ >,
27
+ ): RunProbe {
28
+ return async (probe: string, _params?: Record<string, unknown>, _agent?: string) => {
29
+ const response = responses[probe];
30
+ if (!response) {
31
+ return mockResult(probe, undefined, 'error', `No mock for probe ${probe}`);
32
+ }
33
+ return mockResult(probe, response.data, response.status ?? 'success', response.error);
34
+ };
35
+ }
36
+
37
+ const defaultContext: RunbookContext = { connectedAgents: [] };
38
+
39
+ // --- Fixtures ---
40
+
41
+ const healthyClusters = {
42
+ clusters: [
43
+ { name: 'prod-01', extId: 'c-001', operationMode: 'NORMAL', isDegraded: false, numNodes: 4 },
44
+ { name: 'prod-02', extId: 'c-002', operationMode: 'NORMAL', isDegraded: false, numNodes: 3 },
45
+ ],
46
+ totalCount: 2,
47
+ };
48
+
49
+ const healthyHosts = {
50
+ hosts: [
51
+ { name: 'host-01', extId: 'h-001', maintenanceMode: false },
52
+ { name: 'host-02', extId: 'h-002', maintenanceMode: false },
53
+ { name: 'host-03', extId: 'h-003', maintenanceMode: false },
54
+ ],
55
+ totalCount: 3,
56
+ };
57
+
58
+ const healthyAlertsSummary = {
59
+ bySeverity: { CRITICAL: 0, WARNING: 2, INFO: 5 },
60
+ byEntityType: { vm: 5, host: 2 },
61
+ unresolvedCritical: [],
62
+ totalCount: 7,
63
+ };
64
+
65
+ const healthyStorage = {
66
+ containers: [
67
+ {
68
+ name: 'default-container',
69
+ usedPct: 45,
70
+ highUsage: false,
71
+ maxCapacityBytes: 1e12,
72
+ usedBytes: 4.5e11,
73
+ availableBytes: 5.5e11,
74
+ },
75
+ {
76
+ name: 'ssd-tier',
77
+ usedPct: 60,
78
+ highUsage: false,
79
+ maxCapacityBytes: 5e11,
80
+ usedBytes: 3e11,
81
+ availableBytes: 2e11,
82
+ },
83
+ ],
84
+ totalCount: 2,
85
+ };
86
+
87
+ const healthyTasks = {
88
+ tasks: [
89
+ {
90
+ type: 'VmCreate',
91
+ status: 'SUCCEEDED',
92
+ isFailed: false,
93
+ isLongRunning: false,
94
+ startTime: '2026-02-17T10:00:00Z',
95
+ },
96
+ ],
97
+ totalCount: 1,
98
+ };
99
+
100
+ const healthyLifecycle = {
101
+ entities: [],
102
+ updatableCount: 0,
103
+ totalCount: 0,
104
+ warnings: [],
105
+ };
106
+
107
+ const healthyVmDetail = {
108
+ name: 'web-01',
109
+ extId: 'vm-001',
110
+ powerState: 'ON',
111
+ numSockets: 4,
112
+ numCoresPerSocket: 2,
113
+ memorySizeMb: 8192,
114
+ clusterExtId: 'c-001',
115
+ hostExtId: 'h-001',
116
+ guestTools: { isEnabled: true },
117
+ categories: null,
118
+ };
119
+
120
+ const healthyVmStats = {
121
+ cpuUsagePct: 35,
122
+ memoryUsagePct: 55,
123
+ iops: 200,
124
+ avgIoLatencyMs: 5,
125
+ ioBandwidthKbps: 50000,
126
+ networkRxBytes: 1000000,
127
+ networkTxBytes: 500000,
128
+ };
129
+
130
+ const healthySnapshots = {
131
+ snapshots: [{ name: 'daily-snap', ageDays: 1, isOld: false, isExpired: false }],
132
+ totalCount: 1,
133
+ usedV3: false,
134
+ warnings: [],
135
+ };
136
+
137
+ const healthyProtection = {
138
+ policies: [{ name: 'gold-policy', extId: 'pp-001' }],
139
+ totalCount: 1,
140
+ vmCovered: true,
141
+ allPoliciesCount: 3,
142
+ };
143
+
144
+ const emptyAlerts = { alerts: [], totalCount: 0 };
145
+
146
+ // =============================================================================
147
+ // nutanix-cluster-health tests
148
+ // =============================================================================
149
+
150
+ describe('nutanix-cluster-health', () => {
151
+ const handler = getHandler('nutanix-cluster-health');
152
+
153
+ it('healthy environment — no issues', async () => {
154
+ const runProbe = createMockRunProbe({
155
+ 'nutanix.clusters.list': { data: healthyClusters },
156
+ 'nutanix.alerts.summary': { data: healthyAlertsSummary },
157
+ 'nutanix.storage.containers': { data: healthyStorage },
158
+ 'nutanix.tasks.recent': { data: healthyTasks },
159
+ 'nutanix.lifecycle.status': { data: healthyLifecycle },
160
+ 'nutanix.hosts.list': { data: healthyHosts },
161
+ });
162
+
163
+ const result = await handler({}, runProbe, defaultContext);
164
+
165
+ expect(result.category).toBe('nutanix-cluster-health');
166
+ expect(result.findings).toHaveLength(1);
167
+ expect(result.findings[0]?.severity).toBe('info');
168
+ expect(result.findings[0]?.title).toContain('healthy');
169
+ expect(result.summary.probesRun).toBe(6);
170
+ });
171
+
172
+ it('degraded cluster — critical finding', async () => {
173
+ const degraded = {
174
+ clusters: [
175
+ {
176
+ name: 'prod-01',
177
+ extId: 'c-001',
178
+ operationMode: 'READ_ONLY',
179
+ isDegraded: true,
180
+ numNodes: 4,
181
+ },
182
+ ],
183
+ totalCount: 1,
184
+ };
185
+
186
+ const runProbe = createMockRunProbe({
187
+ 'nutanix.clusters.list': { data: degraded },
188
+ 'nutanix.alerts.summary': { data: healthyAlertsSummary },
189
+ 'nutanix.storage.containers': { data: healthyStorage },
190
+ 'nutanix.tasks.recent': { data: healthyTasks },
191
+ 'nutanix.lifecycle.status': { data: healthyLifecycle },
192
+ 'nutanix.hosts.list': { data: healthyHosts },
193
+ });
194
+
195
+ const result = await handler({}, runProbe, defaultContext);
196
+
197
+ const critical = result.findings.find((f) => f.title.includes('degraded'));
198
+ expect(critical).toBeDefined();
199
+ expect(critical?.severity).toBe('critical');
200
+ expect(critical?.detail).toContain('READ_ONLY');
201
+ });
202
+
203
+ it('hosts in maintenance — warning', async () => {
204
+ const maintenanceHosts = {
205
+ hosts: [
206
+ { name: 'host-01', extId: 'h-001', maintenanceMode: true },
207
+ { name: 'host-02', extId: 'h-002', maintenanceMode: false },
208
+ ],
209
+ totalCount: 2,
210
+ };
211
+
212
+ const runProbe = createMockRunProbe({
213
+ 'nutanix.clusters.list': { data: healthyClusters },
214
+ 'nutanix.alerts.summary': { data: healthyAlertsSummary },
215
+ 'nutanix.storage.containers': { data: healthyStorage },
216
+ 'nutanix.tasks.recent': { data: healthyTasks },
217
+ 'nutanix.lifecycle.status': { data: healthyLifecycle },
218
+ 'nutanix.hosts.list': { data: maintenanceHosts },
219
+ });
220
+
221
+ const result = await handler({}, runProbe, defaultContext);
222
+
223
+ const warning = result.findings.find((f) => f.title.includes('maintenance'));
224
+ expect(warning).toBeDefined();
225
+ expect(warning?.severity).toBe('warning');
226
+ expect(warning?.detail).toContain('host-01');
227
+ });
228
+
229
+ it('unresolved critical alerts — critical findings', async () => {
230
+ const criticalAlerts = {
231
+ bySeverity: { CRITICAL: 2, WARNING: 0, INFO: 0 },
232
+ byEntityType: { vm: 2 },
233
+ unresolvedCritical: [
234
+ {
235
+ title: 'Disk failure on host-01',
236
+ sourceEntity: { type: 'host', name: 'host-01' },
237
+ creationTime: '2026-02-17T08:00:00Z',
238
+ },
239
+ {
240
+ title: 'Memory threshold exceeded',
241
+ sourceEntity: { type: 'vm', name: 'web-01' },
242
+ creationTime: '2026-02-17T09:00:00Z',
243
+ },
244
+ ],
245
+ totalCount: 2,
246
+ };
247
+
248
+ const runProbe = createMockRunProbe({
249
+ 'nutanix.clusters.list': { data: healthyClusters },
250
+ 'nutanix.alerts.summary': { data: criticalAlerts },
251
+ 'nutanix.storage.containers': { data: healthyStorage },
252
+ 'nutanix.tasks.recent': { data: healthyTasks },
253
+ 'nutanix.lifecycle.status': { data: healthyLifecycle },
254
+ 'nutanix.hosts.list': { data: healthyHosts },
255
+ });
256
+
257
+ const result = await handler({}, runProbe, defaultContext);
258
+
259
+ const criticals = result.findings.filter((f) => f.severity === 'critical');
260
+ expect(criticals.length).toBe(2);
261
+ expect(criticals[0]?.title).toContain('Disk failure');
262
+ });
263
+
264
+ it('storage >85% — warning', async () => {
265
+ const fullStorage = {
266
+ containers: [
267
+ {
268
+ name: 'full-container',
269
+ usedPct: 92,
270
+ highUsage: true,
271
+ maxCapacityBytes: 1e12,
272
+ usedBytes: 9.2e11,
273
+ availableBytes: 8e10,
274
+ },
275
+ ],
276
+ totalCount: 1,
277
+ };
278
+
279
+ const runProbe = createMockRunProbe({
280
+ 'nutanix.clusters.list': { data: healthyClusters },
281
+ 'nutanix.alerts.summary': { data: healthyAlertsSummary },
282
+ 'nutanix.storage.containers': { data: fullStorage },
283
+ 'nutanix.tasks.recent': { data: healthyTasks },
284
+ 'nutanix.lifecycle.status': { data: healthyLifecycle },
285
+ 'nutanix.hosts.list': { data: healthyHosts },
286
+ });
287
+
288
+ const result = await handler({}, runProbe, defaultContext);
289
+
290
+ const warning = result.findings.find((f) => f.title.includes('full-container'));
291
+ expect(warning).toBeDefined();
292
+ expect(warning?.severity).toBe('warning');
293
+ expect(warning?.title).toContain('92%');
294
+ });
295
+
296
+ it('failed tasks — warning', async () => {
297
+ const failedTasks = {
298
+ tasks: [
299
+ {
300
+ type: 'VmMigrate',
301
+ status: 'FAILED',
302
+ isFailed: true,
303
+ isLongRunning: false,
304
+ errorMessage: 'Insufficient resources',
305
+ startTime: '2026-02-17T10:00:00Z',
306
+ },
307
+ ],
308
+ totalCount: 1,
309
+ };
310
+
311
+ const runProbe = createMockRunProbe({
312
+ 'nutanix.clusters.list': { data: healthyClusters },
313
+ 'nutanix.alerts.summary': { data: healthyAlertsSummary },
314
+ 'nutanix.storage.containers': { data: healthyStorage },
315
+ 'nutanix.tasks.recent': { data: failedTasks },
316
+ 'nutanix.lifecycle.status': { data: healthyLifecycle },
317
+ 'nutanix.hosts.list': { data: healthyHosts },
318
+ });
319
+
320
+ const result = await handler({}, runProbe, defaultContext);
321
+
322
+ const warning = result.findings.find((f) => f.title.includes('failed task'));
323
+ expect(warning).toBeDefined();
324
+ expect(warning?.severity).toBe('warning');
325
+ });
326
+
327
+ it('long-running tasks — warning', async () => {
328
+ const longRunningTasks = {
329
+ tasks: [
330
+ {
331
+ type: 'ClusterUpgrade',
332
+ status: 'RUNNING',
333
+ isFailed: false,
334
+ isLongRunning: true,
335
+ startTime: '2026-02-17T06:00:00Z',
336
+ },
337
+ ],
338
+ totalCount: 1,
339
+ };
340
+
341
+ const runProbe = createMockRunProbe({
342
+ 'nutanix.clusters.list': { data: healthyClusters },
343
+ 'nutanix.alerts.summary': { data: healthyAlertsSummary },
344
+ 'nutanix.storage.containers': { data: healthyStorage },
345
+ 'nutanix.tasks.recent': { data: longRunningTasks },
346
+ 'nutanix.lifecycle.status': { data: healthyLifecycle },
347
+ 'nutanix.hosts.list': { data: healthyHosts },
348
+ });
349
+
350
+ const result = await handler({}, runProbe, defaultContext);
351
+
352
+ const warning = result.findings.find((f) => f.title.includes('long-running'));
353
+ expect(warning).toBeDefined();
354
+ expect(warning?.severity).toBe('warning');
355
+ });
356
+
357
+ it('LCM updates available — info finding', async () => {
358
+ const updatesAvailable = {
359
+ entities: [
360
+ { entityType: 'AOS', currentVersion: '6.5.1', availableVersion: '6.6.0', hasUpdate: true },
361
+ ],
362
+ updatableCount: 1,
363
+ totalCount: 1,
364
+ warnings: ['1 component(s) have available updates', 'AOS: 6.5.1 \u2192 6.6.0'],
365
+ };
366
+
367
+ const runProbe = createMockRunProbe({
368
+ 'nutanix.clusters.list': { data: healthyClusters },
369
+ 'nutanix.alerts.summary': { data: healthyAlertsSummary },
370
+ 'nutanix.storage.containers': { data: healthyStorage },
371
+ 'nutanix.tasks.recent': { data: healthyTasks },
372
+ 'nutanix.lifecycle.status': { data: updatesAvailable },
373
+ 'nutanix.hosts.list': { data: healthyHosts },
374
+ });
375
+
376
+ const result = await handler({}, runProbe, defaultContext);
377
+
378
+ const info = result.findings.find((f) => f.title.includes('update'));
379
+ expect(info).toBeDefined();
380
+ expect(info?.severity).toBe('info');
381
+ });
382
+ });
383
+
384
+ // =============================================================================
385
+ // nutanix-vm-health tests
386
+ // =============================================================================
387
+
388
+ describe('nutanix-vm-health', () => {
389
+ const handler = getHandler('nutanix-vm-health');
390
+
391
+ it('healthy VM by ID — all probes good', async () => {
392
+ const runProbe = createMockRunProbe({
393
+ 'nutanix.vm.detail': { data: healthyVmDetail },
394
+ 'nutanix.vm.stats': { data: healthyVmStats },
395
+ 'nutanix.vm.snapshots': { data: healthySnapshots },
396
+ 'nutanix.alerts.list': { data: emptyAlerts },
397
+ 'nutanix.protection.policies': { data: healthyProtection },
398
+ });
399
+
400
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
401
+
402
+ expect(result.category).toBe('nutanix-vm-health');
403
+ expect(result.findings).toHaveLength(1);
404
+ expect(result.findings[0]?.severity).toBe('info');
405
+ expect(result.findings[0]?.title).toContain('healthy');
406
+ expect(result.summary.probesRun).toBe(5);
407
+ });
408
+
409
+ it('resolve vm_name to vm_id', async () => {
410
+ const calls: Array<{ probe: string; params?: Record<string, unknown> }> = [];
411
+ const runProbe: RunProbe = async (probe, params) => {
412
+ calls.push({ probe, params });
413
+ const responses: Record<string, unknown> = {
414
+ 'nutanix.vms.list': { vms: [{ name: 'web-01', extId: 'vm-001' }] },
415
+ 'nutanix.vm.detail': healthyVmDetail,
416
+ 'nutanix.vm.stats': healthyVmStats,
417
+ 'nutanix.vm.snapshots': healthySnapshots,
418
+ 'nutanix.alerts.list': emptyAlerts,
419
+ 'nutanix.protection.policies': healthyProtection,
420
+ };
421
+ return mockResult(probe, responses[probe]);
422
+ };
423
+
424
+ const result = await handler({ vm_name: 'web-01' }, runProbe, defaultContext);
425
+
426
+ expect(calls[0]?.probe).toBe('nutanix.vms.list');
427
+ expect(calls[0]?.params?.name).toBe('web-01');
428
+ expect(result.findings.some((f) => f.severity === 'info')).toBe(true);
429
+ });
430
+
431
+ it('VM not found by name — critical', async () => {
432
+ const runProbe = createMockRunProbe({
433
+ 'nutanix.vms.list': { data: { vms: [], totalCount: 0 } },
434
+ });
435
+
436
+ const result = await handler({ vm_name: 'nonexistent' }, runProbe, defaultContext);
437
+
438
+ expect(result.findings).toHaveLength(1);
439
+ expect(result.findings[0]?.severity).toBe('critical');
440
+ expect(result.findings[0]?.title).toContain('not found');
441
+ });
442
+
443
+ it('no VM specified — critical', async () => {
444
+ const runProbe = createMockRunProbe({});
445
+ const result = await handler({}, runProbe, defaultContext);
446
+
447
+ expect(result.findings).toHaveLength(1);
448
+ expect(result.findings[0]?.severity).toBe('critical');
449
+ expect(result.findings[0]?.title).toContain('No VM specified');
450
+ });
451
+
452
+ it('VM powered off — warning', async () => {
453
+ const offVm = { ...healthyVmDetail, powerState: 'OFF' };
454
+
455
+ const runProbe = createMockRunProbe({
456
+ 'nutanix.vm.detail': { data: offVm },
457
+ 'nutanix.vm.stats': { data: healthyVmStats },
458
+ 'nutanix.vm.snapshots': { data: healthySnapshots },
459
+ 'nutanix.alerts.list': { data: emptyAlerts },
460
+ 'nutanix.protection.policies': { data: healthyProtection },
461
+ });
462
+
463
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
464
+
465
+ const warning = result.findings.find((f) => f.title.includes('powered off'));
466
+ expect(warning).toBeDefined();
467
+ expect(warning?.severity).toBe('warning');
468
+ });
469
+
470
+ it('high CPU — warning', async () => {
471
+ const highCpu = { ...healthyVmStats, cpuUsagePct: 92 };
472
+
473
+ const runProbe = createMockRunProbe({
474
+ 'nutanix.vm.detail': { data: healthyVmDetail },
475
+ 'nutanix.vm.stats': { data: highCpu },
476
+ 'nutanix.vm.snapshots': { data: healthySnapshots },
477
+ 'nutanix.alerts.list': { data: emptyAlerts },
478
+ 'nutanix.protection.policies': { data: healthyProtection },
479
+ });
480
+
481
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
482
+
483
+ const warning = result.findings.find((f) => f.title.includes('CPU'));
484
+ expect(warning).toBeDefined();
485
+ expect(warning?.severity).toBe('warning');
486
+ expect(warning?.title).toContain('92%');
487
+ });
488
+
489
+ it('high memory — warning', async () => {
490
+ const highMem = { ...healthyVmStats, memoryUsagePct: 95 };
491
+
492
+ const runProbe = createMockRunProbe({
493
+ 'nutanix.vm.detail': { data: healthyVmDetail },
494
+ 'nutanix.vm.stats': { data: highMem },
495
+ 'nutanix.vm.snapshots': { data: healthySnapshots },
496
+ 'nutanix.alerts.list': { data: emptyAlerts },
497
+ 'nutanix.protection.policies': { data: healthyProtection },
498
+ });
499
+
500
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
501
+
502
+ const warning = result.findings.find((f) => f.title.includes('memory'));
503
+ expect(warning).toBeDefined();
504
+ expect(warning?.severity).toBe('warning');
505
+ });
506
+
507
+ it('high I/O latency — warning', async () => {
508
+ const highLatency = { ...healthyVmStats, avgIoLatencyMs: 45 };
509
+
510
+ const runProbe = createMockRunProbe({
511
+ 'nutanix.vm.detail': { data: healthyVmDetail },
512
+ 'nutanix.vm.stats': { data: highLatency },
513
+ 'nutanix.vm.snapshots': { data: healthySnapshots },
514
+ 'nutanix.alerts.list': { data: emptyAlerts },
515
+ 'nutanix.protection.policies': { data: healthyProtection },
516
+ });
517
+
518
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
519
+
520
+ const warning = result.findings.find((f) => f.title.includes('I/O latency'));
521
+ expect(warning).toBeDefined();
522
+ expect(warning?.severity).toBe('warning');
523
+ expect(warning?.title).toContain('45ms');
524
+ });
525
+
526
+ it('no data protection — warning with remediation', async () => {
527
+ const noProtection = { policies: [], totalCount: 0, vmCovered: false, allPoliciesCount: 3 };
528
+
529
+ const runProbe = createMockRunProbe({
530
+ 'nutanix.vm.detail': { data: healthyVmDetail },
531
+ 'nutanix.vm.stats': { data: healthyVmStats },
532
+ 'nutanix.vm.snapshots': { data: healthySnapshots },
533
+ 'nutanix.alerts.list': { data: emptyAlerts },
534
+ 'nutanix.protection.policies': { data: noProtection },
535
+ });
536
+
537
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
538
+
539
+ const warning = result.findings.find((f) => f.title.includes('no data protection'));
540
+ expect(warning).toBeDefined();
541
+ expect(warning?.severity).toBe('warning');
542
+ expect(warning?.remediation).toContain('protection policy');
543
+ });
544
+
545
+ it('old snapshots — info finding', async () => {
546
+ const oldSnaps = {
547
+ snapshots: [
548
+ { name: 'old-snap', ageDays: 14, isOld: true, isExpired: false },
549
+ { name: 'very-old-snap', ageDays: 30, isOld: true, isExpired: false },
550
+ ],
551
+ totalCount: 2,
552
+ warnings: ['2 snapshot(s) older than 7 days'],
553
+ };
554
+
555
+ const runProbe = createMockRunProbe({
556
+ 'nutanix.vm.detail': { data: healthyVmDetail },
557
+ 'nutanix.vm.stats': { data: healthyVmStats },
558
+ 'nutanix.vm.snapshots': { data: oldSnaps },
559
+ 'nutanix.alerts.list': { data: emptyAlerts },
560
+ 'nutanix.protection.policies': { data: healthyProtection },
561
+ });
562
+
563
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
564
+
565
+ const info = result.findings.find((f) => f.title.includes('older than 7 days'));
566
+ expect(info).toBeDefined();
567
+ expect(info?.severity).toBe('info');
568
+ });
569
+
570
+ it('expired snapshots — warning', async () => {
571
+ const expiredSnaps = {
572
+ snapshots: [{ name: 'expired-snap', ageDays: 10, isOld: true, isExpired: true }],
573
+ totalCount: 1,
574
+ warnings: [],
575
+ };
576
+
577
+ const runProbe = createMockRunProbe({
578
+ 'nutanix.vm.detail': { data: healthyVmDetail },
579
+ 'nutanix.vm.stats': { data: healthyVmStats },
580
+ 'nutanix.vm.snapshots': { data: expiredSnaps },
581
+ 'nutanix.alerts.list': { data: emptyAlerts },
582
+ 'nutanix.protection.policies': { data: healthyProtection },
583
+ });
584
+
585
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
586
+
587
+ const warning = result.findings.find((f) => f.title.includes('expired'));
588
+ expect(warning).toBeDefined();
589
+ expect(warning?.severity).toBe('warning');
590
+ });
591
+
592
+ it('guest tools not installed — info', async () => {
593
+ const noGt = { ...healthyVmDetail, guestTools: { isEnabled: false } };
594
+
595
+ const runProbe = createMockRunProbe({
596
+ 'nutanix.vm.detail': { data: noGt },
597
+ 'nutanix.vm.stats': { data: healthyVmStats },
598
+ 'nutanix.vm.snapshots': { data: healthySnapshots },
599
+ 'nutanix.alerts.list': { data: emptyAlerts },
600
+ 'nutanix.protection.policies': { data: healthyProtection },
601
+ });
602
+
603
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
604
+
605
+ const info = result.findings.find((f) => f.title.includes('Guest tools'));
606
+ expect(info).toBeDefined();
607
+ expect(info?.severity).toBe('info');
608
+ });
609
+
610
+ it('VM-related alerts — reported as findings', async () => {
611
+ const vmAlerts = {
612
+ alerts: [
613
+ {
614
+ title: 'VM memory pressure',
615
+ severity: 'CRITICAL',
616
+ sourceEntity: { type: 'vm', name: 'web-01', extId: 'vm-001' },
617
+ creationTime: '2026-02-17T10:00:00Z',
618
+ },
619
+ ],
620
+ totalCount: 1,
621
+ };
622
+
623
+ const runProbe = createMockRunProbe({
624
+ 'nutanix.vm.detail': { data: healthyVmDetail },
625
+ 'nutanix.vm.stats': { data: healthyVmStats },
626
+ 'nutanix.vm.snapshots': { data: healthySnapshots },
627
+ 'nutanix.alerts.list': { data: vmAlerts },
628
+ 'nutanix.protection.policies': { data: healthyProtection },
629
+ });
630
+
631
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
632
+
633
+ const alert = result.findings.find((f) => f.title.includes('VM memory pressure'));
634
+ expect(alert).toBeDefined();
635
+ expect(alert?.severity).toBe('critical');
636
+ });
637
+
638
+ it('VM unreachable — critical with early return', async () => {
639
+ const runProbe = createMockRunProbe({
640
+ 'nutanix.vm.detail': { status: 'error', error: 'Connection refused' },
641
+ 'nutanix.vm.stats': { data: null },
642
+ 'nutanix.vm.snapshots': { data: null },
643
+ 'nutanix.alerts.list': { data: emptyAlerts },
644
+ 'nutanix.protection.policies': { data: null },
645
+ });
646
+
647
+ const result = await handler({ vm_id: 'vm-001' }, runProbe, defaultContext);
648
+
649
+ expect(result.findings).toHaveLength(1);
650
+ expect(result.findings[0]?.severity).toBe('critical');
651
+ expect(result.findings[0]?.title).toContain('unreachable');
652
+ });
653
+ });
654
+
655
+ // =============================================================================
656
+ // nutanix-capacity-planning tests
657
+ // =============================================================================
658
+
659
+ describe('nutanix-capacity-planning', () => {
660
+ const handler = getHandler('nutanix-capacity-planning');
661
+
662
+ const healthyClusterStats = {
663
+ cpuCapacityHz: 1e12,
664
+ cpuUsedHz: 3e11,
665
+ cpuUsagePct: 30,
666
+ memoryCapacityBytes: 1e12,
667
+ memoryUsedBytes: 4e11,
668
+ memoryUsagePct: 40,
669
+ storageCapacityBytes: 5e12,
670
+ storageUsedBytes: 1.5e12,
671
+ storageUsagePct: 30,
672
+ iops: 5000,
673
+ avgIoLatencyMs: 3,
674
+ };
675
+
676
+ const healthyVmsList = {
677
+ vms: Array.from({ length: 10 }, (_, i) => ({
678
+ name: `vm-${i}`,
679
+ extId: `vm-${i}`,
680
+ powerState: 'ON',
681
+ })),
682
+ totalCount: 10,
683
+ };
684
+
685
+ it('healthy capacity — no issues', async () => {
686
+ const runProbe: RunProbe = async (probe, params) => {
687
+ if (probe === 'nutanix.clusters.list')
688
+ return mockResult(probe, {
689
+ clusters: [{ name: 'prod', extId: 'c-001', isDegraded: false }],
690
+ });
691
+ if (probe === 'nutanix.cluster.stats') return mockResult(probe, healthyClusterStats);
692
+ if (probe === 'nutanix.storage.containers') return mockResult(probe, healthyStorage);
693
+ if (probe === 'nutanix.hosts.list') return mockResult(probe, healthyHosts);
694
+ if (probe === 'nutanix.vms.list') return mockResult(probe, healthyVmsList);
695
+ return mockResult(probe, undefined, 'error');
696
+ };
697
+
698
+ const result = await handler({}, runProbe, defaultContext);
699
+
700
+ expect(result.category).toBe('nutanix-capacity-planning');
701
+ expect(result.findings).toHaveLength(1);
702
+ expect(result.findings[0]?.severity).toBe('info');
703
+ expect(result.findings[0]?.title).toContain('healthy thresholds');
704
+ });
705
+
706
+ it('high CPU utilization >80% — warning', async () => {
707
+ const highCpuStats = { ...healthyClusterStats, cpuUsagePct: 85 };
708
+
709
+ const runProbe: RunProbe = async (probe) => {
710
+ if (probe === 'nutanix.clusters.list')
711
+ return mockResult(probe, {
712
+ clusters: [{ name: 'prod', extId: 'c-001', isDegraded: false }],
713
+ });
714
+ if (probe === 'nutanix.cluster.stats') return mockResult(probe, highCpuStats);
715
+ if (probe === 'nutanix.storage.containers') return mockResult(probe, healthyStorage);
716
+ if (probe === 'nutanix.hosts.list') return mockResult(probe, healthyHosts);
717
+ if (probe === 'nutanix.vms.list') return mockResult(probe, healthyVmsList);
718
+ return mockResult(probe, undefined, 'error');
719
+ };
720
+
721
+ const result = await handler({}, runProbe, defaultContext);
722
+
723
+ const warning = result.findings.find((f) => f.title.includes('CPU'));
724
+ expect(warning).toBeDefined();
725
+ expect(warning?.severity).toBe('warning');
726
+ expect(warning?.title).toContain('85%');
727
+ });
728
+
729
+ it('critical CPU >90% — critical', async () => {
730
+ const criticalCpuStats = { ...healthyClusterStats, cpuUsagePct: 95 };
731
+
732
+ const runProbe: RunProbe = async (probe) => {
733
+ if (probe === 'nutanix.clusters.list')
734
+ return mockResult(probe, {
735
+ clusters: [{ name: 'prod', extId: 'c-001', isDegraded: false }],
736
+ });
737
+ if (probe === 'nutanix.cluster.stats') return mockResult(probe, criticalCpuStats);
738
+ if (probe === 'nutanix.storage.containers') return mockResult(probe, healthyStorage);
739
+ if (probe === 'nutanix.hosts.list') return mockResult(probe, healthyHosts);
740
+ if (probe === 'nutanix.vms.list') return mockResult(probe, healthyVmsList);
741
+ return mockResult(probe, undefined, 'error');
742
+ };
743
+
744
+ const result = await handler({}, runProbe, defaultContext);
745
+
746
+ const critical = result.findings.find((f) => f.title.includes('CPU'));
747
+ expect(critical).toBeDefined();
748
+ expect(critical?.severity).toBe('critical');
749
+ });
750
+
751
+ it('high memory >80% — warning', async () => {
752
+ const highMemStats = { ...healthyClusterStats, memoryUsagePct: 88 };
753
+
754
+ const runProbe: RunProbe = async (probe) => {
755
+ if (probe === 'nutanix.clusters.list')
756
+ return mockResult(probe, {
757
+ clusters: [{ name: 'prod', extId: 'c-001', isDegraded: false }],
758
+ });
759
+ if (probe === 'nutanix.cluster.stats') return mockResult(probe, highMemStats);
760
+ if (probe === 'nutanix.storage.containers') return mockResult(probe, healthyStorage);
761
+ if (probe === 'nutanix.hosts.list') return mockResult(probe, healthyHosts);
762
+ if (probe === 'nutanix.vms.list') return mockResult(probe, healthyVmsList);
763
+ return mockResult(probe, undefined, 'error');
764
+ };
765
+
766
+ const result = await handler({}, runProbe, defaultContext);
767
+
768
+ const warning = result.findings.find((f) => f.title.includes('memory'));
769
+ expect(warning).toBeDefined();
770
+ expect(warning?.severity).toBe('warning');
771
+ });
772
+
773
+ it('high storage >80% — warning', async () => {
774
+ const highStorageStats = { ...healthyClusterStats, storageUsagePct: 87 };
775
+
776
+ const runProbe: RunProbe = async (probe) => {
777
+ if (probe === 'nutanix.clusters.list')
778
+ return mockResult(probe, {
779
+ clusters: [{ name: 'prod', extId: 'c-001', isDegraded: false }],
780
+ });
781
+ if (probe === 'nutanix.cluster.stats') return mockResult(probe, highStorageStats);
782
+ if (probe === 'nutanix.storage.containers') return mockResult(probe, healthyStorage);
783
+ if (probe === 'nutanix.hosts.list') return mockResult(probe, healthyHosts);
784
+ if (probe === 'nutanix.vms.list') return mockResult(probe, healthyVmsList);
785
+ return mockResult(probe, undefined, 'error');
786
+ };
787
+
788
+ const result = await handler({}, runProbe, defaultContext);
789
+
790
+ const warning = result.findings.find((f) => f.title.includes('storage'));
791
+ expect(warning).toBeDefined();
792
+ expect(warning?.severity).toBe('warning');
793
+ });
794
+
795
+ it('high VM density >30/host — warning', async () => {
796
+ const singleHost = { hosts: [{ name: 'host-01', extId: 'h-001' }], totalCount: 1 };
797
+ const manyVms = {
798
+ vms: Array.from({ length: 35 }, (_, i) => ({ name: `vm-${i}` })),
799
+ totalCount: 35,
800
+ };
801
+
802
+ const runProbe: RunProbe = async (probe) => {
803
+ if (probe === 'nutanix.clusters.list')
804
+ return mockResult(probe, {
805
+ clusters: [{ name: 'prod', extId: 'c-001', isDegraded: false }],
806
+ });
807
+ if (probe === 'nutanix.cluster.stats') return mockResult(probe, healthyClusterStats);
808
+ if (probe === 'nutanix.storage.containers') return mockResult(probe, healthyStorage);
809
+ if (probe === 'nutanix.hosts.list') return mockResult(probe, singleHost);
810
+ if (probe === 'nutanix.vms.list') return mockResult(probe, manyVms);
811
+ return mockResult(probe, undefined, 'error');
812
+ };
813
+
814
+ const result = await handler({}, runProbe, defaultContext);
815
+
816
+ const warning = result.findings.find((f) => f.title.includes('VM density'));
817
+ expect(warning).toBeDefined();
818
+ expect(warning?.severity).toBe('warning');
819
+ expect(warning?.detail).toContain('35');
820
+ });
821
+
822
+ it('no clusters — critical with early return', async () => {
823
+ const runProbe = createMockRunProbe({
824
+ 'nutanix.clusters.list': { status: 'error', error: 'Connection refused' },
825
+ });
826
+
827
+ const result = await handler({}, runProbe, defaultContext);
828
+
829
+ expect(result.findings).toHaveLength(1);
830
+ expect(result.findings[0]?.severity).toBe('critical');
831
+ expect(result.findings[0]?.title).toContain('No clusters');
832
+ });
833
+
834
+ it('per-container >85% — warning', async () => {
835
+ const fullContainers = {
836
+ containers: [{ name: 'hot-container', usedPct: 91, highUsage: true }],
837
+ totalCount: 1,
838
+ };
839
+
840
+ const runProbe: RunProbe = async (probe) => {
841
+ if (probe === 'nutanix.clusters.list')
842
+ return mockResult(probe, {
843
+ clusters: [{ name: 'prod', extId: 'c-001', isDegraded: false }],
844
+ });
845
+ if (probe === 'nutanix.cluster.stats') return mockResult(probe, healthyClusterStats);
846
+ if (probe === 'nutanix.storage.containers') return mockResult(probe, fullContainers);
847
+ if (probe === 'nutanix.hosts.list') return mockResult(probe, healthyHosts);
848
+ if (probe === 'nutanix.vms.list') return mockResult(probe, healthyVmsList);
849
+ return mockResult(probe, undefined, 'error');
850
+ };
851
+
852
+ const result = await handler({}, runProbe, defaultContext);
853
+
854
+ const warning = result.findings.find((f) => f.title.includes('hot-container'));
855
+ expect(warning).toBeDefined();
856
+ expect(warning?.severity).toBe('warning');
857
+ });
858
+
859
+ it('multi-cluster — probes run per cluster', async () => {
860
+ const calls: string[] = [];
861
+ const runProbe: RunProbe = async (probe, params) => {
862
+ calls.push(probe);
863
+ if (probe === 'nutanix.clusters.list') {
864
+ return mockResult(probe, {
865
+ clusters: [
866
+ { name: 'dc1', extId: 'c-001', isDegraded: false },
867
+ { name: 'dc2', extId: 'c-002', isDegraded: false },
868
+ ],
869
+ });
870
+ }
871
+ if (probe === 'nutanix.cluster.stats') return mockResult(probe, healthyClusterStats);
872
+ if (probe === 'nutanix.storage.containers') return mockResult(probe, healthyStorage);
873
+ if (probe === 'nutanix.hosts.list') return mockResult(probe, healthyHosts);
874
+ if (probe === 'nutanix.vms.list') return mockResult(probe, healthyVmsList);
875
+ return mockResult(probe, undefined, 'error');
876
+ };
877
+
878
+ const result = await handler({}, runProbe, defaultContext);
879
+
880
+ // Should have 2x cluster.stats calls (one per cluster)
881
+ expect(calls.filter((c) => c === 'nutanix.cluster.stats')).toHaveLength(2);
882
+ expect(result.summary.summaryText).toContain('2 cluster(s)');
883
+ });
884
+ });
885
+
886
+ // =============================================================================
887
+ // nutanix-storefront-investigate tests
888
+ // =============================================================================
889
+
890
+ describe('nutanix-storefront-investigate', () => {
891
+ const handler = getHandler('nutanix-storefront-investigate');
892
+
893
+ it('healthy tagged VMs — no issues', async () => {
894
+ const entities = {
895
+ entities: [
896
+ { entityType: 'vm', entityId: 'vm-001', entityName: 'storefront-01' },
897
+ { entityType: 'vm', entityId: 'vm-002', entityName: 'storefront-02' },
898
+ ],
899
+ totalCount: 2,
900
+ };
901
+
902
+ const runProbe: RunProbe = async (probe, params) => {
903
+ if (probe === 'nutanix.categories.entities') return mockResult(probe, entities);
904
+ if (probe === 'nutanix.vm.detail')
905
+ return mockResult(probe, {
906
+ ...healthyVmDetail,
907
+ name: params?.vm_id === 'vm-001' ? 'storefront-01' : 'storefront-02',
908
+ });
909
+ if (probe === 'nutanix.vm.stats') return mockResult(probe, healthyVmStats);
910
+ if (probe === 'nutanix.alerts.list') return mockResult(probe, emptyAlerts);
911
+ return mockResult(probe, undefined, 'error');
912
+ };
913
+
914
+ const result = await handler(
915
+ { category_key: 'Environment', category_value: 'Storefront' },
916
+ runProbe,
917
+ defaultContext,
918
+ );
919
+
920
+ expect(result.category).toBe('nutanix-storefront-investigate');
921
+ expect(result.findings).toHaveLength(1);
922
+ expect(result.findings[0]?.severity).toBe('info');
923
+ expect(result.findings[0]?.title).toContain('healthy');
924
+ });
925
+
926
+ it('missing category params — critical', async () => {
927
+ const runProbe = createMockRunProbe({});
928
+ const result = await handler({}, runProbe, defaultContext);
929
+
930
+ expect(result.findings).toHaveLength(1);
931
+ expect(result.findings[0]?.severity).toBe('critical');
932
+ expect(result.findings[0]?.title).toContain('Missing category');
933
+ });
934
+
935
+ it('no entities found — info', async () => {
936
+ const runProbe = createMockRunProbe({
937
+ 'nutanix.categories.entities': { data: { entities: [], totalCount: 0 } },
938
+ });
939
+
940
+ const result = await handler(
941
+ { category_key: 'App', category_value: 'None' },
942
+ runProbe,
943
+ defaultContext,
944
+ );
945
+
946
+ expect(result.findings).toHaveLength(1);
947
+ expect(result.findings[0]?.severity).toBe('info');
948
+ expect(result.findings[0]?.title).toContain('No entities');
949
+ });
950
+
951
+ it('non-VM entities only — info', async () => {
952
+ const entities = {
953
+ entities: [{ entityType: 'host', entityId: 'h-001', entityName: 'host-01' }],
954
+ totalCount: 1,
955
+ };
956
+
957
+ const runProbe = createMockRunProbe({
958
+ 'nutanix.categories.entities': { data: entities },
959
+ });
960
+
961
+ const result = await handler(
962
+ { category_key: 'Tier', category_value: 'Gold' },
963
+ runProbe,
964
+ defaultContext,
965
+ );
966
+
967
+ expect(result.findings).toHaveLength(1);
968
+ expect(result.findings[0]?.title).toContain('no VMs');
969
+ });
970
+
971
+ it('powered off VMs — warning per VM', async () => {
972
+ const entities = {
973
+ entities: [
974
+ { entityType: 'vm', entityId: 'vm-001', entityName: 'store-01' },
975
+ { entityType: 'vm', entityId: 'vm-002', entityName: 'store-02' },
976
+ ],
977
+ totalCount: 2,
978
+ };
979
+ const offVm = { ...healthyVmDetail, powerState: 'OFF' };
980
+
981
+ const runProbe: RunProbe = async (probe) => {
982
+ if (probe === 'nutanix.categories.entities') return mockResult(probe, entities);
983
+ if (probe === 'nutanix.vm.detail') return mockResult(probe, offVm);
984
+ if (probe === 'nutanix.vm.stats') return mockResult(probe, healthyVmStats);
985
+ if (probe === 'nutanix.alerts.list') return mockResult(probe, emptyAlerts);
986
+ return mockResult(probe, undefined, 'error');
987
+ };
988
+
989
+ const result = await handler(
990
+ { category_key: 'App', category_value: 'Store' },
991
+ runProbe,
992
+ defaultContext,
993
+ );
994
+
995
+ const perVmOff = result.findings.filter(
996
+ (f) => f.severity === 'warning' && f.title.includes('powered off'),
997
+ );
998
+ expect(perVmOff).toHaveLength(2);
999
+
1000
+ // Also should have "all VMs powered off" critical finding
1001
+ const allOff = result.findings.find(
1002
+ (f) => f.severity === 'critical' && f.title.includes('powered off'),
1003
+ );
1004
+ expect(allOff).toBeDefined();
1005
+ });
1006
+
1007
+ it('high CPU on tagged VMs — warning', async () => {
1008
+ const entities = {
1009
+ entities: [{ entityType: 'vm', entityId: 'vm-001', entityName: 'store-01' }],
1010
+ totalCount: 1,
1011
+ };
1012
+ const highCpuStats = { ...healthyVmStats, cpuUsagePct: 92 };
1013
+
1014
+ const runProbe: RunProbe = async (probe) => {
1015
+ if (probe === 'nutanix.categories.entities') return mockResult(probe, entities);
1016
+ if (probe === 'nutanix.vm.detail') return mockResult(probe, healthyVmDetail);
1017
+ if (probe === 'nutanix.vm.stats') return mockResult(probe, highCpuStats);
1018
+ if (probe === 'nutanix.alerts.list') return mockResult(probe, emptyAlerts);
1019
+ return mockResult(probe, undefined, 'error');
1020
+ };
1021
+
1022
+ const result = await handler(
1023
+ { category_key: 'App', category_value: 'Store' },
1024
+ runProbe,
1025
+ defaultContext,
1026
+ );
1027
+
1028
+ const warning = result.findings.find((f) => f.title.includes('CPU'));
1029
+ expect(warning).toBeDefined();
1030
+ expect(warning?.severity).toBe('warning');
1031
+ });
1032
+
1033
+ it('critical alerts on tagged VMs — critical', async () => {
1034
+ const entities = {
1035
+ entities: [{ entityType: 'vm', entityId: 'vm-001', entityName: 'web-01' }],
1036
+ totalCount: 1,
1037
+ };
1038
+ const vmAlerts = {
1039
+ alerts: [
1040
+ {
1041
+ title: 'Disk failure',
1042
+ severity: 'CRITICAL',
1043
+ sourceEntity: { type: 'vm', name: 'web-01', extId: 'vm-001' },
1044
+ creationTime: '2026-02-17T10:00:00Z',
1045
+ },
1046
+ ],
1047
+ totalCount: 1,
1048
+ };
1049
+
1050
+ const runProbe: RunProbe = async (probe) => {
1051
+ if (probe === 'nutanix.categories.entities') return mockResult(probe, entities);
1052
+ if (probe === 'nutanix.vm.detail') return mockResult(probe, healthyVmDetail);
1053
+ if (probe === 'nutanix.vm.stats') return mockResult(probe, healthyVmStats);
1054
+ if (probe === 'nutanix.alerts.list') return mockResult(probe, vmAlerts);
1055
+ return mockResult(probe, undefined, 'error');
1056
+ };
1057
+
1058
+ const result = await handler(
1059
+ { category_key: 'Environment', category_value: 'Storefront' },
1060
+ runProbe,
1061
+ defaultContext,
1062
+ );
1063
+
1064
+ const critical = result.findings.find((f) => f.title.includes('Disk failure'));
1065
+ expect(critical).toBeDefined();
1066
+ expect(critical?.severity).toBe('critical');
1067
+ });
1068
+
1069
+ it('limits VM checks to 10', async () => {
1070
+ const entities = {
1071
+ entities: Array.from({ length: 15 }, (_, i) => ({
1072
+ entityType: 'vm',
1073
+ entityId: `vm-${i}`,
1074
+ entityName: `store-${i}`,
1075
+ })),
1076
+ totalCount: 15,
1077
+ };
1078
+
1079
+ const vmDetailCalls: string[] = [];
1080
+ const runProbe: RunProbe = async (probe, params) => {
1081
+ if (probe === 'nutanix.categories.entities') return mockResult(probe, entities);
1082
+ if (probe === 'nutanix.vm.detail') {
1083
+ vmDetailCalls.push(params?.vm_id as string);
1084
+ return mockResult(probe, healthyVmDetail);
1085
+ }
1086
+ if (probe === 'nutanix.vm.stats') return mockResult(probe, healthyVmStats);
1087
+ if (probe === 'nutanix.alerts.list') return mockResult(probe, emptyAlerts);
1088
+ return mockResult(probe, undefined, 'error');
1089
+ };
1090
+
1091
+ await handler({ category_key: 'App', category_value: 'Store' }, runProbe, defaultContext);
1092
+
1093
+ // Should only check first 10 VMs
1094
+ expect(vmDetailCalls).toHaveLength(10);
1095
+ });
1096
+ });
1097
+
1098
+ // =============================================================================
1099
+ // Definitions
1100
+ // =============================================================================
1101
+
1102
+ describe('nutanixDiagnosticRunbooks definitions', () => {
1103
+ it('exports 4 runbook definitions', () => {
1104
+ expect(nutanixDiagnosticRunbooks).toHaveLength(4);
1105
+ });
1106
+
1107
+ it('has correct categories', () => {
1108
+ const categories = nutanixDiagnosticRunbooks.map((r) => r.category);
1109
+ expect(categories).toContain('nutanix-cluster-health');
1110
+ expect(categories).toContain('nutanix-vm-health');
1111
+ expect(categories).toContain('nutanix-capacity-planning');
1112
+ expect(categories).toContain('nutanix-storefront-investigate');
1113
+ });
1114
+
1115
+ it('nutanix-vm-health has vm_id and vm_name params', () => {
1116
+ const def = nutanixDiagnosticRunbooks.find((r) => r.category === 'nutanix-vm-health');
1117
+ expect(def?.params?.vm_id).toBeDefined();
1118
+ expect(def?.params?.vm_name).toBeDefined();
1119
+ });
1120
+
1121
+ it('nutanix-storefront-investigate requires category_key and category_value', () => {
1122
+ const def = nutanixDiagnosticRunbooks.find(
1123
+ (r) => r.category === 'nutanix-storefront-investigate',
1124
+ );
1125
+ expect(def?.params?.category_key?.required).toBe(true);
1126
+ expect(def?.params?.category_value?.required).toBe(true);
1127
+ });
1128
+
1129
+ it('nutanix-cluster-health has no required params', () => {
1130
+ const def = nutanixDiagnosticRunbooks.find((r) => r.category === 'nutanix-cluster-health');
1131
+ expect(def?.params).toBeUndefined();
1132
+ });
1133
+
1134
+ it('nutanix-capacity-planning has no required params', () => {
1135
+ const def = nutanixDiagnosticRunbooks.find((r) => r.category === 'nutanix-capacity-planning');
1136
+ expect(def?.params).toBeUndefined();
1137
+ });
1138
+ });