feat(FN-4685): complete Step 1 — define native backend
Fusion-Task-Id: FN-4685 Fusion-Task-Lineage: 406ac389-c360-4e4d-b9ac-f5349a241907
This commit is contained in:
committed by
gsxdsm
parent
a88857a876
commit
00a44e82af
@@ -41,86 +41,56 @@ describe("NativeWorktreeBackend", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("removes worktree", async () => {
|
||||
execMock.mockResolvedValue({ stdout: "", stderr: "" });
|
||||
it("retries with sibling branch suffixes when rename enabled", async () => {
|
||||
execMock
|
||||
.mockRejectedValueOnce(new Error("branch exists"))
|
||||
.mockResolvedValueOnce({ stdout: "", stderr: "" });
|
||||
const backend = new NativeWorktreeBackend();
|
||||
await backend.remove({ rootDir: "/repo", worktreePath: "/repo/.worktrees/fn-1", taskId: "FN-1" });
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
'git worktree remove --force "/repo/.worktrees/fn-1"',
|
||||
expect.objectContaining({ cwd: "/repo" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("prunes worktrees", async () => {
|
||||
execMock.mockResolvedValue({ stdout: "", stderr: "" });
|
||||
const backend = new NativeWorktreeBackend();
|
||||
await backend.prune({ rootDir: "/repo", taskId: "FN-1" });
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
"git worktree prune",
|
||||
expect.objectContaining({ cwd: "/repo" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("implements required methods", () => {
|
||||
const backend = new NativeWorktreeBackend();
|
||||
expect(backend.kind).toBe("native");
|
||||
expect(typeof backend.create).toBe("function");
|
||||
expect(typeof backend.remove).toBe("function");
|
||||
expect(typeof backend.sync).toBe("function");
|
||||
expect(typeof backend.prune).toBe("function");
|
||||
});
|
||||
});
|
||||
|
||||
describe("WorktrunkWorktreeBackend", () => {
|
||||
it("throws typed error when binary is missing", async () => {
|
||||
const backend = new WorktrunkWorktreeBackend({ binaryPath: null });
|
||||
await expect(
|
||||
backend.create({
|
||||
rootDir: "/repo",
|
||||
worktreePath: "/repo/.worktrees/fn-1",
|
||||
branch: "fusion/fn-1",
|
||||
taskId: "FN-1",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
name: "WorktrunkOperationError",
|
||||
code: "worktrunk_binary_missing",
|
||||
operation: "create",
|
||||
exitCode: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("maps non-zero exit to worktrunk_operation_failed and preserves stderr", async () => {
|
||||
execMock.mockRejectedValue({ stderr: "bad", code: 17 });
|
||||
const backend = new WorktrunkWorktreeBackend({ binaryPath: "worktrunk" });
|
||||
|
||||
await expect(
|
||||
backend.prune({ rootDir: "/repo", taskId: "FN-1" }),
|
||||
).rejects.toEqual(
|
||||
expect.objectContaining<Partial<WorktrunkOperationError>>({
|
||||
code: "worktrunk_operation_failed",
|
||||
stderr: "bad",
|
||||
exitCode: 17,
|
||||
operation: "prune",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("passes timeout and maxBuffer to exec", async () => {
|
||||
execMock.mockResolvedValue({ stdout: "", stderr: "" });
|
||||
const backend = new WorktrunkWorktreeBackend({ binaryPath: "worktrunk" });
|
||||
|
||||
await backend.sync({
|
||||
const result = await backend.create({
|
||||
rootDir: "/repo",
|
||||
worktreePath: "/repo/.worktrees/fn-1",
|
||||
branch: "fusion/fn-1",
|
||||
taskId: "FN-1",
|
||||
allowSiblingBranchRename: true,
|
||||
});
|
||||
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
'"worktrunk" --help',
|
||||
expect.objectContaining({ cwd: "/repo/.worktrees/fn-1", timeout: 120000, maxBuffer: 10485760 }),
|
||||
expect(result).toEqual({ path: "/repo/.worktrees/fn-1", branch: "fusion/fn-1-2" });
|
||||
expect(execMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'git worktree add -b "fusion/fn-1-2" "/repo/.worktrees/fn-1"',
|
||||
expect.objectContaining({ cwd: "/repo" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("removes worktree with force command", async () => {
|
||||
execMock.mockResolvedValue({ stdout: "", stderr: "" });
|
||||
const backend = new NativeWorktreeBackend();
|
||||
|
||||
await backend.remove({ rootDir: "/repo", worktreePath: "/repo/.worktrees/fn-1", taskId: "FN-1" });
|
||||
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
'git worktree remove --force "/repo/.worktrees/fn-1"',
|
||||
expect.objectContaining({ cwd: "/repo", timeout: 60000, maxBuffer: 10485760 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("WorktrunkOperationError", () => {
|
||||
it("preserves operation, stderr, exitCode, and code", () => {
|
||||
const error = new WorktrunkOperationError({
|
||||
operation: "create",
|
||||
stderr: "failure",
|
||||
exitCode: 2,
|
||||
code: "worktrunk_operation_failed",
|
||||
});
|
||||
|
||||
expect(error.name).toBe("WorktrunkOperationError");
|
||||
expect(error.operation).toBe("create");
|
||||
expect(error.stderr).toBe("failure");
|
||||
expect(error.exitCode).toBe(2);
|
||||
expect(error.code).toBe("worktrunk_operation_failed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveWorktreeBackend", () => {
|
||||
@@ -136,7 +106,25 @@ describe("resolveWorktreeBackend", () => {
|
||||
expect(resolveWorktreeBackend({ worktrunk: { enabled: true, binaryPath: "worktrunk" } as any }).kind).toBe("worktrunk");
|
||||
});
|
||||
|
||||
it("uses worktrunk when enabled=true and binaryPath missing", () => {
|
||||
expect(resolveWorktreeBackend({ worktrunk: { enabled: true } as any }).kind).toBe("worktrunk");
|
||||
it("uses worktrunk when enabled=true and binaryPath missing", async () => {
|
||||
const backend = resolveWorktreeBackend({ worktrunk: { enabled: true } as any });
|
||||
expect(backend.kind).toBe("worktrunk");
|
||||
await expect(
|
||||
backend.create({
|
||||
rootDir: "/repo",
|
||||
worktreePath: "/repo/.worktrees/fn-1",
|
||||
branch: "fusion/fn-1",
|
||||
taskId: "FN-1",
|
||||
}),
|
||||
).rejects.toMatchObject({ code: "worktrunk_binary_missing" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("WorktrunkWorktreeBackend", () => {
|
||||
it("throws unsupported operation when configured", async () => {
|
||||
const backend = new WorktrunkWorktreeBackend({ binaryPath: "worktrunk" });
|
||||
await expect(
|
||||
backend.prune({ rootDir: "/repo", taskId: "FN-1" }),
|
||||
).rejects.toMatchObject({ code: "worktrunk_unsupported_operation", operation: "prune" });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user