feat(FN-4410): complete Step 1 — dispatch reclaim telemetry wiring
Fusion-Task-Id: FN-4410 Fusion-Task-Lineage: 306d4fe0-d024-49dd-86b4-fb98d4ceff59
This commit is contained in:
@@ -1859,6 +1859,46 @@ describe("TaskExecutor dependency-based worktree creation", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("records branch:auto-reclaim run-audit event when pooled prepare returns reclaimed worktree", async () => {
|
||||||
|
const pool = new WorktreePool();
|
||||||
|
pool.release("/tmp/test/.worktrees/idle-wt");
|
||||||
|
mockedExistsSync.mockImplementation((p) => p === "/tmp/test/.worktrees/idle-wt" || p === "/tmp/test/.worktrees/live-wt");
|
||||||
|
|
||||||
|
vi.spyOn(pool, "prepareForTask").mockResolvedValue({
|
||||||
|
branch: "fusion/fn-066",
|
||||||
|
worktreePath: "/tmp/test/.worktrees/live-wt",
|
||||||
|
reclaimed: true,
|
||||||
|
existingTipSha: "abc123def456",
|
||||||
|
strandedCommitCount: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = createMockStore();
|
||||||
|
store.recordRunAuditEvent = vi.fn().mockResolvedValue(undefined);
|
||||||
|
store.getSettings.mockResolvedValue({
|
||||||
|
maxConcurrent: 2,
|
||||||
|
maxWorktrees: 4,
|
||||||
|
pollIntervalMs: 15000,
|
||||||
|
groupOverlappingFiles: false,
|
||||||
|
autoMerge: false,
|
||||||
|
recycleWorktrees: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const executor = new TaskExecutor(store, "/tmp/test", { pool });
|
||||||
|
await executor.execute(makeTask({ id: "FN-066", worktree: null, branch: null }));
|
||||||
|
|
||||||
|
expect(store.recordRunAuditEvent).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
mutationType: "branch:auto-reclaim",
|
||||||
|
target: "fusion/fn-066",
|
||||||
|
metadata: expect.objectContaining({
|
||||||
|
taskId: "FN-066",
|
||||||
|
trigger: "dispatch-preflight",
|
||||||
|
worktreePath: "/tmp/test/.worktrees/live-wt",
|
||||||
|
existingTipSha: "abc123def456",
|
||||||
|
strandedCommitCount: 2,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
it("stores suffixed branch name when pool returns a different name", async () => {
|
it("stores suffixed branch name when pool returns a different name", async () => {
|
||||||
const pool = new WorktreePool();
|
const pool = new WorktreePool();
|
||||||
pool.release("/tmp/test/.worktrees/idle-wt");
|
pool.release("/tmp/test/.worktrees/idle-wt");
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ describe("acquireTaskWorktree", () => {
|
|||||||
|
|
||||||
it("acquires from pool when enabled", async () => {
|
it("acquires from pool when enabled", async () => {
|
||||||
const prepareForTask = vi.fn().mockResolvedValue({ branch: "fusion/fn-1", worktreePath: "/tmp/pooled", reclaimed: false });
|
const prepareForTask = vi.fn().mockResolvedValue({ branch: "fusion/fn-1", worktreePath: "/tmp/pooled", reclaimed: false });
|
||||||
|
const release = vi.fn();
|
||||||
const result = await acquireTaskWorktree({
|
const result = await acquireTaskWorktree({
|
||||||
task,
|
task,
|
||||||
rootDir: process.cwd(),
|
rootDir: process.cwd(),
|
||||||
@@ -49,10 +50,11 @@ describe("acquireTaskWorktree", () => {
|
|||||||
pool: {
|
pool: {
|
||||||
acquire: () => "/tmp/pooled",
|
acquire: () => "/tmp/pooled",
|
||||||
prepareForTask,
|
prepareForTask,
|
||||||
release: vi.fn(),
|
release,
|
||||||
} as any,
|
} as any,
|
||||||
createWorktree: vi.fn(),
|
createWorktree: vi.fn(),
|
||||||
});
|
});
|
||||||
|
expect(release).not.toHaveBeenCalled();
|
||||||
expect(result.source).toBe("pool");
|
expect(result.source).toBe("pool");
|
||||||
expect(prepareForTask).toHaveBeenCalledWith(
|
expect(prepareForTask).toHaveBeenCalledWith(
|
||||||
"/tmp/pooled",
|
"/tmp/pooled",
|
||||||
@@ -63,6 +65,30 @@ describe("acquireTaskWorktree", () => {
|
|||||||
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: "/tmp/pooled", branch: "fusion/fn-1" });
|
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: "/tmp/pooled", branch: "fusion/fn-1" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("releases acquired pooled worktree when prepareForTask returns reclaimed path", async () => {
|
||||||
|
const release = vi.fn();
|
||||||
|
await acquireTaskWorktree({
|
||||||
|
task,
|
||||||
|
rootDir: process.cwd(),
|
||||||
|
store,
|
||||||
|
settings: { recycleWorktrees: true } as any,
|
||||||
|
pool: {
|
||||||
|
acquire: () => "/tmp/pooled",
|
||||||
|
prepareForTask: vi.fn().mockResolvedValue({
|
||||||
|
branch: "fusion/fn-1",
|
||||||
|
worktreePath: "/tmp/live-existing",
|
||||||
|
reclaimed: true,
|
||||||
|
existingTipSha: "abc123",
|
||||||
|
strandedCommitCount: 2,
|
||||||
|
}),
|
||||||
|
release,
|
||||||
|
} as any,
|
||||||
|
createWorktree: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(release).toHaveBeenCalledWith("/tmp/pooled");
|
||||||
|
});
|
||||||
|
|
||||||
it("creates fresh when pool disabled", async () => {
|
it("creates fresh when pool disabled", async () => {
|
||||||
const createWorktree = vi.fn().mockResolvedValue({ path: "/tmp/new", branch: "fusion/fn-1" });
|
const createWorktree = vi.fn().mockResolvedValue({ path: "/tmp/new", branch: "fusion/fn-1" });
|
||||||
const result = await acquireTaskWorktree({
|
const result = await acquireTaskWorktree({
|
||||||
|
|||||||
@@ -154,6 +154,9 @@ export async function acquireTaskWorktree(opts: AcquireTaskWorktreeOptions): Pro
|
|||||||
const prepared = typeof preparedRaw === "string"
|
const prepared = typeof preparedRaw === "string"
|
||||||
? { branch: preparedRaw, worktreePath: pooled, reclaimed: false as const }
|
? { branch: preparedRaw, worktreePath: pooled, reclaimed: false as const }
|
||||||
: preparedRaw;
|
: preparedRaw;
|
||||||
|
if (prepared.reclaimed && prepared.worktreePath !== pooled) {
|
||||||
|
pool.release(pooled);
|
||||||
|
}
|
||||||
worktreePath = prepared.worktreePath;
|
worktreePath = prepared.worktreePath;
|
||||||
branch = prepared.branch;
|
branch = prepared.branch;
|
||||||
acquiredFromPool = true;
|
acquiredFromPool = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user