-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrewire.ts
More file actions
167 lines (150 loc) · 4.59 KB
/
rewire.ts
File metadata and controls
167 lines (150 loc) · 4.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/**
* @fileoverview Path rewiring utilities for testing.
* Allows tests to override os.tmpdir() and os.homedir() without directly modifying them.
*
* Features:
* - Test-friendly setPath/clearPath/resetPaths that work in beforeEach/afterEach
* - Automatic cache invalidation for path-dependent modules
* - Thread-safe for concurrent test execution
*/
import { MapCtor } from '../primordials'
// Shared test hook state (setPath/clearPath/resetPaths in beforeEach/afterEach)
// IMPORTANT: Use globalThis to ensure singleton across duplicate module instances.
// Vitest alias resolution can create separate module instances for the same file
// (e.g. '@socketsecurity/lib/paths/rewire' vs relative '../paths/rewire').
// Both must share the same Maps for rewiring to work correctly.
// Only initialize in test environment to avoid polluting production runtime.
// Vitest automatically sets VITEST=true when running tests.
interface PathRewireState {
testOverrides: Map<string, string | undefined>
valueCache: Map<string, string>
cacheInvalidationCallbacks: Array<() => void>
}
const stateSymbol = Symbol.for('@socketsecurity/lib/paths/rewire/state')
const _globalThis = globalThis as typeof globalThis &
Record<symbol, PathRewireState | undefined>
if (!_globalThis[stateSymbol]) {
_globalThis[stateSymbol] = {
testOverrides: new MapCtor<string, string | undefined>(),
valueCache: new MapCtor<string, string>(),
cacheInvalidationCallbacks: [] as Array<() => void>,
}
}
const sharedState: PathRewireState = _globalThis[stateSymbol]!
// Per-test overrides
const testOverrides = sharedState.testOverrides
// Cache for computed values (cleared when overrides change)
const valueCache = sharedState.valueCache
// Cache invalidation callbacks - registered by modules that need to clear their caches
const cacheInvalidationCallbacks = sharedState.cacheInvalidationCallbacks
/**
* Clear a specific path override.
*/
export function clearPath(key: string): void {
testOverrides.delete(key)
// Invalidate all path-related caches
invalidateCaches()
}
/**
* Get a path value, checking overrides first.
*
* Resolution order:
* 1. Test overrides (set via setPath in beforeEach)
* 2. Cached value (for performance)
* 3. Original function call (cached for subsequent calls)
*
* @internal Used by path getters to support test rewiring
*/
export function getPathValue(key: string, originalFn: () => string): string {
// Check test overrides first
if (testOverrides.has(key)) {
return testOverrides.get(key) as string
}
// Check cache
if (valueCache.has(key)) {
return valueCache.get(key) as string
}
// Compute and cache
const value = originalFn()
valueCache.set(key, value)
return value
}
/**
* Check if a path has been overridden.
*/
export function hasOverride(key: string): boolean {
return testOverrides.has(key)
}
/**
* Invalidate all cached paths.
* Called automatically when setPath/clearPath/resetPaths are used.
* Can also be called manually for advanced testing scenarios.
*
* @internal Primarily for internal use, but exported for advanced testing
*/
export function invalidateCaches(): void {
// Clear the value cache
valueCache.clear()
// Call registered callbacks
for (const callback of cacheInvalidationCallbacks) {
try {
callback()
} catch {
// Ignore errors from cache invalidation
}
}
}
/**
* Register a cache invalidation callback.
* Called by modules that need to clear their caches when paths change.
*
* @internal Used by paths.ts and fs.ts
*/
export function registerCacheInvalidation(callback: () => void): void {
cacheInvalidationCallbacks.push(callback)
}
/**
* Clear all path overrides and reset caches.
* Useful in afterEach hooks to ensure clean test state.
*
* @example
* ```typescript
* import { resetPaths } from '#paths/rewire'
*
* afterEach(() => {
* resetPaths()
* })
* ```
*/
export function resetPaths(): void {
testOverrides.clear()
// Invalidate all path-related caches
invalidateCaches()
}
/**
* Set a path override for testing.
* This triggers cache invalidation for path-dependent modules.
*
* @example
* ```typescript
* import { setPath, resetPaths } from '#paths/rewire'
* import { getOsTmpDir } from './'
*
* beforeEach(() => {
* setPath('tmpdir', '/custom/tmp')
* })
*
* afterEach(() => {
* resetPaths()
* })
*
* it('should use custom temp directory', () => {
* expect(getOsTmpDir()).toBe('/custom/tmp')
* })
* ```
*/
export function setPath(key: string, value: string | undefined): void {
testOverrides.set(key, value)
// Invalidate all path-related caches
invalidateCaches()
}