Files
kandev/apps/web/e2e/tests/session/setup-script-no-duplicate.spec.ts
T

99 lines
4.3 KiB
TypeScript

import { test, expect } from "../../fixtures/test-base";
import { SessionPage } from "../../pages/session-page";
/**
* Regression test for a duplicate-execution bug where the per-repo setup
* script (Repository.setup_script) ran twice during worktree prepare:
* 1. Once by worktree.Manager.Create via its script handler (correct).
* 2. Once by the env preparer when resolving the prepare script's
* {{repository.setup_script}} placeholder (wrong).
*
* For non-idempotent scripts this surfaces as the second run failing — the
* env preparer treats it as a non-fatal step, so the prepare panel ends in
* `completed_with_error` rather than `completed`. The fix blanks
* RepoSetupScript on the request copy used to resolve the prepare script,
* so the placeholder substitutes to empty and the second run is skipped.
*
* The unit-level coverage lives in
* apps/backend/internal/agent/lifecycle/env_preparer_worktree_dup_test.go;
* this test exercises the full HTTP + worktree manager + env preparer path.
*/
test.describe("Setup script no duplicate execution", () => {
test("non-idempotent repository setup_script runs exactly once on worktree task", async ({
testPage,
apiClient,
seedData,
}) => {
test.setTimeout(60_000);
// `mkdir <dir>` (without -p) fails with "File exists" on a second run.
// Each worktree gets its own working directory, so the directory only
// exists when the script ran twice in the same worktree — which is the
// exact symptom of the duplicate-execution bug.
const nonIdempotentScript = "mkdir build-once-marker";
await apiClient.updateRepository(seedData.repositoryId, {
setup_script: nonIdempotentScript,
});
try {
const task = await apiClient.createTaskWithAgent(
seedData.workspaceId,
"Setup Script No Duplicate",
seedData.agentProfileId,
{
description: "/e2e:simple-message",
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
// Default worktree profile uses the {{repository.setup_script}}
// placeholder, which is the path the bug lived on.
executor_profile_id: seedData.worktreeExecutorProfileId,
},
);
await testPage.goto(`/t/${task.id}`);
const session = new SessionPage(testPage);
await session.waitForLoad();
const panel = testPage.getByTestId("prepare-progress-panel");
await expect(panel).toBeVisible({ timeout: 30_000 });
// Wait for prepare to settle into a terminal state.
await expect(panel).toHaveAttribute("data-status", /completed|completed_with_error|failed/, {
timeout: 45_000,
});
// Expand to see step content if auto-collapsed.
if ((await panel.getAttribute("data-expanded")) === "false") {
await panel.getByTestId("prepare-progress-toggle").click();
}
const text = await panel.innerText();
// The setup script must be executed exactly once, by the worktree
// manager (recorded as a `script_execution` message). The env
// preparer must NOT also run it via its own "Run setup script"
// prepare step — that's the duplicate-execution path the fix
// closes. Two distinct assertions because the two runners surface
// through different channels (prepare panel vs. session messages).
expect(text).not.toContain("Run setup script");
expect(task.session_id).toBeTruthy();
const { messages } = await apiClient.listSessionMessages(task.session_id!);
// Filter by metadata.script_type === "setup" — the chat surfaces other
// `script_execution` rows too (e.g. agent_boot), and counting all of
// them masked the bug where the setup script never ran.
const scriptMsgs = messages.filter(
(m) =>
(m as { type?: string }).type === "script_execution" &&
((m as { metadata?: { script_type?: string } }).metadata?.script_type ?? "") === "setup",
);
expect(scriptMsgs).toHaveLength(1);
} finally {
// Reset the seed repo so worker-scoped state stays clean for other
// tests sharing the same repository.
await apiClient.updateRepository(seedData.repositoryId, { setup_script: "" }).catch(() => {});
}
});
});