From 234f0d9b7535c153a395ec26556ae3a32e7d04d8 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 19 Jul 2026 04:28:05 +0200 Subject: [PATCH] test(migrate): cover deleted checkout recreation --- .../migration-host-automation.e2e.test.ts | 85 +++++++++++++------ 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/packages/server/src/server/daemon-e2e/migration-host-automation.e2e.test.ts b/packages/server/src/server/daemon-e2e/migration-host-automation.e2e.test.ts index 57edc38fd0..57e69ed126 100644 --- a/packages/server/src/server/daemon-e2e/migration-host-automation.e2e.test.ts +++ b/packages/server/src/server/daemon-e2e/migration-host-automation.e2e.test.ts @@ -49,32 +49,7 @@ test("two complete migrations reuse the same real daemon checkout", async () => host: `127.0.0.1:${daemon.port}`, }); cleanupConnections.add(paseo); - const source = { - id: "fixture", - inspect: async () => ({ - skippedSettings: [], - projects: [ - { - sourceId: "project", - rootPath: repoRoot, - config: null, - notices: [], - workspaces: [ - { - sourceId: "workspace", - state: "ready", - path: null, - branch: "feature", - archiveCommit: null, - directoryName: "imported-feature", - disposition: "create" as const, - notices: [], - }, - ], - }, - ], - }), - }; + const source = migrationSource(repoRoot); const first = await migrate({ source, paseo, dryRun: false, output: () => undefined }); const second = await migrate({ source, paseo, dryRun: false, output: () => undefined }); @@ -87,6 +62,35 @@ test("two complete migrations reuse the same real daemon checkout", async () => expect(path.basename(listFeatureWorktrees(repoRoot)[0]?.path ?? "")).toBe("imported-feature"); }); +test.skipIf(process.platform === "win32")( + "a complete migration recreates an externally deleted checkout", + async () => { + const repoRoot = createRepository(); + const daemon = await createTestPaseoDaemon(); + cleanupDaemons.add(daemon); + const paseo = await connectHostAutomation({ + appVersion: "0.1.110", + clientId: "migration-deleted-checkout-e2e", + env: {}, + host: `127.0.0.1:${daemon.port}`, + }); + cleanupConnections.add(paseo); + const source = migrationSource(repoRoot); + + const first = await migrate({ source, paseo, dryRun: false, output: () => undefined }); + const deletedPath = listFeatureWorktrees(repoRoot)[0]?.path; + if (!deletedPath) throw new Error("First migration did not create the feature checkout"); + rmSync(deletedPath, { recursive: true, force: true }); + expect(existsSync(deletedPath)).toBe(false); + + const second = await migrate({ source, paseo, dryRun: false, output: () => undefined }); + + expect(first.notices).toEqual([]); + expect(second.notices).toEqual([]); + expect(existsSync(deletedPath)).toBe(true); + }, +); + test("a different live checkout continues to protect its branch", async () => { const repoRoot = createRepository(); const existingPath = path.join(path.dirname(repoRoot), "existing-feature"); @@ -273,6 +277,35 @@ function createRepository(): string { return realpathSync(repoRoot); } +function migrationSource(repoRoot: string) { + return { + id: "fixture", + inspect: async () => ({ + skippedSettings: [], + projects: [ + { + sourceId: "project", + rootPath: repoRoot, + config: null, + notices: [], + workspaces: [ + { + sourceId: "workspace", + state: "ready", + path: null, + branch: "feature", + archiveCommit: null, + directoryName: "imported-feature", + disposition: "create" as const, + notices: [], + }, + ], + }, + ], + }), + }; +} + function listFeatureWorktrees(repoRoot: string): Array<{ path: string; branch: string }> { const entries = execFileSync("git", ["worktree", "list", "--porcelain"], { cwd: repoRoot,