gitlab-duo.test.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import { test, expect, mock } from "bun:test"
  2. import path from "path"
  3. // === Mocks ===
  4. // These mocks prevent real package installations during tests
  5. mock.module("../../src/bun/index", () => ({
  6. BunProc: {
  7. install: async (pkg: string, _version?: string) => {
  8. // Return package name without version for mocking
  9. const lastAtIndex = pkg.lastIndexOf("@")
  10. return lastAtIndex > 0 ? pkg.substring(0, lastAtIndex) : pkg
  11. },
  12. run: async () => {
  13. throw new Error("BunProc.run should not be called in tests")
  14. },
  15. which: () => process.execPath,
  16. InstallFailedError: class extends Error {},
  17. },
  18. }))
  19. const mockPlugin = () => ({})
  20. mock.module("opencode-copilot-auth", () => ({ default: mockPlugin }))
  21. mock.module("opencode-anthropic-auth", () => ({ default: mockPlugin }))
  22. mock.module("@gitlab/opencode-gitlab-auth", () => ({ default: mockPlugin }))
  23. // Import after mocks are set up
  24. const { tmpdir } = await import("../fixture/fixture")
  25. const { Instance } = await import("../../src/project/instance")
  26. const { Provider } = await import("../../src/provider/provider")
  27. const { Env } = await import("../../src/env")
  28. const { Global } = await import("../../src/global")
  29. test("GitLab Duo: loads provider with API key from environment", async () => {
  30. await using tmp = await tmpdir({
  31. init: async (dir) => {
  32. await Bun.write(
  33. path.join(dir, "opencode.json"),
  34. JSON.stringify({
  35. $schema: "https://opencode.ai/config.json",
  36. }),
  37. )
  38. },
  39. })
  40. await Instance.provide({
  41. directory: tmp.path,
  42. init: async () => {
  43. Env.set("GITLAB_TOKEN", "test-gitlab-token")
  44. },
  45. fn: async () => {
  46. const providers = await Provider.list()
  47. expect(providers["gitlab"]).toBeDefined()
  48. expect(providers["gitlab"].key).toBe("test-gitlab-token")
  49. },
  50. })
  51. })
  52. test("GitLab Duo: config instanceUrl option sets baseURL", async () => {
  53. await using tmp = await tmpdir({
  54. init: async (dir) => {
  55. await Bun.write(
  56. path.join(dir, "opencode.json"),
  57. JSON.stringify({
  58. $schema: "https://opencode.ai/config.json",
  59. provider: {
  60. gitlab: {
  61. options: {
  62. instanceUrl: "https://gitlab.example.com",
  63. },
  64. },
  65. },
  66. }),
  67. )
  68. },
  69. })
  70. await Instance.provide({
  71. directory: tmp.path,
  72. init: async () => {
  73. Env.set("GITLAB_TOKEN", "test-token")
  74. Env.set("GITLAB_INSTANCE_URL", "https://gitlab.example.com")
  75. },
  76. fn: async () => {
  77. const providers = await Provider.list()
  78. expect(providers["gitlab"]).toBeDefined()
  79. expect(providers["gitlab"].options?.instanceUrl).toBe("https://gitlab.example.com")
  80. },
  81. })
  82. })
  83. test("GitLab Duo: loads with OAuth token from auth.json", async () => {
  84. await using tmp = await tmpdir({
  85. init: async (dir) => {
  86. await Bun.write(
  87. path.join(dir, "opencode.json"),
  88. JSON.stringify({
  89. $schema: "https://opencode.ai/config.json",
  90. }),
  91. )
  92. },
  93. })
  94. const authPath = path.join(Global.Path.data, "auth.json")
  95. await Bun.write(
  96. authPath,
  97. JSON.stringify({
  98. gitlab: {
  99. type: "oauth",
  100. access: "test-access-token",
  101. refresh: "test-refresh-token",
  102. expires: Date.now() + 3600000,
  103. },
  104. }),
  105. )
  106. await Instance.provide({
  107. directory: tmp.path,
  108. init: async () => {
  109. Env.set("GITLAB_TOKEN", "")
  110. },
  111. fn: async () => {
  112. const providers = await Provider.list()
  113. expect(providers["gitlab"]).toBeDefined()
  114. },
  115. })
  116. })
  117. test("GitLab Duo: loads with Personal Access Token from auth.json", async () => {
  118. await using tmp = await tmpdir({
  119. init: async (dir) => {
  120. await Bun.write(
  121. path.join(dir, "opencode.json"),
  122. JSON.stringify({
  123. $schema: "https://opencode.ai/config.json",
  124. }),
  125. )
  126. },
  127. })
  128. const authPath2 = path.join(Global.Path.data, "auth.json")
  129. await Bun.write(
  130. authPath2,
  131. JSON.stringify({
  132. gitlab: {
  133. type: "api",
  134. key: "glpat-test-pat-token",
  135. },
  136. }),
  137. )
  138. await Instance.provide({
  139. directory: tmp.path,
  140. init: async () => {
  141. Env.set("GITLAB_TOKEN", "")
  142. },
  143. fn: async () => {
  144. const providers = await Provider.list()
  145. expect(providers["gitlab"]).toBeDefined()
  146. expect(providers["gitlab"].key).toBe("glpat-test-pat-token")
  147. },
  148. })
  149. })
  150. test("GitLab Duo: supports self-hosted instance configuration", async () => {
  151. await using tmp = await tmpdir({
  152. init: async (dir) => {
  153. await Bun.write(
  154. path.join(dir, "opencode.json"),
  155. JSON.stringify({
  156. $schema: "https://opencode.ai/config.json",
  157. provider: {
  158. gitlab: {
  159. options: {
  160. instanceUrl: "https://gitlab.company.internal",
  161. apiKey: "glpat-internal-token",
  162. },
  163. },
  164. },
  165. }),
  166. )
  167. },
  168. })
  169. await Instance.provide({
  170. directory: tmp.path,
  171. init: async () => {
  172. Env.set("GITLAB_INSTANCE_URL", "https://gitlab.company.internal")
  173. },
  174. fn: async () => {
  175. const providers = await Provider.list()
  176. expect(providers["gitlab"]).toBeDefined()
  177. expect(providers["gitlab"].options?.instanceUrl).toBe("https://gitlab.company.internal")
  178. },
  179. })
  180. })
  181. test("GitLab Duo: config apiKey takes precedence over environment variable", async () => {
  182. await using tmp = await tmpdir({
  183. init: async (dir) => {
  184. await Bun.write(
  185. path.join(dir, "opencode.json"),
  186. JSON.stringify({
  187. $schema: "https://opencode.ai/config.json",
  188. provider: {
  189. gitlab: {
  190. options: {
  191. apiKey: "config-token",
  192. },
  193. },
  194. },
  195. }),
  196. )
  197. },
  198. })
  199. await Instance.provide({
  200. directory: tmp.path,
  201. init: async () => {
  202. Env.set("GITLAB_TOKEN", "env-token")
  203. },
  204. fn: async () => {
  205. const providers = await Provider.list()
  206. expect(providers["gitlab"]).toBeDefined()
  207. },
  208. })
  209. })
  210. test("GitLab Duo: supports feature flags configuration", async () => {
  211. await using tmp = await tmpdir({
  212. init: async (dir) => {
  213. await Bun.write(
  214. path.join(dir, "opencode.json"),
  215. JSON.stringify({
  216. $schema: "https://opencode.ai/config.json",
  217. provider: {
  218. gitlab: {
  219. options: {
  220. featureFlags: {
  221. duo_agent_platform_agentic_chat: true,
  222. duo_agent_platform: true,
  223. },
  224. },
  225. },
  226. },
  227. }),
  228. )
  229. },
  230. })
  231. await Instance.provide({
  232. directory: tmp.path,
  233. init: async () => {
  234. Env.set("GITLAB_TOKEN", "test-token")
  235. },
  236. fn: async () => {
  237. const providers = await Provider.list()
  238. expect(providers["gitlab"]).toBeDefined()
  239. expect(providers["gitlab"].options?.featureFlags).toBeDefined()
  240. expect(providers["gitlab"].options?.featureFlags?.duo_agent_platform_agentic_chat).toBe(true)
  241. },
  242. })
  243. })
  244. test("GitLab Duo: has multiple agentic chat models available", async () => {
  245. await using tmp = await tmpdir({
  246. init: async (dir) => {
  247. await Bun.write(
  248. path.join(dir, "opencode.json"),
  249. JSON.stringify({
  250. $schema: "https://opencode.ai/config.json",
  251. }),
  252. )
  253. },
  254. })
  255. await Instance.provide({
  256. directory: tmp.path,
  257. init: async () => {
  258. Env.set("GITLAB_TOKEN", "test-token")
  259. },
  260. fn: async () => {
  261. const providers = await Provider.list()
  262. expect(providers["gitlab"]).toBeDefined()
  263. const models = Object.keys(providers["gitlab"].models)
  264. expect(models.length).toBeGreaterThan(0)
  265. expect(models).toContain("duo-chat-haiku-4-5")
  266. expect(models).toContain("duo-chat-sonnet-4-5")
  267. expect(models).toContain("duo-chat-opus-4-5")
  268. },
  269. })
  270. })