Skip to content

Commit ee4437f

Browse files
committed
core: add provider test coverage for upcoming refactor
Add comprehensive test suite for Provider module to ensure safe refactoring of provider internals. Tests cover: - Provider loading from env vars and config - Provider filtering (disabled_providers, enabled_providers) - Model whitelist/blacklist - Model aliasing and custom providers - getModel, getProvider, closest, defaultModel functions Also adds Env module for instance-scoped environment variable access, enabling isolated test environments without global state pollution.
1 parent 7a4aa68 commit ee4437f

File tree

4 files changed

+1805
-11
lines changed

4 files changed

+1805
-11
lines changed

packages/opencode/src/env/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Instance } from "../project/instance"
2+
3+
export namespace Env {
4+
const state = Instance.state(() => {
5+
return { ...process.env } as Record<string, string | undefined>
6+
})
7+
8+
export function get(key: string) {
9+
const env = state()
10+
return env[key]
11+
}
12+
13+
export function all() {
14+
return state()
15+
}
16+
17+
export function set(key: string, value: string) {
18+
const env = state()
19+
env[key] = value
20+
}
21+
22+
export function remove(key: string) {
23+
const env = state()
24+
delete env[key]
25+
}
26+
}

packages/opencode/src/provider/provider.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Plugin } from "../plugin"
99
import { ModelsDev } from "./models"
1010
import { NamedError } from "@opencode-ai/util/error"
1111
import { Auth } from "../auth"
12+
import { Env } from "../env"
1213
import { Instance } from "../project/instance"
1314
import { Flag } from "../flag/flag"
1415
import { iife } from "@/util/iife"
@@ -64,7 +65,8 @@ export namespace Provider {
6465
},
6566
async opencode(input) {
6667
const hasKey = await (async () => {
67-
if (input.env.some((item) => process.env[item])) return true
68+
const env = Env.all()
69+
if (input.env.some((item) => env[item])) return true
6870
if (await Auth.get(input.id)) return true
6971
return false
7072
})()
@@ -128,7 +130,7 @@ export namespace Provider {
128130
}
129131
},
130132
"azure-cognitive-services": async () => {
131-
const resourceName = process.env["AZURE_COGNITIVE_SERVICES_RESOURCE_NAME"]
133+
const resourceName = Env.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME")
132134
return {
133135
autoload: false,
134136
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
@@ -144,10 +146,15 @@ export namespace Provider {
144146
}
145147
},
146148
"amazon-bedrock": async () => {
147-
if (!process.env["AWS_PROFILE"] && !process.env["AWS_ACCESS_KEY_ID"] && !process.env["AWS_BEARER_TOKEN_BEDROCK"])
148-
return { autoload: false }
149+
const [awsProfile, awsAccessKeyId, awsBearerToken, awsRegion] = await Promise.all([
150+
Env.get("AWS_PROFILE"),
151+
Env.get("AWS_ACCESS_KEY_ID"),
152+
Env.get("AWS_BEARER_TOKEN_BEDROCK"),
153+
Env.get("AWS_REGION"),
154+
])
155+
if (!awsProfile && !awsAccessKeyId && !awsBearerToken) return { autoload: false }
149156

150-
const region = process.env["AWS_REGION"] ?? "us-east-1"
157+
const region = awsRegion ?? "us-east-1"
151158

152159
const { fromNodeProviderChain } = await import(await BunProc.install("@aws-sdk/credential-providers"))
153160
return {
@@ -246,8 +253,8 @@ export namespace Provider {
246253
}
247254
},
248255
"google-vertex": async () => {
249-
const project = process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCP_PROJECT"] ?? process.env["GCLOUD_PROJECT"]
250-
const location = process.env["GOOGLE_CLOUD_LOCATION"] ?? process.env["VERTEX_LOCATION"] ?? "us-east5"
256+
const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT")
257+
const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-east5"
251258
const autoload = Boolean(project)
252259
if (!autoload) return { autoload: false }
253260
return {
@@ -263,8 +270,8 @@ export namespace Provider {
263270
}
264271
},
265272
"google-vertex-anthropic": async () => {
266-
const project = process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCP_PROJECT"] ?? process.env["GCLOUD_PROJECT"]
267-
const location = process.env["GOOGLE_CLOUD_LOCATION"] ?? process.env["VERTEX_LOCATION"] ?? "global"
273+
const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT")
274+
const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "global"
268275
const autoload = Boolean(project)
269276
if (!autoload) return { autoload: false }
270277
return {
@@ -435,9 +442,10 @@ export namespace Provider {
435442
}
436443

437444
// load env
445+
const env = Env.all()
438446
for (const [providerID, provider] of Object.entries(database)) {
439447
if (disabled.has(providerID)) continue
440-
const apiKey = provider.env.map((item) => process.env[item]).at(0)
448+
const apiKey = provider.env.map((item) => env[item]).find(Boolean)
441449
if (!apiKey) continue
442450
mergeProvider(
443451
providerID,

packages/opencode/test/preload.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,35 @@
1-
import { Log } from "../src/util/log"
1+
// IMPORTANT: Set env vars BEFORE any imports from src/ directory
2+
// xdg-basedir reads env vars at import time, so we must set these first
3+
import os from "os"
4+
import path from "path"
5+
6+
const testDataDir = path.join(os.tmpdir(), "opencode-test-data-" + process.pid)
7+
process.env["XDG_DATA_HOME"] = testDataDir
8+
process.env["XDG_CACHE_HOME"] = path.join(testDataDir, "cache")
9+
process.env["XDG_CONFIG_HOME"] = path.join(testDataDir, "config")
10+
process.env["XDG_STATE_HOME"] = path.join(testDataDir, "state")
11+
12+
// Clear provider env vars to ensure clean test state
13+
delete process.env["ANTHROPIC_API_KEY"]
14+
delete process.env["OPENAI_API_KEY"]
15+
delete process.env["GOOGLE_API_KEY"]
16+
delete process.env["GOOGLE_GENERATIVE_AI_API_KEY"]
17+
delete process.env["AZURE_OPENAI_API_KEY"]
18+
delete process.env["AWS_ACCESS_KEY_ID"]
19+
delete process.env["AWS_PROFILE"]
20+
delete process.env["OPENROUTER_API_KEY"]
21+
delete process.env["GROQ_API_KEY"]
22+
delete process.env["MISTRAL_API_KEY"]
23+
delete process.env["PERPLEXITY_API_KEY"]
24+
delete process.env["TOGETHER_API_KEY"]
25+
delete process.env["XAI_API_KEY"]
26+
delete process.env["DEEPSEEK_API_KEY"]
27+
delete process.env["FIREWORKS_API_KEY"]
28+
delete process.env["CEREBRAS_API_KEY"]
29+
delete process.env["SAMBANOVA_API_KEY"]
30+
31+
// Now safe to import from src/
32+
const { Log } = await import("../src/util/log")
233

334
Log.init({
435
print: false,

0 commit comments

Comments
 (0)