mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
- add dependency injection to export-command, binary-manager, codex-plan-compatibility, config-command, and usage-syncer - override process.exit in api-export test to prevent silent termination in Bun's parallel test runner - harden test-environment bootstrap with process.env isolation - fix auth middleware to avoid config upgrade during checks
247 lines
7.0 KiB
JavaScript
247 lines
7.0 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
/**
|
|
* Test Environment Isolation
|
|
*
|
|
* Provides isolated ~/.ccs/ directory for tests to prevent
|
|
* interference with the user's actual CCS configuration.
|
|
*
|
|
* Usage:
|
|
* const { createTestEnvironment } = require('../fixtures/test-environment');
|
|
*
|
|
* describe('my tests', () => {
|
|
* let testEnv;
|
|
*
|
|
* before(() => {
|
|
* testEnv = createTestEnvironment();
|
|
* });
|
|
*
|
|
* after(() => {
|
|
* testEnv.cleanup();
|
|
* });
|
|
*
|
|
* it('test something', () => {
|
|
* // Tests run with CCS_HOME pointing to temp directory
|
|
* // Your test code here
|
|
* });
|
|
* });
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const os = require('os');
|
|
|
|
let bootstrappedTestHome;
|
|
const originalHomedir = os.homedir;
|
|
let homedirPatched = false;
|
|
|
|
function getEffectiveTestHome() {
|
|
return process.env.CCS_HOME || process.env.HOME || process.env.USERPROFILE || bootstrappedTestHome || originalHomedir();
|
|
}
|
|
|
|
function patchHomedirForTests() {
|
|
if (homedirPatched) {
|
|
return;
|
|
}
|
|
|
|
os.homedir = () => getEffectiveTestHome();
|
|
homedirPatched = true;
|
|
}
|
|
|
|
function createIsolatedTestHome(prefix = 'ccs-test-home-') {
|
|
const testHome = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
fs.mkdirSync(path.join(testHome, '.ccs'), { recursive: true });
|
|
fs.mkdirSync(path.join(testHome, '.claude'), { recursive: true });
|
|
fs.mkdirSync(path.join(testHome, '.config'), { recursive: true });
|
|
fs.mkdirSync(path.join(testHome, '.cache'), { recursive: true });
|
|
fs.mkdirSync(path.join(testHome, '.state'), { recursive: true });
|
|
return testHome;
|
|
}
|
|
|
|
function ensureGlobalTestEnvironment() {
|
|
if (bootstrappedTestHome) {
|
|
return bootstrappedTestHome;
|
|
}
|
|
|
|
const testHome = createIsolatedTestHome();
|
|
process.env.HOME = testHome;
|
|
process.env.USERPROFILE = testHome;
|
|
process.env.CCS_HOME = testHome;
|
|
process.env.XDG_CONFIG_HOME = path.join(testHome, '.config');
|
|
process.env.XDG_CACHE_HOME = path.join(testHome, '.cache');
|
|
process.env.XDG_STATE_HOME = path.join(testHome, '.state');
|
|
process.env.CCS_TEST_BOOTSTRAP_HOME = testHome;
|
|
|
|
bootstrappedTestHome = testHome;
|
|
patchHomedirForTests();
|
|
return bootstrappedTestHome;
|
|
}
|
|
|
|
/**
|
|
* Create an isolated test environment
|
|
* Sets CCS_HOME to a temporary directory and provides cleanup
|
|
*
|
|
* @returns {object} Test environment with paths and cleanup function
|
|
*/
|
|
function createTestEnvironment() {
|
|
// Create unique temp directory for this test run
|
|
const testHome = createIsolatedTestHome('ccs-test-');
|
|
const testCcsDir = path.join(testHome, '.ccs');
|
|
|
|
// Store original environment
|
|
const originalHome = process.env.HOME;
|
|
const originalCcsHome = process.env.CCS_HOME;
|
|
const originalUserProfile = process.env.USERPROFILE;
|
|
const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
const originalXdgCacheHome = process.env.XDG_CACHE_HOME;
|
|
const originalXdgStateHome = process.env.XDG_STATE_HOME;
|
|
|
|
// Keep HOME-family env vars aligned so code that still consults os.homedir()
|
|
// or XDG defaults stays inside the isolated test sandbox.
|
|
process.env.HOME = testHome;
|
|
process.env.USERPROFILE = testHome;
|
|
process.env.CCS_HOME = testHome;
|
|
process.env.XDG_CONFIG_HOME = path.join(testHome, '.config');
|
|
process.env.XDG_CACHE_HOME = path.join(testHome, '.cache');
|
|
process.env.XDG_STATE_HOME = path.join(testHome, '.state');
|
|
patchHomedirForTests();
|
|
|
|
// Return environment object
|
|
return {
|
|
/** Path to the test home directory (like ~) */
|
|
testHome,
|
|
|
|
/** Path to the test .ccs directory (like ~/.ccs) */
|
|
ccsDir: testCcsDir,
|
|
|
|
/** Original HOME value for reference */
|
|
originalHome,
|
|
|
|
/**
|
|
* Get a path within the test CCS directory
|
|
* @param {...string} parts - Path segments
|
|
* @returns {string} Full path
|
|
*/
|
|
getCcsPath(...parts) {
|
|
return path.join(testCcsDir, ...parts);
|
|
},
|
|
|
|
/**
|
|
* Create a file in the test CCS directory
|
|
* @param {string} relativePath - Path relative to .ccs/
|
|
* @param {string|object} content - File content (objects are JSON stringified)
|
|
*/
|
|
createFile(relativePath, content) {
|
|
const fullPath = path.join(testCcsDir, relativePath);
|
|
const dir = path.dirname(fullPath);
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
const data = typeof content === 'object' ? JSON.stringify(content, null, 2) : content;
|
|
fs.writeFileSync(fullPath, data);
|
|
},
|
|
|
|
/**
|
|
* Read a file from the test CCS directory
|
|
* @param {string} relativePath - Path relative to .ccs/
|
|
* @param {boolean} [asJson=false] - Parse as JSON
|
|
* @returns {string|object} File content
|
|
*/
|
|
readFile(relativePath, asJson = false) {
|
|
const fullPath = path.join(testCcsDir, relativePath);
|
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
return asJson ? JSON.parse(content) : content;
|
|
},
|
|
|
|
/**
|
|
* Check if a file exists in the test CCS directory
|
|
* @param {string} relativePath - Path relative to .ccs/
|
|
* @returns {boolean}
|
|
*/
|
|
fileExists(relativePath) {
|
|
return fs.existsSync(path.join(testCcsDir, relativePath));
|
|
},
|
|
|
|
/**
|
|
* Clean up the test environment and restore original settings
|
|
*/
|
|
cleanup() {
|
|
// Restore original environment
|
|
if (originalHome !== undefined) {
|
|
process.env.HOME = originalHome;
|
|
} else {
|
|
delete process.env.HOME;
|
|
}
|
|
|
|
if (originalUserProfile !== undefined) {
|
|
process.env.USERPROFILE = originalUserProfile;
|
|
} else {
|
|
delete process.env.USERPROFILE;
|
|
}
|
|
|
|
if (originalCcsHome !== undefined) {
|
|
process.env.CCS_HOME = originalCcsHome;
|
|
} else {
|
|
delete process.env.CCS_HOME;
|
|
}
|
|
|
|
if (originalXdgConfigHome !== undefined) {
|
|
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
|
|
} else {
|
|
delete process.env.XDG_CONFIG_HOME;
|
|
}
|
|
|
|
if (originalXdgCacheHome !== undefined) {
|
|
process.env.XDG_CACHE_HOME = originalXdgCacheHome;
|
|
} else {
|
|
delete process.env.XDG_CACHE_HOME;
|
|
}
|
|
|
|
if (originalXdgStateHome !== undefined) {
|
|
process.env.XDG_STATE_HOME = originalXdgStateHome;
|
|
} else {
|
|
delete process.env.XDG_STATE_HOME;
|
|
}
|
|
|
|
// Clean up temp directory
|
|
try {
|
|
fs.rmSync(testHome, { recursive: true, force: true });
|
|
} catch (err) {
|
|
// Ignore cleanup errors
|
|
console.warn(`[test-environment] Cleanup warning: ${err.message}`);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the CCS home directory (respects CCS_HOME env var)
|
|
* This should be used instead of os.homedir() for CCS paths
|
|
*
|
|
* @returns {string} Home directory path
|
|
*/
|
|
function getCcsHome() {
|
|
return process.env.CCS_HOME || os.homedir();
|
|
}
|
|
|
|
/**
|
|
* Get the CCS directory path
|
|
*
|
|
* @returns {string} Path to .ccs directory
|
|
*/
|
|
function getCcsDir() {
|
|
return path.join(getCcsHome(), '.ccs');
|
|
}
|
|
|
|
module.exports = {
|
|
createTestEnvironment,
|
|
ensureGlobalTestEnvironment,
|
|
getCcsHome,
|
|
getCcsDir
|
|
};
|
|
|
|
if (process.env.CCS_TEST_DISABLE_GLOBAL_BOOTSTRAP !== '1') {
|
|
ensureGlobalTestEnvironment();
|
|
}
|