Skip to content

Commit e362385

Browse files
committed
fix .env file issue
1 parent d0edb90 commit e362385

2 files changed

Lines changed: 149 additions & 7 deletions

File tree

src/features/terminal/terminalEnvVarInjector.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ export class TerminalEnvVarInjector implements Disposable {
149149
traceVerbose(
150150
`TerminalEnvVarInjector: Env file injection disabled for workspace: ${workspaceUri.fsPath}`,
151151
);
152+
// Clear any previously set variables when injection is disabled
153+
envVarScope.clear();
152154
return;
153155
}
154156

@@ -165,14 +167,18 @@ export class TerminalEnvVarInjector implements Disposable {
165167
traceVerbose(
166168
`TerminalEnvVarInjector: No .env file found for workspace: ${workspaceUri.fsPath}, not injecting environment variables.`,
167169
);
168-
return; // No .env file to inject
170+
// Clear any previously set variables when no .env file exists
171+
envVarScope.clear();
172+
return;
169173
}
170174

175+
// Clear all previously set variables before re-injecting.
176+
// This ensures that when variables are commented out or removed from .env,
177+
// they are properly removed from the terminal environment.
178+
envVarScope.clear();
179+
171180
for (const [key, value] of Object.entries(envVars)) {
172-
if (value === undefined) {
173-
// Remove the environment variable if the value is undefined
174-
envVarScope.delete(key);
175-
} else {
181+
if (value !== undefined) {
176182
envVarScope.replace(key, value);
177183
}
178184
}

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

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
import * as sinon from 'sinon';
55
import * as typeMoq from 'typemoq';
6-
import { GlobalEnvironmentVariableCollection, workspace } from 'vscode';
6+
import { GlobalEnvironmentVariableCollection, Uri, workspace, WorkspaceFolder } from 'vscode';
7+
import * as workspaceApis from '../../common/workspace.apis';
78
import { EnvVarManager } from '../../features/execution/envVariableManager';
89
import { TerminalEnvVarInjector } from '../../features/terminal/terminalEnvVarInjector';
910

@@ -55,8 +56,14 @@ suite('TerminalEnvVarInjector Basic Tests', () => {
5556
({
5657
dispose: () => {},
5758
// eslint-disable-next-line @typescript-eslint/no-explicit-any
58-
} as any),
59+
}) as any,
5960
);
61+
// Mock workspace.onDidChangeConfiguration to return a proper disposable
62+
Object.defineProperty(workspace, 'onDidChangeConfiguration', {
63+
value: () => ({ dispose: () => {} }),
64+
configurable: true,
65+
writable: true,
66+
});
6067
});
6168

