Skip to content

Commit f87238f

Browse files
committed
Fix provider tests with local models fixture
1 parent e08d1ed commit f87238f

3 files changed

Lines changed: 252 additions & 50 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
{
2+
"anthropic": {
3+
"id": "anthropic",
4+
"name": "Anthropic",
5+
"env": ["ANTHROPIC_API_KEY"],
6+
"npm": "@ai-sdk/anthropic",
7+
"api": "https://api.anthropic.com",
8+
"models": {
9+
"claude-sonnet-4-20250514": {
10+
"id": "claude-sonnet-4-20250514",
11+
"name": "Claude Sonnet 4",
12+
"release_date": "2025-05-14",
13+
"attachment": true,
14+
"reasoning": true,
15+
"temperature": true,
16+
"tool_call": true,
17+
"limit": { "context": 200000, "output": 8192 },
18+
"options": {}
19+
},
20+
"claude-opus-4-20250514": {
21+
"id": "claude-opus-4-20250514",
22+
"name": "Claude Opus 4",
23+
"release_date": "2025-05-14",
24+
"attachment": true,
25+
"reasoning": true,
26+
"temperature": true,
27+
"tool_call": true,
28+
"limit": { "context": 200000, "output": 8192 },
29+
"options": {}
30+
},
31+
"claude-haiku-4-5-20250514": {
32+
"id": "claude-haiku-4-5-20250514",
33+
"name": "Claude Haiku 4.5",
34+
"release_date": "2025-05-14",
35+
"attachment": true,
36+
"reasoning": false,
37+
"temperature": true,
38+
"tool_call": true,
39+
"limit": { "context": 200000, "output": 4096 },
40+
"options": {}
41+
}
42+
}
43+
},
44+
"openai": {
45+
"id": "openai",
46+
"name": "OpenAI",
47+
"env": ["OPENAI_API_KEY"],
48+
"npm": "@ai-sdk/openai",
49+
"api": "https://api.openai.com/v1",
50+
"models": {
51+
"gpt-5": {
52+
"id": "gpt-5",
53+
"name": "GPT-5",
54+
"release_date": "2025-11-20",
55+
"attachment": true,
56+
"reasoning": true,
57+
"temperature": true,
58+
"tool_call": true,
59+
"limit": { "context": 200000, "output": 8192 },
60+
"options": {}
61+
}
62+
}
63+
},
64+
"openrouter": {
65+
"id": "openrouter",
66+
"name": "OpenRouter",
67+
"env": ["OPENROUTER_API_KEY"],
68+
"npm": "@openrouter/ai-sdk-provider",
69+
"api": "https://openrouter.ai/api/v1",
70+
"models": {
71+
"openai/gpt-4": {
72+
"id": "openai/gpt-4",
73+
"name": "GPT-4 (OpenRouter)",
74+
"release_date": "2024-05-13",
75+
"attachment": true,
76+
"reasoning": false,
77+
"temperature": true,
78+
"tool_call": true,
79+
"limit": { "context": 128000, "output": 4096 },
80+
"options": {}
81+
}
82+
}
83+
},
84+
"amazon-bedrock": {
85+
"id": "amazon-bedrock",
86+
"name": "Amazon Bedrock",
87+
"env": ["AWS_ACCESS_KEY_ID", "AWS_BEARER_TOKEN_BEDROCK", "AWS_PROFILE", "AWS_WEB_IDENTITY_TOKEN_FILE"],
88+
"npm": "@ai-sdk/amazon-bedrock",
89+
"models": {
90+
"amazon.nova-pro": {
91+
"id": "amazon.nova-pro",
92+
"name": "Amazon Nova Pro",
93+
"release_date": "2024-12-01",
94+
"attachment": false,
95+
"reasoning": false,
96+
"temperature": true,
97+
"tool_call": true,
98+
"limit": { "context": 200000, "output": 8192 },
99+
"options": {}
100+
}
101+
}
102+
},
103+
"gitlab": {
104+
"id": "gitlab",
105+
"name": "GitLab Duo",
106+
"env": ["GITLAB_TOKEN"],
107+
"npm": "@gitlab/gitlab-ai-provider",
108+
"models": {
109+
"duo-chat-haiku-4-5": {
110+
"id": "duo-chat-haiku-4-5",
111+
"name": "Duo Chat Haiku 4.5",
112+
"release_date": "2024-10-01",
113+
"attachment": false,
114+
"reasoning": false,
115+
"temperature": true,
116+
"tool_call": true,
117+
"limit": { "context": 8192, "output": 2048 },
118+
"options": {}
119+
},
120+
"duo-chat-sonnet-4-5": {
121+
"id": "duo-chat-sonnet-4-5",
122+
"name": "Duo Chat Sonnet 4.5",
123+
"release_date": "2024-10-01",
124+
"attachment": false,
125+
"reasoning": false,
126+
"temperature": true,
127+
"tool_call": true,
128+
"limit": { "context": 8192, "output": 2048 },
129+
"options": {}
130+
},
131+
"duo-chat-opus-4-5": {
132+
"id": "duo-chat-opus-4-5",
133+
"name": "Duo Chat Opus 4.5",
134+
"release_date": "2024-10-01",
135+
"attachment": false,
136+
"reasoning": false,
137+
"temperature": true,
138+
"tool_call": true,
139+
"limit": { "context": 8192, "output": 2048 },
140+
"options": {}
141+
}
142+
}
143+
}
144+
}

