215 lines
8.1 KiB
JavaScript
215 lines
8.1 KiB
JavaScript
/**
|
|
* Verification Test for install-bmad-memtrace.sh
|
|
*
|
|
* Simulates the installation process in a clean temporary directory by:
|
|
* 1. Initializing a mock Git repository.
|
|
* 2. Committing some dummy "legacy clone" files.
|
|
* 3. Adding the install script and helper script fixtures.
|
|
* 4. Running the bash installer.
|
|
* 5. Verifying cleanup, restore, workspace anchor generation, and MCP config injection.
|
|
*
|
|
* Usage: node test/verify-installer.js
|
|
*/
|
|
|
|
const fs = require('node:fs/promises');
|
|
const path = require('node:path');
|
|
const os = require('node:os');
|
|
const { exec } = require('node:child_process');
|
|
|
|
const colors = {
|
|
reset: '\u001B[0m',
|
|
green: '\u001B[32m',
|
|
red: '\u001B[31m',
|
|
yellow: '\u001B[33m',
|
|
cyan: '\u001B[36m',
|
|
dim: '\u001B[2m',
|
|
};
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function assert(condition, testName, errorMessage = '') {
|
|
if (condition) {
|
|
console.log(`${colors.green}✓${colors.reset} ${testName}`);
|
|
passed++;
|
|
} else {
|
|
console.log(`${colors.red}✗${colors.reset} ${testName}`);
|
|
if (errorMessage) {
|
|
console.log(` ${colors.dim}${errorMessage}${colors.reset}`);
|
|
}
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
function runCmd(command, cwd) {
|
|
return new Promise((resolve, reject) => {
|
|
exec(command, { cwd }, (error, stdout, stderr) => {
|
|
if (error) {
|
|
reject({ error, stdout, stderr });
|
|
} else {
|
|
resolve({ stdout, stderr });
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Helper to ensure parent directories exist
|
|
async function ensureDir(filePath) {
|
|
const dir = path.dirname(filePath);
|
|
try {
|
|
await fs.mkdir(dir, { recursive: true });
|
|
} catch (err) {}
|
|
}
|
|
|
|
async function runVerification() {
|
|
console.log(`${colors.cyan}========================================`);
|
|
console.log('Standalone Installer Verification');
|
|
console.log(`========================================${colors.reset}\n`);
|
|
|
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-installer-verify-'));
|
|
console.log(`Created temporary test environment at: ${tempDir}\n`);
|
|
|
|
try {
|
|
// 1. Initialize git repository
|
|
await runCmd('git init -b main', tempDir);
|
|
await runCmd('git config user.name "Test"', tempDir);
|
|
await runCmd('git config user.email "test@example.com"', tempDir);
|
|
|
|
// 2. Create and commit dummy cloned files (to simulate legacy clone cleanup)
|
|
const dummyClonedFile = path.join(tempDir, 'README.md');
|
|
await fs.writeFile(dummyClonedFile, '# Legacy README\n');
|
|
await runCmd('git add README.md', tempDir);
|
|
await runCmd('git commit -m "initial commit"', tempDir);
|
|
|
|
// 3. Create core BMad directory structure and helper script
|
|
const helperSourcePath = path.resolve(__dirname, '../_bmad/scripts/memtrace/inject-mcp-config.mjs');
|
|
const helperDestPath = path.join(tempDir, '_bmad/scripts/memtrace/inject-mcp-config.mjs');
|
|
await ensureDir(helperDestPath);
|
|
await fs.copyFile(helperSourcePath, helperDestPath);
|
|
|
|
// Also copy install script
|
|
const installerSourcePath = path.resolve(__dirname, '../install-bmad-memtrace.sh');
|
|
const installerDestPath = path.join(tempDir, 'install-bmad-memtrace.sh');
|
|
await fs.copyFile(installerSourcePath, installerDestPath);
|
|
|
|
// 4. Run installer script in bash
|
|
// We override TEST_CLAUDE_CONFIG_PATH and TEST_OPENCODE_CONFIG_PATH so it targets temp config files
|
|
const claudeTestConfig = path.join(tempDir, 'claude_desktop_config.json');
|
|
const opencodeTestConfig = path.join(tempDir, 'opencode.json');
|
|
|
|
console.log('Running install-bmad-memtrace.sh in temp directory...');
|
|
const envOverrides = {
|
|
...process.env,
|
|
TEST_CLAUDE_CONFIG_PATH: claudeTestConfig,
|
|
TEST_OPENCODE_CONFIG_PATH: opencodeTestConfig,
|
|
};
|
|
|
|
// Run bash on Windows (locate git-bash path to avoid WSL-relay failures)
|
|
let command = 'bash install-bmad-memtrace.sh';
|
|
if (process.platform === 'win32') {
|
|
try {
|
|
const { execSync } = require('node:child_process');
|
|
const gitPath = execSync('where git').toString().trim().split('\r\n')[0];
|
|
if (gitPath) {
|
|
const gitDir = path.dirname(gitPath); // C:\Program Files\Git\cmd
|
|
const gitParent = path.dirname(gitDir);
|
|
const possibleBash1 = path.join(gitParent, 'bin', 'bash.exe');
|
|
const possibleBash2 = path.join(gitParent, 'bin', 'sh.exe');
|
|
|
|
if (await fs.access(possibleBash1).then(() => true).catch(() => false)) {
|
|
command = `"${possibleBash1}" install-bmad-memtrace.sh`;
|
|
} else if (await fs.access(possibleBash2).then(() => true).catch(() => false)) {
|
|
command = `"${possibleBash2}" install-bmad-memtrace.sh`;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Fallback
|
|
const paths = [
|
|
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
];
|
|
for (const p of paths) {
|
|
if (await fs.access(p).then(() => true).catch(() => false)) {
|
|
command = `"${p}" install-bmad-memtrace.sh`;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`Running installer with command: ${command}`);
|
|
const result = await new Promise((resolve, reject) => {
|
|
exec(command, { cwd: tempDir, env: envOverrides }, (error, stdout, stderr) => {
|
|
if (error) reject({ error, stdout, stderr });
|
|
else resolve({ stdout, stderr });
|
|
});
|
|
});
|
|
|
|
console.log(`${colors.dim}${result.stdout}${colors.reset}\n`);
|
|
|
|
// 5. Assertions
|
|
|
|
// AC 1: .memtrace-workspace exists
|
|
const anchorExists = await fs.access(path.join(tempDir, '.memtrace-workspace')).then(() => true).catch(() => false);
|
|
assert(anchorExists, 'AC 1: .memtrace-workspace anchor file successfully created in project root');
|
|
|
|
// Legacy cleanup: README.md is deleted
|
|
const readmeExists = await fs.access(dummyClonedFile).then(() => true).catch(() => false);
|
|
assert(!readmeExists, 'Legacy cleanup: Tracked clone files (README.md) successfully deleted');
|
|
|
|
// Git removal: .git is deleted
|
|
const gitExists = await fs.access(path.join(tempDir, '.git')).then(() => true).catch(() => false);
|
|
assert(!gitExists, 'Security/Standalone: .git directory completely removed');
|
|
|
|
// Staging cleanup: bmad-install is deleted
|
|
const stagingExists = await fs.access(path.join(tempDir, 'bmad-install')).then(() => true).catch(() => false);
|
|
assert(!stagingExists, 'Runtime cleanup: bmad-install staging directory successfully removed');
|
|
|
|
// BMad preservation: _bmad directory remains
|
|
const bmadExists = await fs.access(path.join(tempDir, '_bmad')).then(() => true).catch(() => false);
|
|
assert(bmadExists, 'Core preservation: _bmad directory preserved post-cleanup');
|
|
|
|
// AC 2: Claude Desktop configuration successfully injected
|
|
const claudeContent = await fs.readFile(claudeTestConfig, 'utf8');
|
|
const claudeConfig = JSON.parse(claudeContent);
|
|
assert(
|
|
claudeConfig.mcpServers && claudeConfig.mcpServers.memtrace && claudeConfig.mcpServers.memtrace.command === 'memtrace',
|
|
'AC 2: Claude Desktop config correctly created and populated with memtrace MCP server'
|
|
);
|
|
|
|
// AC 3: OpenCode configuration successfully injected
|
|
const opencodeContent = await fs.readFile(opencodeTestConfig, 'utf8');
|
|
const opencodeConfig = JSON.parse(opencodeContent);
|
|
assert(
|
|
opencodeConfig.mcp && opencodeConfig.mcp.memtrace && opencodeConfig.mcp.memtrace.type === 'local',
|
|
'AC 3: OpenCode config correctly created and populated with memtrace local definition'
|
|
);
|
|
|
|
} catch (err) {
|
|
console.error('Verification failed with error:', err);
|
|
if (err.stdout) console.error('stdout:', err.stdout);
|
|
if (err.stderr) console.error('stderr:', err.stderr);
|
|
failed++;
|
|
}
|
|
|
|
// Clean up
|
|
try {
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
} catch (err) {}
|
|
|
|
console.log(`\n${colors.cyan}========================================`);
|
|
console.log(`Verification Summary: Passed: ${passed}, Failed: ${failed}`);
|
|
console.log(`========================================${colors.reset}\n`);
|
|
|
|
if (failed > 0) {
|
|
process.exit(1);
|
|
} else {
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
runVerification().catch(err => {
|
|
console.error('Fatal verification error:', err);
|
|
process.exit(1);
|
|
});
|