ModelMapper.test.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. /**
  2. * Unit tests for ModelMapper
  3. *
  4. * Tests AI model identifier mapping between platforms.
  5. */
  6. import { describe, it, expect } from "vitest";
  7. import {
  8. mapModelFromOAC,
  9. mapModelToOAC,
  10. getModelFamily,
  11. getModelInfo,
  12. getAllModels,
  13. getModelsForPlatform,
  14. isModelAvailable,
  15. getDefaultModel,
  16. } from "../../../src/mappers/ModelMapper";
  17. describe("ModelMapper", () => {
  18. // ==========================================================================
  19. // mapModelFromOAC
  20. // ==========================================================================
  21. describe("mapModelFromOAC()", () => {
  22. describe("Claude models", () => {
  23. it("maps claude-sonnet-4 to Claude format with date suffix", () => {
  24. const result = mapModelFromOAC("claude-sonnet-4", "claude");
  25. expect(result.id).toBe("claude-sonnet-4-20250514");
  26. expect(result.exact).toBe(true);
  27. });
  28. it("maps claude-sonnet-4 to Cursor format", () => {
  29. const result = mapModelFromOAC("claude-sonnet-4", "cursor");
  30. expect(result.id).toBe("claude-sonnet-4");
  31. expect(result.exact).toBe(true);
  32. });
  33. it("maps claude-sonnet-4 to Windsurf format", () => {
  34. const result = mapModelFromOAC("claude-sonnet-4", "windsurf");
  35. expect(result.id).toBe("claude-4-sonnet");
  36. expect(result.exact).toBe(true);
  37. });
  38. it("maps claude-3.5-sonnet correctly", () => {
  39. const result = mapModelFromOAC("claude-3.5-sonnet", "claude");
  40. expect(result.id).toBe("claude-3-5-sonnet-20241022");
  41. expect(result.exact).toBe(true);
  42. });
  43. it("maps claude-3-opus correctly", () => {
  44. const result = mapModelFromOAC("claude-3-opus", "claude");
  45. expect(result.id).toBe("claude-3-opus-20240229");
  46. expect(result.exact).toBe(true);
  47. });
  48. });
  49. describe("GPT models", () => {
  50. it("maps gpt-4 to Cursor", () => {
  51. const result = mapModelFromOAC("gpt-4", "cursor");
  52. expect(result.id).toBe("gpt-4");
  53. expect(result.exact).toBe(true);
  54. });
  55. it("maps gpt-4-turbo to Windsurf", () => {
  56. const result = mapModelFromOAC("gpt-4-turbo", "windsurf");
  57. expect(result.id).toBe("gpt-4-turbo");
  58. expect(result.exact).toBe(true);
  59. });
  60. it("maps gpt-4o to Cursor", () => {
  61. const result = mapModelFromOAC("gpt-4o", "cursor");
  62. expect(result.id).toBe("gpt-4o");
  63. expect(result.exact).toBe(true);
  64. });
  65. });
  66. describe("unknown models", () => {
  67. it("returns model as-is with warning", () => {
  68. const result = mapModelFromOAC("unknown-model", "claude");
  69. expect(result.id).toBe("unknown-model");
  70. expect(result.exact).toBe(false);
  71. expect(result.warning).toContain("Unknown model");
  72. });
  73. it("recognizes platform format and returns as-is", () => {
  74. const result = mapModelFromOAC("claude-sonnet-4-20250514", "claude");
  75. expect(result.id).toBe("claude-sonnet-4-20250514");
  76. expect(result.exact).toBe(true);
  77. });
  78. });
  79. });
  80. // ==========================================================================
  81. // mapModelToOAC
  82. // ==========================================================================
  83. describe("mapModelToOAC()", () => {
  84. describe("from Claude", () => {
  85. it("maps Claude format to OAC", () => {
  86. const result = mapModelToOAC("claude-sonnet-4-20250514", "claude");
  87. expect(result.id).toBe("claude-sonnet-4");
  88. expect(result.exact).toBe(true);
  89. });
  90. it("maps claude-3-5-sonnet with date to OAC", () => {
  91. const result = mapModelToOAC("claude-3-5-sonnet-20241022", "claude");
  92. expect(result.id).toBe("claude-3.5-sonnet");
  93. expect(result.exact).toBe(true);
  94. });
  95. });
  96. describe("from Cursor", () => {
  97. it("maps Cursor format to OAC", () => {
  98. const result = mapModelToOAC("claude-sonnet-4", "cursor");
  99. expect(result.id).toBe("claude-sonnet-4");
  100. expect(result.exact).toBe(true);
  101. });
  102. it("maps gpt-4 from Cursor", () => {
  103. const result = mapModelToOAC("gpt-4", "cursor");
  104. expect(result.id).toBe("gpt-4");
  105. expect(result.exact).toBe(true);
  106. });
  107. });
  108. describe("from Windsurf", () => {
  109. it("maps Windsurf format to OAC", () => {
  110. const result = mapModelToOAC("claude-4-sonnet", "windsurf");
  111. expect(result.id).toBe("claude-sonnet-4");
  112. expect(result.exact).toBe(true);
  113. });
  114. });
  115. describe("unknown models", () => {
  116. it("attempts pattern matching", () => {
  117. const result = mapModelToOAC("claude-sonnet-new-20260101", "claude");
  118. // Should strip date and attempt normalization
  119. expect(result.exact).toBe(false);
  120. expect(result.warning).toBeDefined();
  121. });
  122. it("returns as-is for completely unknown models", () => {
  123. const result = mapModelToOAC("some-new-model", "cursor");
  124. expect(result.id).toBe("some-new-model");
  125. expect(result.exact).toBe(false);
  126. });
  127. });
  128. });
  129. // ==========================================================================
  130. // getModelFamily
  131. // ==========================================================================
  132. describe("getModelFamily()", () => {
  133. it("identifies Claude models", () => {
  134. expect(getModelFamily("claude-sonnet-4")).toBe("claude");
  135. expect(getModelFamily("claude-3-opus")).toBe("claude");
  136. expect(getModelFamily("CLAUDE-HAIKU")).toBe("claude");
  137. });
  138. it("identifies GPT models", () => {
  139. expect(getModelFamily("gpt-4")).toBe("gpt");
  140. expect(getModelFamily("gpt-4-turbo")).toBe("gpt");
  141. expect(getModelFamily("GPT-4o")).toBe("gpt");
  142. });
  143. it("identifies Gemini models", () => {
  144. expect(getModelFamily("gemini-pro")).toBe("gemini");
  145. expect(getModelFamily("gemini-2.0-flash")).toBe("gemini");
  146. });
  147. it("identifies Llama models", () => {
  148. expect(getModelFamily("llama-3")).toBe("llama");
  149. });
  150. it("identifies Mistral models", () => {
  151. expect(getModelFamily("mistral-large")).toBe("mistral");
  152. });
  153. it("returns 'other' for unknown models", () => {
  154. expect(getModelFamily("some-unknown-model")).toBe("other");
  155. });
  156. });
  157. // ==========================================================================
  158. // getModelInfo
  159. // ==========================================================================
  160. describe("getModelInfo()", () => {
  161. it("returns model info for known OAC model", () => {
  162. const info = getModelInfo("claude-sonnet-4");
  163. expect(info).toBeDefined();
  164. expect(info?.displayName).toBe("Claude Sonnet 4");
  165. expect(info?.family).toBe("claude");
  166. });
  167. it("returns undefined for unknown model", () => {
  168. const info = getModelInfo("unknown-model");
  169. expect(info).toBeUndefined();
  170. });
  171. });
  172. // ==========================================================================
  173. // getAllModels
  174. // ==========================================================================
  175. describe("getAllModels()", () => {
  176. it("returns array of all registered models", () => {
  177. const models = getAllModels();
  178. expect(Array.isArray(models)).toBe(true);
  179. expect(models.length).toBeGreaterThan(0);
  180. });
  181. it("includes Claude models", () => {
  182. const models = getAllModels();
  183. const claudeModels = models.filter((m) => m.family === "claude");
  184. expect(claudeModels.length).toBeGreaterThan(0);
  185. });
  186. it("includes GPT models", () => {
  187. const models = getAllModels();
  188. const gptModels = models.filter((m) => m.family === "gpt");
  189. expect(gptModels.length).toBeGreaterThan(0);
  190. });
  191. });
  192. // ==========================================================================
  193. // getModelsForPlatform
  194. // ==========================================================================
  195. describe("getModelsForPlatform()", () => {
  196. it("returns models available on Claude", () => {
  197. const models = getModelsForPlatform("claude");
  198. expect(models.length).toBeGreaterThan(0);
  199. expect(models.every((m) => m.platformIds.claude !== undefined)).toBe(true);
  200. });
  201. it("returns models available on Cursor", () => {
  202. const models = getModelsForPlatform("cursor");
  203. expect(models.length).toBeGreaterThan(0);
  204. });
  205. it("returns models available on Windsurf", () => {
  206. const models = getModelsForPlatform("windsurf");
  207. expect(models.length).toBeGreaterThan(0);
  208. });
  209. });
  210. // ==========================================================================
  211. // isModelAvailable
  212. // ==========================================================================
  213. describe("isModelAvailable()", () => {
  214. it("returns true for available models", () => {
  215. expect(isModelAvailable("claude-sonnet-4", "claude")).toBe(true);
  216. expect(isModelAvailable("gpt-4", "cursor")).toBe(true);
  217. });
  218. it("returns false for unavailable models", () => {
  219. expect(isModelAvailable("unknown-model", "claude")).toBe(false);
  220. });
  221. it("returns false for models not on specific platform", () => {
  222. // GPT models are not available on Claude platform
  223. expect(isModelAvailable("gpt-4", "claude")).toBe(false);
  224. });
  225. });
  226. // ==========================================================================
  227. // getDefaultModel
  228. // ==========================================================================
  229. describe("getDefaultModel()", () => {
  230. it("returns Claude format default for Claude", () => {
  231. const model = getDefaultModel("claude");
  232. expect(model).toContain("claude");
  233. expect(model).toMatch(/\d{8}$/); // Date suffix
  234. });
  235. it("returns Cursor format default for Cursor", () => {
  236. const model = getDefaultModel("cursor");
  237. expect(model).toContain("claude");
  238. });
  239. it("returns Windsurf format default for Windsurf", () => {
  240. const model = getDefaultModel("windsurf");
  241. expect(model).toContain("claude");
  242. });
  243. });
  244. });