From 08bff6f8e9fa1ce93531fd46d59eacb5738beff9 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Sun, 29 Mar 2026 14:22:28 -0700 Subject: [PATCH] test(install): add automated tests for user file preservation (#1423) E2E tests verifying USER-PROFILE.md and dev-preferences.md survive uninstall. Covers: profile preservation, preferences preservation, engine files still removed, clean uninstall without user files. Addresses review feedback requesting automated coverage for the preserve-and-restore pattern in the rmSync uninstall path. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/copilot-install.test.cjs | 84 ++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/copilot-install.test.cjs b/tests/copilot-install.test.cjs index 25ec66c1e..c80ddff53 100644 --- a/tests/copilot-install.test.cjs +++ b/tests/copilot-install.test.cjs @@ -1341,3 +1341,87 @@ describe('E2E: Copilot uninstall verification', () => { }); }); }); + +// ─── Claude uninstall: user file preservation (#1423) ───────────────────────── + +function runClaudeInstall(cwd) { + const env = { ...process.env }; + delete env.GSD_TEST_MODE; + return execFileSync(process.execPath, [INSTALL_PATH, '--claude', '--local'], { + cwd, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + env, + }); +} + +function runClaudeUninstall(cwd) { + const env = { ...process.env }; + delete env.GSD_TEST_MODE; + return execFileSync(process.execPath, [INSTALL_PATH, '--claude', '--local', '--uninstall'], { + cwd, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + env, + }); +} + +describe('Claude uninstall preserves user-generated files (#1423)', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-preserve-')); + runClaudeInstall(tmpDir); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + test('preserves USER-PROFILE.md across uninstall', () => { + const profilePath = path.join(tmpDir, '.claude', 'get-shit-done', 'USER-PROFILE.md'); + const content = '# Developer Profile\n\nAutonomy: High\nGenerated: 2026-03-29\n'; + fs.writeFileSync(profilePath, content); + + runClaudeUninstall(tmpDir); + + assert.ok(fs.existsSync(profilePath), 'USER-PROFILE.md should survive uninstall'); + assert.strictEqual(fs.readFileSync(profilePath, 'utf-8'), content, 'content should be identical'); + }); + + test('preserves dev-preferences.md across uninstall', () => { + const prefsPath = path.join(tmpDir, '.claude', 'commands', 'gsd', 'dev-preferences.md'); + const content = '---\nname: dev-preferences\n---\n# Preferences\nUse TypeScript strict.\n'; + fs.writeFileSync(prefsPath, content); + + runClaudeUninstall(tmpDir); + + assert.ok(fs.existsSync(prefsPath), 'dev-preferences.md should survive uninstall'); + assert.strictEqual(fs.readFileSync(prefsPath, 'utf-8'), content, 'content should be identical'); + }); + + test('still removes GSD engine files during uninstall', () => { + const profilePath = path.join(tmpDir, '.claude', 'get-shit-done', 'USER-PROFILE.md'); + fs.writeFileSync(profilePath, '# Profile\n'); + + // Verify engine files exist before uninstall + const binDir = path.join(tmpDir, '.claude', 'get-shit-done', 'bin'); + assert.ok(fs.existsSync(binDir), 'bin/ should exist before uninstall'); + + runClaudeUninstall(tmpDir); + + // Engine files gone, user file preserved + assert.ok(!fs.existsSync(binDir), 'bin/ should be removed after uninstall'); + assert.ok(fs.existsSync(profilePath), 'USER-PROFILE.md should survive'); + }); + + test('clean uninstall when no user files exist', () => { + runClaudeUninstall(tmpDir); + + const gsdDir = path.join(tmpDir, '.claude', 'get-shit-done'); + const cmdDir = path.join(tmpDir, '.claude', 'commands', 'gsd'); + // Directories should be fully removed when no user files to preserve + assert.ok(!fs.existsSync(gsdDir), 'get-shit-done/ should not exist after clean uninstall'); + assert.ok(!fs.existsSync(cmdDir), 'commands/gsd/ should not exist after clean uninstall'); + }); +});