Skip to content

Commit 1bef610

Browse files
committed
Add more tests for .env file
1 parent 7551eb5 commit 1bef610

File tree

2 files changed

+234
-104
lines changed

2 files changed

+234
-104
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as assert from 'assert';
5+
import * as sinon from 'sinon';
6+
import * as typeMoq from 'typemoq';
7+
import {
8+
Disposable,
9+
Event,
10+
GlobalEnvironmentVariableCollection,
11+
Uri,
12+
WorkspaceConfiguration,
13+
WorkspaceFolder,
14+
workspace,
15+
} from 'vscode';
16+
import * as workspaceApis from '../../common/workspace.apis';
17+
import { EnvVarManager } from '../../features/execution/envVariableManager';
18+
import { TerminalEnvVarInjector } from '../../features/terminal/terminalEnvVarInjector';
19+
20+
interface MockScopedCollection {
21+
clear: sinon.SinonStub;
22+
replace: sinon.SinonStub;
23+
delete: sinon.SinonStub;
24+
}
25+
26+
function createMockConfig(settings: { useEnvFile?: boolean; envFilePath?: string }): Partial<WorkspaceConfiguration> {
27+
return {
28+
get: <T>(key: string, defaultValue?: T): T | undefined => {
29+
if (key === 'terminal.useEnvFile') {
30+
return (settings.useEnvFile ?? false) as T;
31+
}
32+
if (key === 'envFile') {
33+
return settings.envFilePath as T;
34+
}
35+
return defaultValue;
36+
},
37+
};
38+
}
39+
40+
function createMockWorkspaceFolder(fsPath: string, name: string, index: number): WorkspaceFolder {
41+
return { uri: Uri.file(fsPath), name, index };
42+
}
43+
44+
function createMockEvent<T>(): Event<T> {
45+
return (_listener: (e: T) => void): Disposable => new Disposable(() => {});
46+
}
47+
48+
suite('TerminalEnvVarInjector', () => {
49+
let envVarCollection: typeMoq.IMock<GlobalEnvironmentVariableCollection>;
50+
let envVarManager: typeMoq.IMock<EnvVarManager>;
51+
let injector: TerminalEnvVarInjector;
52+
let mockScopedCollection: MockScopedCollection;
53+
let getConfigurationStub: sinon.SinonStub;
54+
let workspaceFoldersValue: readonly WorkspaceFolder[] | undefined;
55+
56+
const testWorkspacePath = '/test/workspace';
57+
const testWorkspaceFolder = createMockWorkspaceFolder(testWorkspacePath, 'test', 0);
58+
59+
setup(() => {
60+
envVarCollection = typeMoq.Mock.ofType<GlobalEnvironmentVariableCollection>();
61+
envVarManager = typeMoq.Mock.ofType<EnvVarManager>();
62+
63+
workspaceFoldersValue = [testWorkspaceFolder];
64+
Object.defineProperty(workspace, 'workspaceFolders', {
65+
get: () => workspaceFoldersValue,
66+
configurable: true,
67+
});
68+
69+
mockScopedCollection = {
70+
clear: sinon.stub(),
71+
replace: sinon.stub(),
72+
delete: sinon.stub(),
73+
};
74+
75+
envVarCollection
76+
.setup((x) => x.getScoped(typeMoq.It.isAny()))
77+
.returns(
78+
() => mockScopedCollection as unknown as ReturnType<GlobalEnvironmentVariableCollection['getScoped']>,
79+
);
80+
envVarCollection.setup((x) => x.clear()).returns(() => {});
81+
82+
envVarManager
83+
.setup((m) => m.onDidChangeEnvironmentVariables)
84+
.returns(() => createMockEvent());
85+
86+
getConfigurationStub = sinon.stub(workspaceApis, 'getConfiguration');
87+
getConfigurationStub.returns(createMockConfig({ useEnvFile: false }) as WorkspaceConfiguration);
88+
});
89+
90+
teardown(() => {
91+
sinon.restore();
92+
try {
93+
injector?.dispose();
94+
} catch {
95+
// Ignore disposal errors
96+
}
97+
});
98+
99+
suite('Basic functionality', () => {
100+
test('should initialize without errors', () => {
101+
injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object);
102+
sinon.assert.match(injector, sinon.match.object);
103+
});
104+
105+
test('should dispose cleanly', () => {
106+
injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object);
107+
injector.dispose();
108+
envVarCollection.verify((c) => c.clear(), typeMoq.Times.atLeastOnce());
109+
});
110+
111+
test('should register environment variable change event handler', () => {
112+
let eventHandlerRegistered = false;
113+
envVarManager.reset();
114+
envVarManager
115+
.setup((m) => m.onDidChangeEnvironmentVariables)
116+
.returns(() => {
117+
eventHandlerRegistered = true;
118+
return createMockEvent();
119+
});
120+
121+
injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object);
122+
sinon.assert.match(eventHandlerRegistered, true);
123+
});
124+
});
125+
126+
suite('useEnvFile=false (Issue #936)', () => {
127+
test('should NOT inject env vars when useEnvFile is false', async () => {
128+
getConfigurationStub.returns(createMockConfig({ useEnvFile: false }) as WorkspaceConfiguration);
129+
envVarManager
130+
.setup((m) => m.getEnvironmentVariables(typeMoq.It.isAny()))
131+
.returns(() => Promise.resolve({ TEST_VAR: 'test_value', API_KEY: 'secret123' }));
132+
133+
injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object);
134+
await new Promise((resolve) => setTimeout(resolve, 50));
135+
136+
assert.strictEqual(mockScopedCollection.replace.called, false);
137+
});
138+
139+
test('should NOT inject when useEnvFile is false even with python.envFile configured', async () => {
140+
getConfigurationStub.returns(
141+
createMockConfig({
142+
useEnvFile: false,
143+
envFilePath: '${workspaceFolder}/.env.local',
144+
}) as WorkspaceConfiguration,
145+
);
146+
envVarManager
147+
.setup((m) => m.getEnvironmentVariables(typeMoq.It.isAny()))
148+
.returns(() => Promise.resolve({ DATABASE_URL: 'postgres://localhost/db' }));
149+
150+
injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object);
151+
await new Promise((resolve) => setTimeout(resolve, 50));
152+
153+
assert.strictEqual(mockScopedCollection.replace.called, false);
154+
});
155+
156+
test('should NOT inject when useEnvFile is false with multiple workspace folders', async () => {
157+
const workspace1 = createMockWorkspaceFolder('/workspace1', 'workspace1', 0);
158+
const workspace2 = createMockWorkspaceFolder('/workspace2', 'workspace2', 1);
159+
workspaceFoldersValue = [workspace1, workspace2];
160+
161+
getConfigurationStub.returns(createMockConfig({ useEnvFile: false }) as WorkspaceConfiguration);
162+
envVarManager
163+
.setup((m) => m.getEnvironmentVariables(typeMoq.It.isAny()))
164+
.returns(() => Promise.resolve({ VAR1: 'value1' }));
165+
166+
injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object);
167+
await new Promise((resolve) => setTimeout(resolve, 100));
168+
169+
assert.strictEqual(mockScopedCollection.replace.called, false);
170+
});
171+
172+
test('should handle no workspace folders gracefully', async () => {
173+
workspaceFoldersValue = [];
174+
getConfigurationStub.returns(createMockConfig({ useEnvFile: false }) as WorkspaceConfiguration);
175+
envVarManager
176+
.setup((m) => m.getEnvironmentVariables(typeMoq.It.isAny()))
177+
.returns(() => Promise.resolve({ VAR: 'value' }));
178+
179+
injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object);
180+
await new Promise((resolve) => setTimeout(resolve, 50));
181+
182+
assert.strictEqual(mockScopedCollection.replace.called, false);
183+
});
184+
});
185+
186+
suite('python.envFile compatibility', () => {
187+
test('python.envFile has no effect when useEnvFile is false', async () => {
188+
getConfigurationStub.returns(
189+
createMockConfig({
190+
useEnvFile: false,
191+
envFilePath: '${workspaceFolder}/.env.production',
192+
}) as WorkspaceConfiguration,
193+
);
194+
envVarManager
195+
.setup((m) => m.getEnvironmentVariables(typeMoq.It.isAny()))
196+
.returns(() => Promise.resolve({ PRODUCTION_API_KEY: 'prod_key_123' }));
197+
198+
injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object);
199+
await new Promise((resolve) => setTimeout(resolve, 50));
200+
201+
assert.strictEqual(mockScopedCollection.replace.called, false);
202+
});
203+
204+
test('different envFile paths should not matter when useEnvFile is false', async () => {
205+
const pathConfigs = [undefined, '', '.env', '.env.local', '${workspaceFolder}/.env', '/absolute/path/.env'];
206+
207+
for (const envFilePath of pathConfigs) {
208+
mockScopedCollection.replace.resetHistory();
209+
getConfigurationStub.returns(
210+
createMockConfig({ useEnvFile: false, envFilePath }) as WorkspaceConfiguration,
211+
);
212+
213+
envVarManager.reset();
214+
envVarManager
215+
.setup((m) => m.onDidChangeEnvironmentVariables)
216+
.returns(() => createMockEvent());
217+
envVarManager
218+
.setup((m) => m.getEnvironmentVariables(typeMoq.It.isAny()))
219+
.returns(() => Promise.resolve({ VAR: 'value' }));
220+
221+
injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object);
222+
await new Promise((resolve) => setTimeout(resolve, 50));
223+
224+
assert.strictEqual(mockScopedCollection.replace.called, false, `Failed for envFilePath="${envFilePath}"`);
225+
226+
try {
227+
injector.dispose();
228+
} catch {
229+
// Ignore
230+
}
231+
}
232+
});
233+
});
234+
});

src/test/features/terminalEnvVarInjectorBasic.unit.test.ts

Lines changed: 0 additions & 104 deletions
This file was deleted.

0 commit comments

Comments
 (0)