|
| 1 | +const FSExtra = require('fs-extra'); |
| 2 | +const { join, resolve } = require('node:path'); |
| 3 | +const { |
| 4 | + applyDependencyPatches, |
| 5 | + applyBuiltInPatches, |
| 6 | + _isAlreadyPatchedMap, |
| 7 | + pathsBeingPatched, |
| 8 | + // @ts-ignore |
| 9 | +} = require('./patches'); // Adjust path as necessary, assuming patches.ts is compiled to patches.js |
| 10 | + |
| 11 | +const MOCK_NODE_MODULES_PATH = resolve(__dirname, 'test-fixtures', 'mock-node_modules'); |
| 12 | +const TEST_MODULE_PATH = join(MOCK_NODE_MODULES_PATH, 'patching-test'); |
| 13 | +const TEST_FILE_PATH = join(TEST_MODULE_PATH, 'testfile.js'); |
| 14 | +const OG_TEST_FILE_PATH = TEST_FILE_PATH + '.vxrn.ogfile'; |
| 15 | + |
| 16 | +// --- Helper Functions --- |
| 17 | + |
| 18 | +async function setupTestEnvironment() { |
| 19 | + // Reset global state |
| 20 | + _isAlreadyPatchedMap.clear(); |
| 21 | + pathsBeingPatched.clear(); |
| 22 | + |
| 23 | + // Ensure mock-node_modules and patching-test directory exist |
| 24 | + await FSExtra.ensureDir(TEST_MODULE_PATH); |
| 25 | + |
| 26 | + // Recreate testfile.js |
| 27 | + await FSExtra.writeFile(TEST_FILE_PATH, '// orig'); |
| 28 | + |
| 29 | + // Delete ogfile if it exists |
| 30 | + if (await FSExtra.exists(OG_TEST_FILE_PATH)) { |
| 31 | + await FSExtra.remove(OG_TEST_FILE_PATH); |
| 32 | + } |
| 33 | + |
| 34 | + // Unset VXRN_FORCE_PATCH |
| 35 | + delete process.env.VXRN_FORCE_PATCH; |
| 36 | +} |
| 37 | + |
| 38 | +async function readFileContent(filePath) { |
| 39 | + if (await FSExtra.exists(filePath)) { |
| 40 | + return FSExtra.readFile(filePath, 'utf-8'); |
| 41 | + } |
| 42 | + return null; |
| 43 | +} |
| 44 | + |
| 45 | +function setForcePatchEnv(value) { |
| 46 | + if (value) { |
| 47 | + process.env.VXRN_FORCE_PATCH = 'true'; |
| 48 | + } else { |
| 49 | + delete process.env.VXRN_FORCE_PATCH; |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +async function runAssertions(testName, expectedContent, expectedOgContent, expectedPatchedMapSize, expectedPathsBeingPatchedSize) { |
| 54 | + const fileContent = await readFileContent(TEST_FILE_PATH); |
| 55 | + const ogFileContent = await readFileContent(OG_TEST_FILE_PATH); |
| 56 | + |
| 57 | + if (fileContent !== expectedContent) { |
| 58 | + console.error(`[${testName}] TestFile Content FAIL: Expected "${expectedContent}", got "${fileContent}"`); |
| 59 | + } else { |
| 60 | + console.log(`[${testName}] TestFile Content PASS`); |
| 61 | + } |
| 62 | + |
| 63 | + if (ogFileContent !== expectedOgContent) { |
| 64 | + console.error(`[${testName}] OgFile Content FAIL: Expected "${expectedOgContent}", got "${ogFileContent}"`); |
| 65 | + } else { |
| 66 | + console.log(`[${testName}] OgFile Content PASS`); |
| 67 | + } |
| 68 | + |
| 69 | + if (_isAlreadyPatchedMap.size !== expectedPatchedMapSize) { |
| 70 | + console.error(`[${testName}] _isAlreadyPatchedMap Size FAIL: Expected ${expectedPatchedMapSize}, got ${_isAlreadyPatchedMap.size}`); |
| 71 | + } else { |
| 72 | + console.log(`[${testName}] _isAlreadyPatchedMap Size PASS`); |
| 73 | + } |
| 74 | + |
| 75 | + if (pathsBeingPatched.size !== expectedPathsBeingPatchedSize) { |
| 76 | + console.error(`[${testName}] pathsBeingPatched Size FAIL: Expected ${expectedPathsBeingPatchedSize}, got ${pathsBeingPatched.size}`); |
| 77 | + } else { |
| 78 | + console.log(`[${testName}] pathsBeingPatched Size PASS`); |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +// --- Test Cases --- |
| 83 | + |
| 84 | +async function testScenario1_TwoRulesSameFile() { |
| 85 | + const testName = 'Scenario 1: Two patching rules for the same file'; |
| 86 | + console.log(`\n--- Running ${testName} ---`); |
| 87 | + await setupTestEnvironment(); |
| 88 | + |
| 89 | + const patches = [ |
| 90 | + { |
| 91 | + module: 'patching-test', |
| 92 | + patchFiles: { |
| 93 | + 'testfile.js': (contents) => contents + '\n// patch 1', |
| 94 | + }, |
| 95 | + }, |
| 96 | + { |
| 97 | + module: 'patching-test', |
| 98 | + patchFiles: { |
| 99 | + 'testfile.js': (contents) => contents + '\n// patch 2', |
| 100 | + }, |
| 101 | + }, |
| 102 | + ]; |
| 103 | + |
| 104 | + console.log('Applying patches first time...'); |
| 105 | + await applyDependencyPatches(patches, { nodeModulesPath: MOCK_NODE_MODULES_PATH }); |
| 106 | + await runAssertions(testName + " - First Run", '// orig\n// patch 1\n// patch 2', '// orig', 1, 1); |
| 107 | + |
| 108 | + console.log('Applying patches second time (should skip)...'); |
| 109 | + const mapSizeBefore = _isAlreadyPatchedMap.size; |
| 110 | + const setSizeBefore = pathsBeingPatched.size; |
| 111 | + pathsBeingPatched.clear(); // clear for this run as it's per-run |
| 112 | + await applyDependencyPatches(patches, { nodeModulesPath: MOCK_NODE_MODULES_PATH }); |
| 113 | + await runAssertions(testName + " - Second Run (Skipped)", '// orig\n// patch 1\n// patch 2', '// orig', mapSizeBefore, setSizeBefore -1); // pathsBeingPatched should remain 0 as nothing new is patched |
| 114 | + |
| 115 | + console.log('Applying patches with VXRN_FORCE_PATCH...'); |
| 116 | + setForcePatchEnv(true); |
| 117 | + pathsBeingPatched.clear(); // clear for this run |
| 118 | + _isAlreadyPatchedMap.clear(); // clear this to simulate fresh check |
| 119 | + await applyDependencyPatches(patches, { nodeModulesPath: MOCK_NODE_MODULES_PATH }); |
| 120 | + await runAssertions(testName + " - Force Patch", '// orig\n// patch 1\n// patch 2', '// orig', 1, 1); |
| 121 | + setForcePatchEnv(false); |
| 122 | + |
| 123 | + console.log(`--- ${testName} Complete ---`); |
| 124 | +} |
| 125 | + |
| 126 | +async function testScenario2_MergeExtraPatches() { |
| 127 | + const testName = 'Scenario 2: Merging extraPatches'; |
| 128 | + console.log(`\n--- Running ${testName} ---`); |
| 129 | + await setupTestEnvironment(); |
| 130 | + |
| 131 | + const builtInPatches = [ |
| 132 | + { |
| 133 | + module: 'patching-test', |
| 134 | + patchFiles: { |
| 135 | + 'testfile.js': '// built-in patch', |
| 136 | + 'version': '1.0.0', // ensure version check is handled if present |
| 137 | + }, |
| 138 | + }, |
| 139 | + ]; |
| 140 | + |
| 141 | + const extraPatches = { |
| 142 | + 'patching-test': { |
| 143 | + 'testfile.js': '// extra patch', // This should overwrite the built-in one |
| 144 | + 'anotherfile.js': '// new file patch by extra', // This should be added |
| 145 | + optimize: 'exclude', |
| 146 | + }, |
| 147 | + }; |
| 148 | + |
| 149 | + const options = { root: process.cwd(), तम: {} }; // Mock options as needed by applyBuiltInPatches |
| 150 | + |
| 151 | + console.log('Applying built-in and extra patches...'); |
| 152 | + // @ts-ignore |
| 153 | + await applyBuiltInPatches(options, extraPatches, builtInPatches); |
| 154 | + // Note: applyBuiltInPatches internally calls applyDependencyPatches. |
| 155 | + // We need to assert the final state of testfile.js |
| 156 | + // And potentially anotherfile.js if we were to create it. For now, focus on testfile.js |
| 157 | + // The current applyBuiltInPatches structure merges patchFiles objects. |
| 158 | + // If a file key is the same, the extraPatch one should win. |
| 159 | + await runAssertions(testName + " - First Run", '// extra patch', '// orig', 1, 1); |
| 160 | + |
| 161 | + |
| 162 | + console.log('Applying patches second time (should skip)...'); |
| 163 | + const mapSizeBefore = _isAlreadyPatchedMap.size; |
| 164 | + pathsBeingPatched.clear(); |
| 165 | + // @ts-ignore |
| 166 | + await applyBuiltInPatches(options, extraPatches, builtInPatches); |
| 167 | + await runAssertions(testName + " - Second Run (Skipped)", '// extra patch', '// orig', mapSizeBefore, 0); |
| 168 | + |
| 169 | + |
| 170 | + console.log('Applying patches with VXRN_FORCE_PATCH...'); |
| 171 | + setForcePatchEnv(true); |
| 172 | + pathsBeingPatched.clear(); |
| 173 | + _isAlreadyPatchedMap.clear(); |
| 174 | + // @ts-ignore |
| 175 | + await applyBuiltInPatches(options, extraPatches, builtInPatches); |
| 176 | + await runAssertions(testName + " - Force Patch", '// extra patch', '// orig', 1, 1); |
| 177 | + setForcePatchEnv(false); |
| 178 | + |
| 179 | + console.log(`--- ${testName} Complete ---`); |
| 180 | +} |
| 181 | + |
| 182 | + |
| 183 | +async function testScenario3_MultipleApplyDependencyPatches() { |
| 184 | + const testName = 'Scenario 3: Calling applyDependencyPatches multiple times with different inputs'; |
| 185 | + console.log(`\n--- Running ${testName} ---`); |
| 186 | + await setupTestEnvironment(); |
| 187 | + |
| 188 | + const patches1 = [ |
| 189 | + { |
| 190 | + module: 'patching-test', |
| 191 | + patchFiles: { 'testfile.js': (contents) => contents + '\n// first apply' }, |
| 192 | + }, |
| 193 | + ]; |
| 194 | + const patches2 = [ |
| 195 | + { |
| 196 | + module: 'patching-test', |
| 197 | + patchFiles: { 'testfile.js': (contents) => contents + '\n// second apply (should be from og)' }, |
| 198 | + }, |
| 199 | + ]; |
| 200 | + |
| 201 | + console.log('Applying patches1 first time...'); |
| 202 | + await applyDependencyPatches(patches1, { nodeModulesPath: MOCK_NODE_MODULES_PATH }); |
| 203 | + await runAssertions(testName + " - Patches1 First Run", '// orig\n// first apply', '// orig', 1, 1); |
| 204 | + |
| 205 | + // Reset pathsBeingPatched for the next distinct call, but _isAlreadyPatchedMap should persist for `testfile.js` |
| 206 | + pathsBeingPatched.clear(); |
| 207 | + // _isAlreadyPatchedMap.clear(); // DO NOT CLEAR THIS, to simulate separate calls where previous patching is known |
| 208 | + |
| 209 | + console.log('Applying patches2 (should apply to original because testfile.js is already considered patched by patches1 run, then force patch will use OG)'); |
| 210 | + // This scenario is tricky. If applyDependencyPatches is called with *different* patch rules for the *same file* |
| 211 | + // that was already patched in a *previous* call (even in the same overall script run), |
| 212 | + // the current logic will skip it because `getIsAlreadyPatched(fullPath)` will be true. |
| 213 | + // The `ogfile` will be from the *first* patch application. |
| 214 | + // If we want the second set of patches to apply to the *original*, we need VXRN_FORCE_PATCH. |
| 215 | + |
| 216 | + await applyDependencyPatches(patches2, { nodeModulesPath: MOCK_NODE_MODULES_PATH }); |
| 217 | + // It will skip because testfile.js is already patched by patches1 |
| 218 | + await runAssertions(testName + " - Patches2 Second Run (Skipped due to prior patch)", '// orig\n// first apply', '// orig', 1, 0); |
| 219 | + |
| 220 | + |
| 221 | + console.log('Applying patches2 with VXRN_FORCE_PATCH (should apply to original)...'); |
| 222 | + setForcePatchEnv(true); |
| 223 | + pathsBeingPatched.clear(); |
| 224 | + // _isAlreadyPatchedMap.clear(); // Clear to ensure it re-evaluates, or keep to test if force overrides existing map entry |
| 225 | + // Let's keep _isAlreadyPatchedMap to see if VXRN_FORCE_PATCH bypasses its check, it should. |
| 226 | + // The key is that it should read from OG file. |
| 227 | + await applyDependencyPatches(patches2, { nodeModulesPath: MOCK_NODE_MODULES_PATH }); |
| 228 | + // Since patches2 is a function (contents) => contents + ..., and it reads from ogfile. |
| 229 | + await runAssertions(testName + " - Patches2 Force Patch", '// orig\n// second apply (should be from og)', '// orig', 1, 1); |
| 230 | + setForcePatchEnv(false); |
| 231 | + |
| 232 | + console.log(`--- ${testName} Complete ---`); |
| 233 | +} |
| 234 | + |
| 235 | + |
| 236 | +async function main() { |
| 237 | + await testScenario1_TwoRulesSameFile(); |
| 238 | + await testScenario2_MergeExtraPatches(); |
| 239 | + await testScenario3_MultipleApplyDependencyPatches(); |
| 240 | + console.log("\nAll tests complete. Check console output for PASS/FAIL."); |
| 241 | +} |
| 242 | + |
| 243 | +main().catch(console.error); |
0 commit comments