packages/opencode/test/preload.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import path from "path"
55
import fs from "fs/promises"
66
import fsSync from "fs"
77
import { afterAll } from "bun:test"
8-
const { Global } = await import("../src/global")
98

109
const dir = path.join(os.tmpdir(), "opencode-test-data-" + process.pid)
1110
await fs.mkdir(dir, { recursive: true })
@@ -27,15 +26,30 @@ process.env["XDG_STATE_HOME"] = path.join(dir, "state")
2726
// These plugins have dependencies that can fail to bundle in CI environments
2827
process.env["OPENCODE_DISABLE_DEFAULT_PLUGINS"] = "true"
2928

29+
// Ensure cache directory exists before Global import writes version file
30+
const cacheDir = path.join(process.env["XDG_CACHE_HOME"]!, "opencode")
31+
await fs.mkdir(cacheDir, { recursive: true })
32+
33+
// Now safe to import from src/
34+
const { Global } = await import("../src/global")
35+
3036
// Pre-fetch models.json so tests don't need the macro fallback
3137
// Also write the cache version file to prevent global/index.ts from clearing the cache
32-
const cacheDir = path.join(dir, "cache", "opencode")
33-
await fs.mkdir(cacheDir, { recursive: true })
3438
await fs.writeFile(path.join(cacheDir, "version"), "18")
35-
const url = Global.Path.modelsDevUrl
36-
const response = await fetch(`${url}/api.json`)
37-
if (response.ok) {
38-
await fs.writeFile(path.join(cacheDir, "models.json"), await response.text())
39+
const fixturePath = path.join(import.meta.dir, "fixture", "models.dev.json")
40+
let modelsJson: string | undefined
41+
try {
42+
modelsJson = await fs.readFile(fixturePath, "utf8")
43+
} catch {}
44+
if (!modelsJson) {
45+
const url = Global.Path.modelsDevUrl
46+
const response = await fetch(`${url}/api.json`).catch(() => undefined)
47+
if (response?.ok) {
48+
modelsJson = await response.text()
49+
}
50+
}
51+
if (modelsJson) {
52+
await fs.writeFile(path.join(cacheDir, "models.json"), modelsJson)
3953
}
4054
// Disable models.dev refresh to avoid race conditions during tests
4155
process.env["OPENCODE_DISABLE_MODELS_FETCH"] = "true"
@@ -61,7 +75,6 @@ delete process.env["FIREWORKS_API_KEY"]
6175
delete process.env["CEREBRAS_API_KEY"]
6276
delete process.env["SAMBANOVA_API_KEY"]
6377

64-
// Now safe to import from src/
6578
const { Log } = await import("../src/util/log")
6679

6780
Log.init({

packages/opencode/test/provider/gitlab-duo.test.ts

Lines changed: 87 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { test, expect, mock } from "bun:test"
22
import path from "path"
3+
import { unlink } from "fs/promises"
34

45
// === Mocks ===
56
// These mocks prevent real package installations during tests
@@ -100,28 +101,50 @@ test("GitLab Duo: loads with OAuth token from auth.json", async () => {
100101
})
101102

102103
const authPath = path.join(Global.Path.data, "auth.json")
103-
await Bun.write(
104-
authPath,
105-
JSON.stringify({
106-
gitlab: {
107-
type: "oauth",
108-
access: "test-access-token",
109-
refresh: "test-refresh-token",
110-
expires: Date.now() + 3600000,
111-
},
112-
}),
113-
)
114104

115-
await Instance.provide({
116-
directory: tmp.path,
117-
init: async () => {
118-
Env.set("GITLAB_TOKEN", "")
119-
},
120-
fn: async () => {
121-
const providers = await Provider.list()
122-
expect(providers["gitlab"]).toBeDefined()
123-
},
124-
})
105+
// Save original auth.json if it exists
106+
let originalAuth: string | undefined
107+
try {
108+
originalAuth = await Bun.file(authPath).text()
109+
} catch {
110+
// File doesn't exist, that's fine
111+
}
112+
113+
try {
114+
await Bun.write(
115+
authPath,
116+
JSON.stringify({
117+
gitlab: {
118+
type: "oauth",
119+
access: "test-access-token",
120+
refresh: "test-refresh-token",
121+
expires: Date.now() + 3600000,
122+
},
123+
}),
124+
)
125+
126+
await Instance.provide({
127+
directory: tmp.path,
128+
init: async () => {
129+
Env.set("GITLAB_TOKEN", "")
130+
},
131+
fn: async () => {
132+
const providers = await Provider.list()
133+
expect(providers["gitlab"]).toBeDefined()
134+
},
135+
})
136+
} finally {
137+
// Restore original or delete
138+
if (originalAuth !== undefined) {
139+
await Bun.write(authPath, originalAuth)
140+
} else {
141+
try {
142+
await unlink(authPath)
143+
} catch {
144+
// Ignore errors if file doesn't exist
145+
}
146+
}
147+
}
125148
})
126149

127150
test("GitLab Duo: loads with Personal Access Token from auth.json", async () => {
@@ -136,28 +159,50 @@ test("GitLab Duo: loads with Personal Access Token from auth.json", async () =>
136159
},
137160
})
138161

139-
const authPath2 = path.join(Global.Path.data, "auth.json")
140-
await Bun.write(
141-
authPath2,
142-
JSON.stringify({
143-
gitlab: {
144-
type: "api",
145-
key: "glpat-test-pat-token",
146-
},
147-
}),
148-
)
162+
const authPath = path.join(Global.Path.data, "auth.json")
149163

150-
await Instance.provide({
151-
directory: tmp.path,
152-
init: async () => {
153-
Env.set("GITLAB_TOKEN", "")
154-
},
155-
fn: async () => {
156-
const providers = await Provider.list()
157-
expect(providers["gitlab"]).toBeDefined()
158-
expect(providers["gitlab"].key).toBe("glpat-test-pat-token")
159-
},
160-
})
164+
// Save original auth.json if it exists
165+
let originalAuth: string | undefined
166+
try {
167+
originalAuth = await Bun.file(authPath).text()
168+
} catch {
169+
// File doesn't exist, that's fine
170+
}
171+
172+
try {
173+
await Bun.write(
174+
authPath,
175+
JSON.stringify({
176+
gitlab: {
177+
type: "api",
178+
key: "glpat-test-pat-token",
179+
},
180+
}),
181+
)
182+
183+
await Instance.provide({
184+
directory: tmp.path,
185+
init: async () => {
186+
Env.set("GITLAB_TOKEN", "")
187+
},
188+
fn: async () => {
189+
const providers = await Provider.list()
190+
expect(providers["gitlab"]).toBeDefined()
191+
expect(providers["gitlab"].key).toBe("glpat-test-pat-token")
192+
},
193+
})
194+
} finally {
195+
// Restore original or delete
196+
if (originalAuth !== undefined) {
197+
await Bun.write(authPath, originalAuth)
198+
} else {
199+
try {
200+
await unlink(authPath)
201+
} catch {
202+
// Ignore errors if file doesn't exist
203+
}
204+
}
205+
}
161206
})
162207

163208
test("GitLab Duo: supports self-hosted instance configuration", async () => {

0 commit comments

Comments
 (0)