Skip to content

Commit 695caa2

Browse files
tomassurinclaude
andcommitted
Refactor getEnvironment to pure function with DI
Remove module-level singleton cache and resetEnvironmentForTests() escape hatch. getEnvironment() now accepts rawEnv parameter (defaults to process.env) and runServer() takes Environment as a parameter. Tests pass env directly instead of mutating process.env. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b234fd5 commit 695caa2

3 files changed

Lines changed: 21 additions & 61 deletions

File tree

src/index.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,7 @@ type RunServerResult = {
224224
close: () => Promise<void>
225225
}
226226

227-
export async function runServer(): Promise<RunServerResult> {
228-
let environment: Environment
229-
try {
230-
environment = getEnvironment()
231-
} catch (e) {
232-
throw new Error(`Invalid environment configuration: ${e instanceof Error ? e.message : e}`)
233-
}
227+
export async function runServer(environment: Environment): Promise<RunServerResult> {
234228
const { sandboxDir } = await parseCommandLineArgs()
235229

236230
await prepareSandbox(sandboxDir)
@@ -288,7 +282,15 @@ function isMainModule() {
288282
if (isMainModule()) {
289283
let activeServer: RunServerResult | undefined
290284

291-
runServer()
285+
let environment: Environment
286+
try {
287+
environment = getEnvironment()
288+
} catch (e) {
289+
console.error(`Invalid environment configuration: ${e instanceof Error ? e.message : e}`)
290+
process.exit(1)
291+
}
292+
293+
runServer(environment)
292294
.then((result) => {
293295
activeServer = result
294296
})

src/utils/environment.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ const RawEnvironmentSchema = z.object({
2121
CLIENT_ID: z.string().optional(),
2222
})
2323

24-
let cachedEnvironment: Environment | undefined
25-
26-
function parseEnvironment(rawEnv: NodeJS.ProcessEnv): Environment {
24+
export function getEnvironment(rawEnv: NodeJS.ProcessEnv = process.env): Environment {
2725
const raw = RawEnvironmentSchema.parse(rawEnv)
2826

2927
return {
@@ -33,15 +31,3 @@ function parseEnvironment(rawEnv: NodeJS.ProcessEnv): Environment {
3331
clientId: raw.CLIENT_ID,
3432
}
3533
}
36-
37-
export function getEnvironment(): Environment {
38-
if (!cachedEnvironment) {
39-
cachedEnvironment = parseEnvironment(process.env)
40-
}
41-
42-
return cachedEnvironment
43-
}
44-
45-
export function resetEnvironmentForTests() {
46-
cachedEnvironment = undefined
47-
}

tests/environment.test.ts

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,49 @@
1-
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
2-
import { getEnvironment, resetEnvironmentForTests } from '../src/utils/environment.js'
1+
import { describe, expect, it } from 'vitest'
2+
import { getEnvironment } from '../src/utils/environment.js'
33

44
describe('environment', () => {
5-
const originalEnv = process.env
6-
7-
beforeEach(() => {
8-
process.env = { ...originalEnv }
9-
resetEnvironmentForTests()
10-
})
11-
12-
afterEach(() => {
13-
process.env = originalEnv
14-
resetEnvironmentForTests()
15-
})
16-
175
it('parses default configuration with API key', () => {
18-
process.env.NUTRIENT_DWS_API_KEY = 'dws-key'
19-
20-
const environment = getEnvironment()
6+
const environment = getEnvironment({ NUTRIENT_DWS_API_KEY: 'dws-key' })
217

228
expect(environment.nutrientApiKey).toBe('dws-key')
239
expect(environment.dwsApiBaseUrl).toBe('https://api.nutrient.io')
2410
expect(environment.authServerUrl).toBe('https://api.nutrient.io')
2511
})
2612

2713
it('allows overriding DWS API base URL', () => {
28-
process.env.DWS_API_BASE_URL = 'http://localhost:4000'
29-
30-
const environment = getEnvironment()
14+
const environment = getEnvironment({ DWS_API_BASE_URL: 'http://localhost:4000' })
3115

3216
expect(environment.dwsApiBaseUrl).toBe('http://localhost:4000')
3317
})
3418

3519
it('allows overriding auth server URL', () => {
36-
process.env.AUTH_SERVER_URL = 'http://localhost:4000'
37-
38-
const environment = getEnvironment()
20+
const environment = getEnvironment({ AUTH_SERVER_URL: 'http://localhost:4000' })
3921

4022
expect(environment.authServerUrl).toBe('http://localhost:4000')
4123
})
4224

4325
it('allows setting client ID', () => {
44-
process.env.CLIENT_ID = 'my-client'
45-
46-
const environment = getEnvironment()
26+
const environment = getEnvironment({ CLIENT_ID: 'my-client' })
4727

4828
expect(environment.clientId).toBe('my-client')
4929
})
5030

5131
it('works without API key (OAuth mode)', () => {
52-
delete process.env.NUTRIENT_DWS_API_KEY
53-
54-
const environment = getEnvironment()
32+
const environment = getEnvironment({})
5533

5634
expect(environment.nutrientApiKey).toBeUndefined()
5735
})
5836

5937
it('throws when DWS_API_BASE_URL is not a valid URL', () => {
60-
process.env.DWS_API_BASE_URL = 'not-a-url'
61-
62-
expect(() => getEnvironment()).toThrow()
38+
expect(() => getEnvironment({ DWS_API_BASE_URL: 'not-a-url' })).toThrow()
6339
})
6440

6541
it('rejects non-HTTPS AUTH_SERVER_URL', () => {
66-
process.env.AUTH_SERVER_URL = 'http://example.com'
67-
68-
expect(() => getEnvironment()).toThrow(/https/)
42+
expect(() => getEnvironment({ AUTH_SERVER_URL: 'http://example.com' })).toThrow(/https/)
6943
})
7044

7145
it('allows http://localhost AUTH_SERVER_URL for local development', () => {
72-
process.env.AUTH_SERVER_URL = 'http://localhost:4000'
73-
74-
const environment = getEnvironment()
46+
const environment = getEnvironment({ AUTH_SERVER_URL: 'http://localhost:4000' })
7547

7648
expect(environment.authServerUrl).toBe('http://localhost:4000')
7749
})

0 commit comments

Comments
 (0)