Skip to content

Commit 142034f

Browse files
committed
test: restore environment after each case
1 parent c8fabdf commit 142034f

4 files changed

Lines changed: 119 additions & 0 deletions

File tree

__tests__/helpers/env-utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,36 @@ export interface EnvSnapshot {
88
[key: string]: string | undefined;
99
}
1010

11+
export type ProcessEnvSnapshot = Record<string, string>;
12+
13+
/**
14+
* Snapshot the complete process environment for per-test isolation.
15+
*/
16+
export function snapshotProcessEnv(): ProcessEnvSnapshot {
17+
const snapshot = Object.create(null) as ProcessEnvSnapshot;
18+
for (const [key, value] of Object.entries(process.env)) {
19+
if (value !== undefined) {
20+
snapshot[key] = value;
21+
}
22+
}
23+
return snapshot;
24+
}
25+
26+
/**
27+
* Restore the complete process environment, removing keys added after the snapshot.
28+
*/
29+
export function restoreProcessEnv(snapshot: ProcessEnvSnapshot): void {
30+
for (const key of Object.keys(process.env)) {
31+
if (!Object.hasOwn(snapshot, key)) {
32+
delete process.env[key];
33+
}
34+
}
35+
36+
for (const [key, value] of Object.entries(snapshot)) {
37+
process.env[key] = value;
38+
}
39+
}
40+
1141
/**
1242
* Save current environment variables
1343
*/

__tests__/helpers/test-utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* Shared utility functions for test suite
55
*/
66

7+
import { restoreProcessEnv, snapshotProcessEnv } from './env-utils.js';
8+
79
export interface TestResult {
810
passed: number;
911
failed: number;
@@ -25,6 +27,7 @@ export async function testFunction(
2527
results: TestResult
2628
): Promise<void> {
2729
console.log(`Testing ${name}...`);
30+
const environmentSnapshot = snapshotProcessEnv();
2831
try {
2932
const result = fn();
3033
if (result instanceof Promise) {
@@ -37,6 +40,8 @@ export async function testFunction(
3740
const errorMsg = `❌ ${name} failed: ${error.message}`;
3841
results.errors.push(errorMsg);
3942
console.log(errorMsg);
43+
} finally {
44+
restoreProcessEnv(environmentSnapshot);
4045
}
4146
}
4247

__tests__/run-all.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { TestResult } from './helpers/test-utils.js';
1010

1111
// Import all test suites
1212
import { runTests as runLoggingTests } from './unit/logging.test.js';
13+
import { runTests as runTestUtilsTests } from './unit/test-utils.test.js';
1314
import { runTests as runDiagnosticSanitizerTests } from './unit/diagnostic-sanitizer.test.js';
1415
import { runTests as runDiagnosticOutputTests } from './unit/diagnostic-output.test.js';
1516
import { runTests as runTypesTests } from './unit/types.test.js';
@@ -45,6 +46,7 @@ interface TestSuite {
4546
const testSuites: TestSuite[] = [
4647
// Unit Tests
4748
{ name: 'Logging', category: 'unit', run: runLoggingTests },
49+
{ name: 'Test Utilities', category: 'unit', run: runTestUtilsTests },
4850
{ name: 'Diagnostic Sanitizer', category: 'unit', run: runDiagnosticSanitizerTests },
4951
{ name: 'Diagnostic Output', category: 'unit', run: runDiagnosticOutputTests },
5052
{ name: 'Types', category: 'unit', run: runTypesTests },

__tests__/unit/test-utils.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env tsx
2+
3+
import { strict as assert } from 'node:assert';
4+
import { fileURLToPath } from 'node:url';
5+
import {
6+
createTestResults,
7+
printTestSummary,
8+
testFunction,
9+
} from '../helpers/test-utils.js';
10+
import { snapshotProcessEnv } from '../helpers/env-utils.js';
11+
12+
const results = createTestResults();
13+
const isolationKey = 'MCP_SEARXNG_TEST_ENV_ISOLATION';
14+
15+
async function runTests() {
16+
console.log('🧪 Testing: test-utils.ts\n');
17+
18+
await testFunction('testFunction restores process.env after callbacks pass or fail', async () => {
19+
const originalValue = process.env[isolationKey];
20+
const originalLog = console.log;
21+
const nestedResults = createTestResults();
22+
23+
try {
24+
delete process.env[isolationKey];
25+
console.log = () => {};
26+
27+
await testFunction('intentional failure', () => {
28+
process.env[isolationKey] = 'failed-test-value';
29+
throw new Error('intentional harness failure');
30+
}, nestedResults);
31+
32+
assert.equal(nestedResults.failed, 1);
33+
assert.equal(process.env[isolationKey], undefined);
34+
35+
process.env[isolationKey] = 'baseline-value';
36+
await testFunction('intentional success', () => {
37+
process.env[isolationKey] = 'successful-test-value';
38+
}, nestedResults);
39+
40+
assert.equal(nestedResults.passed, 1);
41+
assert.equal(process.env[isolationKey], 'baseline-value');
42+
} finally {
43+
console.log = originalLog;
44+
if (originalValue === undefined) {
45+
delete process.env[isolationKey];
46+
} else {
47+
process.env[isolationKey] = originalValue;
48+
}
49+
}
50+
}, results);
51+
52+
await testFunction('snapshotProcessEnv preserves prototype-like environment keys', () => {
53+
const key = '__proto__';
54+
const originalValue = process.env[key];
55+
56+
try {
57+
process.env[key] = 'prototype-key-value';
58+
const snapshot = snapshotProcessEnv();
59+
60+
assert.equal(Object.getPrototypeOf(snapshot), null);
61+
assert.equal(Object.hasOwn(snapshot, key), true);
62+
assert.equal(snapshot[key], 'prototype-key-value');
63+
} finally {
64+
if (originalValue === undefined) {
65+
delete process.env[key];
66+
} else {
67+
process.env[key] = originalValue;
68+
}
69+
}
70+
}, results);
71+
72+
printTestSummary(results, 'Test Utilities');
73+
return results;
74+
}
75+
76+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
77+
runTests().then((testResults) => {
78+
process.exit(testResults.failed > 0 ? 1 : 0);
79+
}).catch(console.error);
80+
}
81+
82+
export { runTests };

0 commit comments

Comments
 (0)