test(FN-4626): complete Step 6 — add worktrunk audit coverage
Fusion-Task-Id: FN-4626 Fusion-Task-Lineage: 04b06bf6-b08c-4ff1-b69a-ab93f6e508b3
This commit is contained in:
committed by
gsxdsm
parent
80382ed911
commit
178bad0d08
@@ -0,0 +1,70 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import type { RunAuditEventInput, TaskStore } from "@fusion/core";
|
||||||
|
import { createRunAuditor } from "../../run-audit.js";
|
||||||
|
import { handleWorktrunkOperationFailure } from "../../worktrunk-failure-handler.js";
|
||||||
|
|
||||||
|
describe("reliability interactions: worktrunk audit correlation", () => {
|
||||||
|
it("keeps runId/taskId aligned across install + create lifecycle events", async () => {
|
||||||
|
const events: RunAuditEventInput[] = [];
|
||||||
|
const store = {
|
||||||
|
recordRunAuditEvent: vi.fn(async (event: RunAuditEventInput) => {
|
||||||
|
events.push(event);
|
||||||
|
}),
|
||||||
|
} as unknown as TaskStore;
|
||||||
|
|
||||||
|
const auditor = createRunAuditor(store, { runId: "run-123", agentId: "agent-1", taskId: "FN-4626", phase: "execute" });
|
||||||
|
|
||||||
|
await auditor.git({
|
||||||
|
type: "worktree:worktrunk-install",
|
||||||
|
target: "/usr/local/bin/worktrunk",
|
||||||
|
metadata: { op: "install", binaryPath: "/usr/local/bin/worktrunk", installSource: "release-binary", durationMs: 90 },
|
||||||
|
});
|
||||||
|
|
||||||
|
await auditor.git({
|
||||||
|
type: "worktree:worktrunk-create",
|
||||||
|
target: "/repo/.worktrees/fn-4626",
|
||||||
|
metadata: { op: "create", binaryPath: "/usr/local/bin/worktrunk", worktreePath: "/repo/.worktrees/fn-4626", durationMs: 35 },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(events.map((event) => event.mutationType)).toEqual([
|
||||||
|
"worktree:worktrunk-install",
|
||||||
|
"worktree:worktrunk-create",
|
||||||
|
]);
|
||||||
|
expect(new Set(events.map((event) => event.runId))).toEqual(new Set(["run-123"]));
|
||||||
|
expect(new Set(events.map((event) => event.taskId))).toEqual(new Set(["FN-4626"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps failure and fallback-native events correlated to the same run/task", async () => {
|
||||||
|
const events: RunAuditEventInput[] = [];
|
||||||
|
const store = {
|
||||||
|
recordRunAuditEvent: vi.fn(async (event: RunAuditEventInput) => {
|
||||||
|
events.push(event);
|
||||||
|
}),
|
||||||
|
pauseTask: vi.fn(async () => undefined),
|
||||||
|
updateTask: vi.fn(async () => undefined),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const runAudit = createRunAuditor(store, { runId: "run-456", agentId: "agent-1", taskId: "FN-4626" });
|
||||||
|
await expect(handleWorktrunkOperationFailure({
|
||||||
|
failure: {
|
||||||
|
op: "create",
|
||||||
|
cause: new Error("create failed"),
|
||||||
|
stderr: "x".repeat(5000),
|
||||||
|
exitCode: 9,
|
||||||
|
binaryPath: "/usr/local/bin/worktrunk",
|
||||||
|
worktreePath: "/repo/.worktrees/fn-4626",
|
||||||
|
},
|
||||||
|
task: { id: "FN-4626", worktrunkFallbackAlertedAt: null } as any,
|
||||||
|
settings: { enabled: true, onFailure: "fallback-native" },
|
||||||
|
store,
|
||||||
|
runAudit,
|
||||||
|
notify: vi.fn(),
|
||||||
|
nativeFallback: vi.fn(async () => ({ path: "/repo/.worktrees/fn-4626", branch: "fusion/fn-4626" })),
|
||||||
|
})).resolves.toEqual(expect.objectContaining({ kind: "fallback-native" }));
|
||||||
|
|
||||||
|
const fallbackEvent = events.find((event) => event.mutationType === "worktree:worktrunk-fallback-native");
|
||||||
|
expect(fallbackEvent?.runId).toBe("run-456");
|
||||||
|
expect(fallbackEvent?.taskId).toBe("FN-4626");
|
||||||
|
expect((fallbackEvent?.metadata as Record<string, unknown>)?.worktreePath).toBe("/repo/.worktrees/fn-4626");
|
||||||
|
});
|
||||||
|
});
|
||||||
89
packages/engine/src/__tests__/run-audit-worktrunk.test.ts
Normal file
89
packages/engine/src/__tests__/run-audit-worktrunk.test.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import type { RunAuditEventInput, TaskStore } from "@fusion/core";
|
||||||
|
import { createRunAuditor } from "../run-audit.js";
|
||||||
|
|
||||||
|
type WorktrunkLifecycleCase = {
|
||||||
|
type:
|
||||||
|
| "worktree:worktrunk-install"
|
||||||
|
| "worktree:worktrunk-create"
|
||||||
|
| "worktree:worktrunk-sync"
|
||||||
|
| "worktree:worktrunk-prune"
|
||||||
|
| "worktree:worktrunk-remove";
|
||||||
|
target: string;
|
||||||
|
metadata: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("run-audit worktrunk lifecycle events", () => {
|
||||||
|
it.each<WorktrunkLifecycleCase>([
|
||||||
|
{
|
||||||
|
type: "worktree:worktrunk-install",
|
||||||
|
target: "/usr/local/bin/worktrunk",
|
||||||
|
metadata: { op: "install", binaryPath: "/usr/local/bin/worktrunk", durationMs: 12, installSource: "cargo" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "worktree:worktrunk-create",
|
||||||
|
target: "/repo/.worktrees/fn-1",
|
||||||
|
metadata: { op: "create", binaryPath: "/usr/local/bin/worktrunk", worktreePath: "/repo/.worktrees/fn-1", durationMs: 31 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "worktree:worktrunk-sync",
|
||||||
|
target: "/repo/.worktrees/fn-1",
|
||||||
|
metadata: { op: "sync", binaryPath: "/usr/local/bin/worktrunk", worktreePath: "/repo/.worktrees/fn-1", durationMs: 44 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "worktree:worktrunk-prune",
|
||||||
|
target: "worktrunk-prune",
|
||||||
|
metadata: { op: "prune", binaryPath: "/usr/local/bin/worktrunk", durationMs: 20, prunedCount: 3 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "worktree:worktrunk-remove",
|
||||||
|
target: "/repo/.worktrees/fn-1",
|
||||||
|
metadata: { op: "remove", binaryPath: "/usr/local/bin/worktrunk", worktreePath: "/repo/.worktrees/fn-1", durationMs: 13 },
|
||||||
|
},
|
||||||
|
])("persists $type with metadata", async ({ type, target, metadata }) => {
|
||||||
|
const recordRunAuditEvent = vi.fn(async (_event: RunAuditEventInput) => undefined);
|
||||||
|
const store = { recordRunAuditEvent } as unknown as TaskStore;
|
||||||
|
const auditor = createRunAuditor(store, { runId: "run-1", agentId: "agent-1", taskId: "FN-1", phase: "execute" });
|
||||||
|
|
||||||
|
await auditor.git({ type, target, metadata });
|
||||||
|
|
||||||
|
expect(recordRunAuditEvent).toHaveBeenCalledTimes(1);
|
||||||
|
expect(recordRunAuditEvent).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
domain: "git",
|
||||||
|
mutationType: type,
|
||||||
|
target,
|
||||||
|
taskId: "FN-1",
|
||||||
|
runId: "run-1",
|
||||||
|
metadata: expect.objectContaining(metadata),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("records long stderr previews without mutating payload content", async () => {
|
||||||
|
const recordRunAuditEvent = vi.fn(async (_event: RunAuditEventInput) => undefined);
|
||||||
|
const auditor = createRunAuditor({ recordRunAuditEvent } as unknown as TaskStore, {
|
||||||
|
runId: "run-1",
|
||||||
|
agentId: "agent-1",
|
||||||
|
taskId: "FN-1",
|
||||||
|
});
|
||||||
|
const longPreview = "x".repeat(5000);
|
||||||
|
|
||||||
|
await auditor.git({
|
||||||
|
type: "worktree:worktrunk-failure",
|
||||||
|
target: "FN-1",
|
||||||
|
metadata: { op: "failure", stderrPreview: longPreview },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(recordRunAuditEvent.mock.calls[0]?.[0]?.metadata?.stderrPreview).toHaveLength(5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("propagates store write failures", async () => {
|
||||||
|
const store = {
|
||||||
|
recordRunAuditEvent: vi.fn(async () => {
|
||||||
|
throw new Error("boom");
|
||||||
|
}),
|
||||||
|
} as unknown as TaskStore;
|
||||||
|
const auditor = createRunAuditor(store, { runId: "run-1", agentId: "agent-1", taskId: "FN-1" });
|
||||||
|
|
||||||
|
await expect(auditor.git({ type: "worktree:worktrunk-create", target: "/repo/.worktrees/fn-1" })).rejects.toThrow("boom");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user