6269
teardown(() => {
@@ -102,3 +109,132 @@ suite('TerminalEnvVarInjector Basic Tests', () => {
102109
sinon.assert.match(eventHandlerRegistered, true);
103110
});
104111
});
112+
113+
/**
114+
* Tests for variable clearing: Ensure that when .env file variables are commented out or removed,
115+
* they are properly removed from the terminal environment.
116+
*
117+
* These tests verify the clear() behavior when useEnvFile setting is disabled.
118+
* Tests for file-existence scenarios are integration-level and not covered here.
119+
*/
120+
suite('TerminalEnvVarInjector - Variable Clearing', () => {
121+
let envVarCollection: typeMoq.IMock<GlobalEnvironmentVariableCollection>;
122+
let injector: TerminalEnvVarInjector;
123+
let mockScopedCollection: MockScopedCollection;
124+
let mockGetConfiguration: sinon.SinonStub;
125+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
126+
let workspaceFoldersStub: any;
127+
let mockWorkspaceFolder: WorkspaceFolder;
128+
let mockEnvVarManager: {
129+
onDidChangeEnvironmentVariables: sinon.SinonStub;
130+
getEnvironmentVariables: sinon.SinonStub;
131+
};
132+
133+
interface MockWorkspaceConfig {
134+
get: sinon.SinonStub;
135+
}
136+
137+
setup(() => {
138+
envVarCollection = typeMoq.Mock.ofType<GlobalEnvironmentVariableCollection>();
139+
140+
// Create mock EnvVarManager using sinon stubs
141+
mockEnvVarManager = {
142+
onDidChangeEnvironmentVariables: sinon.stub().returns({ dispose: () => {} }),
143+
getEnvironmentVariables: sinon.stub().resolves({}),
144+
};
145+
146+
// Create a mock workspace folder
147+
mockWorkspaceFolder = {
148+
uri: Uri.file('/test/workspace'),
149+
name: 'test-workspace',
150+
index: 0,
151+
};
152+
153+
// Mock workspace.workspaceFolders property
154+
workspaceFoldersStub = [mockWorkspaceFolder];
155+
Object.defineProperty(workspace, 'workspaceFolders', {
156+
get: () => workspaceFoldersStub,
157+
configurable: true,
158+
});
159+
160+
// Setup scoped collection mock
161+
mockScopedCollection = {
162+
clear: sinon.stub(),
163+
replace: sinon.stub(),
164+
delete: sinon.stub(),
165+
};
166+
167+
// Setup environment variable collection to return scoped collection
168+
envVarCollection
169+
.setup((x) => x.getScoped(typeMoq.It.isAny()))
170+
.returns(
171+
() => mockScopedCollection as unknown as ReturnType<GlobalEnvironmentVariableCollection['getScoped']>,
172+
);
173+
envVarCollection.setup((x) => x.clear()).returns(() => {});
174+
175+
// Mock getConfiguration
176+
mockGetConfiguration = sinon.stub(workspaceApis, 'getConfiguration');
177+
178+
// Mock workspace.onDidChangeConfiguration to return a proper disposable
179+
Object.defineProperty(workspace, 'onDidChangeConfiguration', {
180+
value: () => ({ dispose: () => {} }),
181+
configurable: true,
182+
writable: true,
183+
});
184+
});
185+
186+
teardown(() => {
187+
sinon.restore();
188+
injector?.dispose();
189+
});
190+
191+
test('should call clear() when useEnvFile setting is disabled', async () => {
192+
// Arrange - mock configuration with useEnvFile disabled
193+
const mockConfig: MockWorkspaceConfig = {
194+
get: sinon.stub(),
195+
};
196+
mockConfig.get.withArgs('terminal.useEnvFile', false).returns(false);
197+
mockConfig.get.withArgs('envFile').returns(undefined);
198+
mockGetConfiguration.returns(mockConfig);
199+
200+
// Mock getEnvironmentVariables
201+
mockEnvVarManager.getEnvironmentVariables.resolves({ TEST_VAR: 'value' });
202+
203+
// Act
204+
injector = new TerminalEnvVarInjector(envVarCollection.object, mockEnvVarManager as unknown as EnvVarManager);
205+
206+
// Wait for async initialization
207+
await new Promise((resolve) => setTimeout(resolve, 150));
208+
209+
// Assert - clear() should be called, but replace() should NOT be called
210+
sinon.assert.called(mockScopedCollection.clear);
211+
sinon.assert.notCalled(mockScopedCollection.replace);
212+
});
213+
214+
test('should not inject variables when useEnvFile is disabled even if env vars exist', async () => {
215+
// Arrange - mock configuration with useEnvFile disabled
216+
const mockConfig: MockWorkspaceConfig = {
217+
get: sinon.stub(),
218+
};
219+
mockConfig.get.withArgs('terminal.useEnvFile', false).returns(false);
220+
mockConfig.get.withArgs('envFile').returns('.env');
221+
mockGetConfiguration.returns(mockConfig);
222+
223+
// Mock getEnvironmentVariables to return multiple variables
224+
mockEnvVarManager.getEnvironmentVariables.resolves({
225+
API_KEY: 'secret123',
226+
DATABASE_URL: 'postgres://localhost/db',
227+
DEBUG: 'true',
228+
});
229+
230+
// Act
231+
injector = new TerminalEnvVarInjector(envVarCollection.object, mockEnvVarManager as unknown as EnvVarManager);
232+
233+
// Wait for async initialization
234+
await new Promise((resolve) => setTimeout(resolve, 150));
235+
236+
// Assert - clear() should be called to remove any previous variables, but replace() should NOT be called
237+
sinon.assert.called(mockScopedCollection.clear);
238+
sinon.assert.notCalled(mockScopedCollection.replace);
239+
});
240+
});

0 commit comments

Comments
 (0)