info.test.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. import { describe, it, expect } from "vitest";
  2. import { executeInfo } from "../commands/info.js";
  3. import type { GlobalOptions } from "../types.js";
  4. /**
  5. * Integration tests for the info CLI command
  6. *
  7. * Test strategy:
  8. * 1. Feature matrix - Display all platforms info
  9. * 2. Single platform info - Detailed info for one platform
  10. * 3. Platform comparison - Compare two platforms
  11. * 4. Error handling - Invalid platform names
  12. * 5. Output format - JSON and text output
  13. */
  14. // Default global options for tests
  15. const defaultGlobalOptions: GlobalOptions = {
  16. verbose: false,
  17. quiet: true,
  18. outputFormat: "text",
  19. };
  20. describe("info command", () => {
  21. // ============================================================================
  22. // FEATURE MATRIX (NO PLATFORM SPECIFIED)
  23. // ============================================================================
  24. describe("feature matrix (no platform specified)", () => {
  25. it("returns info for all platforms when no platform specified", async () => {
  26. // Act
  27. const result = await executeInfo(
  28. undefined,
  29. {},
  30. defaultGlobalOptions
  31. );
  32. // Assert
  33. expect(result.success).toBe(true);
  34. expect(result.data?.platforms).toBeDefined();
  35. expect(Array.isArray(result.data?.platforms)).toBe(true);
  36. });
  37. it("includes all four platforms in matrix", async () => {
  38. // Act
  39. const result = await executeInfo(
  40. undefined,
  41. {},
  42. defaultGlobalOptions
  43. );
  44. // Assert
  45. expect(result.success).toBe(true);
  46. const platformNames = result.data?.platforms?.map(p => p.platform) || [];
  47. expect(platformNames).toContain("oac");
  48. expect(platformNames).toContain("cursor");
  49. expect(platformNames).toContain("claude");
  50. expect(platformNames).toContain("windsurf");
  51. });
  52. it("each platform has required fields", async () => {
  53. // Act
  54. const result = await executeInfo(
  55. undefined,
  56. {},
  57. defaultGlobalOptions
  58. );
  59. // Assert
  60. expect(result.success).toBe(true);
  61. for (const platform of result.data?.platforms || []) {
  62. expect(platform.platform).toBeDefined();
  63. expect(platform.displayName).toBeDefined();
  64. expect(platform.capabilities).toBeDefined();
  65. expect(platform.features).toBeDefined();
  66. }
  67. });
  68. });
  69. // ============================================================================
  70. // SINGLE PLATFORM INFO
  71. // ============================================================================
  72. describe("single platform info", () => {
  73. it("returns detailed info for OAC platform", async () => {
  74. // Act
  75. const result = await executeInfo(
  76. "oac",
  77. {},
  78. defaultGlobalOptions
  79. );
  80. // Assert
  81. expect(result.success).toBe(true);
  82. expect(result.data?.platform).toBeDefined();
  83. expect(result.data?.platform?.platform).toBe("oac");
  84. expect(result.data?.platform?.displayName).toBe("OpenAgents Control");
  85. });
  86. it("returns detailed info for Cursor platform", async () => {
  87. // Act
  88. const result = await executeInfo(
  89. "cursor",
  90. {},
  91. defaultGlobalOptions
  92. );
  93. // Assert
  94. expect(result.success).toBe(true);
  95. expect(result.data?.platform?.platform).toBe("cursor");
  96. expect(result.data?.platform?.displayName).toBe("Cursor IDE");
  97. });
  98. it("returns detailed info for Claude platform", async () => {
  99. // Act
  100. const result = await executeInfo(
  101. "claude",
  102. {},
  103. defaultGlobalOptions
  104. );
  105. // Assert
  106. expect(result.success).toBe(true);
  107. expect(result.data?.platform?.platform).toBe("claude");
  108. expect(result.data?.platform?.displayName).toBe("Claude Code");
  109. });
  110. it("returns detailed info for Windsurf platform", async () => {
  111. // Act
  112. const result = await executeInfo(
  113. "windsurf",
  114. {},
  115. defaultGlobalOptions
  116. );
  117. // Assert
  118. expect(result.success).toBe(true);
  119. expect(result.data?.platform?.platform).toBe("windsurf");
  120. expect(result.data?.platform?.displayName).toBe("Windsurf");
  121. });
  122. it("includes capabilities in platform info", async () => {
  123. // Act
  124. const result = await executeInfo(
  125. "cursor",
  126. {},
  127. defaultGlobalOptions
  128. );
  129. // Assert
  130. expect(result.success).toBe(true);
  131. const caps = result.data?.platform?.capabilities;
  132. expect(caps).toBeDefined();
  133. expect(typeof caps?.supportsMultipleAgents).toBe("boolean");
  134. expect(typeof caps?.supportsSkills).toBe("boolean");
  135. expect(typeof caps?.supportsHooks).toBe("boolean");
  136. expect(typeof caps?.supportsGranularPermissions).toBe("boolean");
  137. expect(typeof caps?.supportsContexts).toBe("boolean");
  138. expect(typeof caps?.supportsCustomModels).toBe("boolean");
  139. expect(typeof caps?.supportsTemperature).toBe("boolean");
  140. expect(typeof caps?.supportsMaxSteps).toBe("boolean");
  141. expect(caps?.configFormat).toBeDefined();
  142. expect(caps?.outputStructure).toBeDefined();
  143. });
  144. it("includes features list in platform info", async () => {
  145. // Act
  146. const result = await executeInfo(
  147. "oac",
  148. {},
  149. defaultGlobalOptions
  150. );
  151. // Assert
  152. expect(result.success).toBe(true);
  153. expect(Array.isArray(result.data?.platform?.features)).toBe(true);
  154. for (const feature of result.data?.platform?.features || []) {
  155. expect(feature.name).toBeDefined();
  156. expect(feature.category).toBeDefined();
  157. expect(feature.description).toBeDefined();
  158. expect(["full", "partial", "none"]).toContain(feature.support);
  159. }
  160. });
  161. });
  162. // ============================================================================
  163. // PLATFORM COMPARISON
  164. // ============================================================================
  165. describe("platform comparison", () => {
  166. it("compares OAC with Cursor", async () => {
  167. // Act
  168. const result = await executeInfo(
  169. "oac",
  170. { compare: "cursor" },
  171. defaultGlobalOptions
  172. );
  173. // Assert
  174. expect(result.success).toBe(true);
  175. expect(result.data?.comparison).toBeDefined();
  176. expect(result.data?.comparison?.platformA).toBe("oac");
  177. expect(result.data?.comparison?.platformB).toBe("cursor");
  178. });
  179. it("compares Claude with Windsurf", async () => {
  180. // Act
  181. const result = await executeInfo(
  182. "claude",
  183. { compare: "windsurf" },
  184. defaultGlobalOptions
  185. );
  186. // Assert
  187. expect(result.success).toBe(true);
  188. expect(result.data?.comparison).toBeDefined();
  189. expect(result.data?.comparison?.platformA).toBe("claude");
  190. expect(result.data?.comparison?.platformB).toBe("windsurf");
  191. });
  192. it("comparison includes identical features list", async () => {
  193. // Act
  194. const result = await executeInfo(
  195. "oac",
  196. { compare: "cursor" },
  197. defaultGlobalOptions
  198. );
  199. // Assert
  200. expect(result.success).toBe(true);
  201. expect(Array.isArray(result.data?.comparison?.identical)).toBe(true);
  202. });
  203. it("comparison includes betterInA features list", async () => {
  204. // Act
  205. const result = await executeInfo(
  206. "oac",
  207. { compare: "cursor" },
  208. defaultGlobalOptions
  209. );
  210. // Assert
  211. expect(result.success).toBe(true);
  212. expect(Array.isArray(result.data?.comparison?.betterInA)).toBe(true);
  213. });
  214. it("comparison includes betterInB features list", async () => {
  215. // Act
  216. const result = await executeInfo(
  217. "oac",
  218. { compare: "cursor" },
  219. defaultGlobalOptions
  220. );
  221. // Assert
  222. expect(result.success).toBe(true);
  223. expect(Array.isArray(result.data?.comparison?.betterInB)).toBe(true);
  224. });
  225. it("also returns platform info when comparing", async () => {
  226. // Act
  227. const result = await executeInfo(
  228. "oac",
  229. { compare: "claude" },
  230. defaultGlobalOptions
  231. );
  232. // Assert
  233. expect(result.success).toBe(true);
  234. expect(result.data?.platform).toBeDefined();
  235. expect(result.data?.comparison).toBeDefined();
  236. });
  237. });
  238. // ============================================================================
  239. // ERROR HANDLING
  240. // ============================================================================
  241. describe("error handling", () => {
  242. it("returns error for invalid platform name", async () => {
  243. // Act
  244. const result = await executeInfo(
  245. "invalid-platform",
  246. {},
  247. defaultGlobalOptions
  248. );
  249. // Assert
  250. expect(result.success).toBe(false);
  251. expect(result.error).toContain("Unknown platform");
  252. });
  253. it("returns error for invalid comparison platform", async () => {
  254. // Act
  255. const result = await executeInfo(
  256. "oac",
  257. { compare: "invalid-platform" as any },
  258. defaultGlobalOptions
  259. );
  260. // Assert
  261. expect(result.success).toBe(false);
  262. expect(result.error).toContain("Unknown platform");
  263. });
  264. it("handles case-sensitive platform names", async () => {
  265. // Act
  266. const result = await executeInfo(
  267. "OAC", // uppercase should fail
  268. {},
  269. defaultGlobalOptions
  270. );
  271. // Assert
  272. expect(result.success).toBe(false);
  273. });
  274. });
  275. // ============================================================================
  276. // JSON OUTPUT FORMAT
  277. // ============================================================================
  278. describe("JSON output format", () => {
  279. it("works with JSON output format for matrix", async () => {
  280. // Act
  281. const result = await executeInfo(
  282. undefined,
  283. {},
  284. { ...defaultGlobalOptions, outputFormat: "json" }
  285. );
  286. // Assert
  287. expect(result.success).toBe(true);
  288. expect(result.data?.platforms).toBeDefined();
  289. });
  290. it("works with JSON output format for single platform", async () => {
  291. // Act
  292. const result = await executeInfo(
  293. "cursor",
  294. {},
  295. { ...defaultGlobalOptions, outputFormat: "json" }
  296. );
  297. // Assert
  298. expect(result.success).toBe(true);
  299. expect(result.data?.platform).toBeDefined();
  300. });
  301. it("works with JSON output format for comparison", async () => {
  302. // Act
  303. const result = await executeInfo(
  304. "oac",
  305. { compare: "claude" },
  306. { ...defaultGlobalOptions, outputFormat: "json" }
  307. );
  308. // Assert
  309. expect(result.success).toBe(true);
  310. expect(result.data?.comparison).toBeDefined();
  311. });
  312. });
  313. // ============================================================================
  314. // DETAILED OPTION
  315. // ============================================================================
  316. describe("detailed option", () => {
  317. it("respects detailed flag", async () => {
  318. // Act
  319. const result = await executeInfo(
  320. "oac",
  321. { detailed: true },
  322. defaultGlobalOptions
  323. );
  324. // Assert
  325. expect(result.success).toBe(true);
  326. });
  327. });
  328. // ============================================================================
  329. // CAPABILITY VALUES
  330. // ============================================================================
  331. describe("capability values", () => {
  332. it("OAC supports all features", async () => {
  333. // Act
  334. const result = await executeInfo(
  335. "oac",
  336. {},
  337. defaultGlobalOptions
  338. );
  339. // Assert
  340. expect(result.success).toBe(true);
  341. const caps = result.data?.platform?.capabilities;
  342. expect(caps?.supportsMultipleAgents).toBe(true);
  343. expect(caps?.supportsSkills).toBe(true);
  344. expect(caps?.supportsHooks).toBe(true);
  345. expect(caps?.supportsGranularPermissions).toBe(true);
  346. expect(caps?.supportsContexts).toBe(true);
  347. });
  348. it("Cursor has limited feature support", async () => {
  349. // Act
  350. const result = await executeInfo(
  351. "cursor",
  352. {},
  353. defaultGlobalOptions
  354. );
  355. // Assert
  356. expect(result.success).toBe(true);
  357. const caps = result.data?.platform?.capabilities;
  358. expect(caps?.supportsMultipleAgents).toBe(false);
  359. expect(caps?.supportsSkills).toBe(false);
  360. expect(caps?.supportsHooks).toBe(false);
  361. });
  362. it("Claude supports skills and hooks", async () => {
  363. // Act
  364. const result = await executeInfo(
  365. "claude",
  366. {},
  367. defaultGlobalOptions
  368. );
  369. // Assert
  370. expect(result.success).toBe(true);
  371. const caps = result.data?.platform?.capabilities;
  372. expect(caps?.supportsMultipleAgents).toBe(true);
  373. expect(caps?.supportsSkills).toBe(true);
  374. expect(caps?.supportsHooks).toBe(true);
  375. });
  376. });
  377. // ============================================================================
  378. // FEATURE CATEGORIES
  379. // ============================================================================
  380. describe("feature categories", () => {
  381. it("features are organized by category", async () => {
  382. // Act
  383. const result = await executeInfo(
  384. "oac",
  385. {},
  386. defaultGlobalOptions
  387. );
  388. // Assert
  389. expect(result.success).toBe(true);
  390. const categories = new Set(
  391. result.data?.platform?.features?.map(f => f.category) || []
  392. );
  393. // Should have multiple categories
  394. expect(categories.size).toBeGreaterThan(1);
  395. });
  396. it("includes expected categories", async () => {
  397. // Act
  398. const result = await executeInfo(
  399. "oac",
  400. {},
  401. defaultGlobalOptions
  402. );
  403. // Assert
  404. expect(result.success).toBe(true);
  405. const categories = new Set(
  406. result.data?.platform?.features?.map(f => f.category) || []
  407. );
  408. // Should include common categories
  409. const expectedCategories = ["agents", "permissions", "tools", "context", "model", "advanced"];
  410. for (const cat of expectedCategories) {
  411. expect(categories.has(cat)).toBe(true);
  412. }
  413. });
  414. });
  415. // ============================================================================
  416. // CONFIG FORMAT AND OUTPUT STRUCTURE
  417. // ============================================================================
  418. describe("config format and output structure", () => {
  419. it("OAC uses markdown format and directory structure", async () => {
  420. // Act
  421. const result = await executeInfo(
  422. "oac",
  423. {},
  424. defaultGlobalOptions
  425. );
  426. // Assert
  427. expect(result.success).toBe(true);
  428. expect(result.data?.platform?.capabilities?.configFormat).toBe("markdown");
  429. expect(result.data?.platform?.capabilities?.outputStructure).toBe("directory");
  430. });
  431. it("Cursor uses plain format and single-file structure", async () => {
  432. // Act
  433. const result = await executeInfo(
  434. "cursor",
  435. {},
  436. defaultGlobalOptions
  437. );
  438. // Assert
  439. expect(result.success).toBe(true);
  440. expect(result.data?.platform?.capabilities?.configFormat).toBe("plain");
  441. expect(result.data?.platform?.capabilities?.outputStructure).toBe("single-file");
  442. });
  443. it("Claude uses markdown format and directory structure", async () => {
  444. // Act
  445. const result = await executeInfo(
  446. "claude",
  447. {},
  448. defaultGlobalOptions
  449. );
  450. // Assert
  451. expect(result.success).toBe(true);
  452. // Claude Code agents are markdown files with YAML frontmatter under
  453. // plugins/claude-code/agents/. This previously asserted 'json' on the reasoning that
  454. // settings.json is JSON — but settings.json is not what the adapter emits, and
  455. // ClaudeAdapter reported 'markdown' at the same time. The emitted artifact decides.
  456. expect(result.data?.platform?.capabilities?.configFormat).toBe("markdown");
  457. expect(result.data?.platform?.capabilities?.outputStructure).toBe("directory");
  458. });
  459. });
  460. });