test: cover legacy OpenCode home repair and uninstall

This commit is contained in:
Yusuf Efe
2026-09-30 20:09:49 +03:00
parent ef475c7997
commit 780dfb7894
+91
View File
@@ -786,6 +786,97 @@ function runTests() {
}
})) passed++; else failed++;
if (test('OpenCode pre-fix managed sources upgrade on repair and uninstall preserves user changes', () => {
const tempDir = createTempDir('install-executor-opencode-upgrade-');
try {
const sourceRoot = path.join(tempDir, 'source');
writeManifestSourceFixture(sourceRoot);
const modulesPath = path.join(sourceRoot, 'manifests', 'install-modules.json');
const manifest = JSON.parse(fs.readFileSync(modulesPath, 'utf8'));
writeJson(sourceRoot, 'manifests/install-modules.json', {
...manifest,
modules: manifest.modules.map(module => ({
...module,
paths: ['.opencode'],
targets: ['opencode'],
})),
});
writeFile(sourceRoot, '.opencode/dist/index.js', 'export default () => ({});\n');
writeFile(sourceRoot, '.opencode/dist/plugins/index.js', 'export {};\n');
writeFile(sourceRoot, '.opencode/dist/tools/index.js', 'export {};\n');
writeJson(sourceRoot, '.opencode/opencode.json', { plugin: ['./plugins'] });
fs.cpSync(path.join(REPO_ROOT, '.opencode', 'tools'), path.join(sourceRoot, '.opencode', 'tools'), {
recursive: true,
});
const currentContent = fs.readFileSync(path.join(REPO_ROOT, '.opencode', 'tools', 'index.ts'), 'utf8');
const legacyContent = [
'export { default as runTests } from "./run-tests.js"',
'export { default as checkCoverage } from "./check-coverage.js"',
'export { default as securityAudit } from "./security-audit.js"',
'export { default as formatCode } from "./format-code.js"',
'export { default as lintCheck } from "./lint-check.js"',
'export { default as gitSummary } from "./git-summary.js"',
'export { default as changedFiles } from "./changed-files.js"',
'export { default as dependencyAnalyzer } from "./dependency-analyzer.js"',
'',
].join('\n');
const digest = content => crypto.createHash('sha256').update(content).digest('hex');
for (const upgrade of [false, true]) {
writeFile(sourceRoot, '.opencode/tools/index.ts', legacyContent);
const homeDir = path.join(tempDir, upgrade ? 'repair-home' : 'uninstall-home');
const projectRoot = path.join(tempDir, 'project');
const plan = createManifestInstallPlan({
sourceRoot, projectRoot, homeDir, target: 'opencode', moduleIds: ['fixture-core'],
});
applyInstallPlan(plan);
const barrelPath = path.join(plan.targetRoot, 'tools', 'index.ts');
const toolPath = path.join(plan.targetRoot, 'tools', 'run-tests.ts');
const userPath = writeFile(plan.targetRoot, 'tools/user-note.txt', 'Keep my settings.\n');
const installedState = JSON.parse(fs.readFileSync(plan.installStatePath, 'utf8'));
const recordedBarrel = installedState.operations.find(operation => operation.destinationPath === barrelPath);
assert.strictEqual(fs.readFileSync(barrelPath, 'utf8'), legacyContent);
assert.strictEqual(recordedBarrel.contentTransform, undefined, 'TypeScript imports are copied, not transformed');
assert.strictEqual(recordedBarrel.contentSha256, digest(legacyContent));
assert.ok(fs.existsSync(toolPath));
assert.ok(!fs.existsSync(path.join(plan.targetRoot, 'tools', 'run-tests.js')), 'legacy import has no adjacent JavaScript file');
// Upgrade the repository independently of the existing managed home.
writeFile(sourceRoot, '.opencode/tools/index.ts', currentContent);
const lifecycleOptions = { repoRoot: sourceRoot, projectRoot, homeDir, targets: ['opencode'] };
if (upgrade) {
const repaired = repairInstalledStates(lifecycleOptions);
assert.strictEqual(repaired.results.length, 1);
assert.strictEqual(repaired.results[0].status, 'repaired', JSON.stringify(repaired));
assert.ok(repaired.results[0].repairedPaths.includes(barrelPath));
const repairedContent = fs.readFileSync(barrelPath, 'utf8');
assert.strictEqual(repairedContent, currentContent);
assert.ok(repairedContent.includes('from "./run-tests.ts"'));
assert.ok(!repairedContent.includes('.js"'), 'repair replaces legacy JavaScript specifiers');
const repairedState = JSON.parse(fs.readFileSync(plan.installStatePath, 'utf8'));
const repairedBarrel = repairedState.operations.find(operation => operation.destinationPath === barrelPath);
assert.strictEqual(repairedBarrel.contentSha256, digest(currentContent));
assert.notStrictEqual(repairedBarrel.contentSha256, recordedBarrel.contentSha256);
assert.strictEqual(fs.readFileSync(userPath, 'utf8'), 'Keep my settings.\n');
}
const modifiedContent = `${fs.readFileSync(toolPath, 'utf8')}\n// User test customisation.\n`;
fs.writeFileSync(toolPath, modifiedContent, 'utf8');
const uninstalled = uninstallInstalledStates(lifecycleOptions);
assert.strictEqual(uninstalled.results.length, 1);
assert.strictEqual(uninstalled.results[0].status, 'partial', JSON.stringify(uninstalled));
assert.ok(uninstalled.results[0].removedPaths.includes(barrelPath));
assert.ok(!fs.existsSync(barrelPath), 'both legacy and repaired managed barrels are recognised');
assert.deepStrictEqual(uninstalled.results[0].retainedPaths, [toolPath]);
assert.strictEqual(fs.readFileSync(toolPath, 'utf8'), modifiedContent);
assert.strictEqual(fs.readFileSync(userPath, 'utf8'), 'Keep my settings.\n');
assert.ok(fs.existsSync(plan.installStatePath), 'modified managed content retains its ownership record');
}
} finally {
cleanup(tempDir);
}
})) passed++; else failed++;
if (test('dedupeCopyFileOperations keeps the last writer per destination (issue #2414)', () => {
// Mirrors the OpenCode command scenario: a generic commands/<name>.md source
// (preserve-relative-path) and an override .opencode/commands/<name>.md source