patch.moi/apps/patch/test/flow.test.ts
matamune 81ea91e387
Some checks failed
check / check (push) Failing after 3s
Adopt codex-flows 0.3.5 surfaces
2026-05-18 00:07:15 +00:00

147 lines
4.6 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import type { FlowRunView } from "@peezy.tech/codex-flows/flow-runtime/client";
import {
maintenanceAttemptForWorkspaceDispatch,
maintenanceAttemptWithWorkspaceRuns,
patchUpstreamReleaseEvent,
} from "../src/flow";
import type {
CandidateRefRecord,
FlowDispatchRecord,
MaintenanceAttemptRecord,
} from "../src/types";
describe("maintenance attempt sync", () => {
test("aggregates multi-run fanout statuses and candidate refs", () => {
const attempt = baseAttempt();
const next = maintenanceAttemptWithWorkspaceRuns(attempt, [
flowRun("run-completed", "completed", [candidate("refs/heads/a", "aaa")]),
flowRun("run-changed", "changed", [candidate("refs/heads/b", "bbb")]),
], "2026-05-16T00:10:00.000Z");
expect(next.status).toBe("changed");
expect(next.workspaceRunIds).toEqual(["run-completed", "run-changed"]);
expect(next.workspaceRunStatuses).toEqual({
"run-completed": "completed",
"run-changed": "changed",
});
expect(next.candidateRefs).toMatchObject([
{ ref: "refs/heads/a", sha: "aaa" },
{ ref: "refs/heads/b", sha: "bbb" },
]);
expect(next.completedAt).toBe("2026-05-16T00:10:00.000Z");
});
test("uses failure precedence across partial fanout", () => {
expect(statusFor(["completed", "skipped"])).toBe("completed");
expect(statusFor(["skipped", "skipped"])).toBe("skipped");
expect(statusFor(["completed", "changed"])).toBe("changed");
expect(statusFor(["changed", "failed"])).toBe("failed");
expect(statusFor(["failed", "blocked"])).toBe("blocked");
expect(statusFor(["blocked", "needs_intervention"])).toBe("needs_intervention");
});
test("preserves successful candidates when another run fails", () => {
const next = maintenanceAttemptWithWorkspaceRuns(baseAttempt(), [
flowRun("run-ok", "completed", [candidate("refs/heads/candidate", "abc")]),
flowRun("run-failed", "failed", [], "release verification failed"),
]);
expect(next.status).toBe("failed");
expect(next.error).toBe("release verification failed");
expect(next.candidateRefs).toMatchObject([
{ ref: "refs/heads/candidate", sha: "abc" },
]);
});
test("records replay attempts with workspace run results", () => {
const event = patchUpstreamReleaseEvent({
repo: "openai/codex",
tag: "rust-v0.130.0",
receivedAt: "2026-05-16T00:00:00.000Z",
});
const record: FlowDispatchRecord = {
eventId: event.id,
eventType: event.type,
operation: "replay",
target: "workspace-backend",
transport: "workspace-ws",
workspaceBackendUrl: "ws://127.0.0.1:3586",
status: "dispatched",
runIds: ["run-a", "run-b"],
matched: 2,
createdAt: "2026-05-16T00:05:00.000Z",
};
const attempt = maintenanceAttemptForWorkspaceDispatch(event, record, [
flowRun("run-a", "completed"),
flowRun("run-b", "changed", [candidate("refs/heads/codex-candidate", "def")]),
]);
expect(attempt.id).toBe(`${event.id}:replay:${record.createdAt}`);
expect(attempt.operation).toBe("replay");
expect(attempt.status).toBe("changed");
expect(attempt.upstreamRepo).toBe("openai/codex");
expect(attempt.upstreamTag).toBe("rust-v0.130.0");
expect(attempt.workspaceRunIds).toEqual(["run-a", "run-b"]);
expect(attempt.candidateRefs).toMatchObject([
{ ref: "refs/heads/codex-candidate", sha: "def" },
]);
});
});
function statusFor(statuses: string[]): string {
return maintenanceAttemptWithWorkspaceRuns(
baseAttempt(),
statuses.map((status, index) => flowRun(`run-${index}`, status)),
).status;
}
function baseAttempt(): MaintenanceAttemptRecord {
return {
id: "attempt-1",
eventId: "event-1",
eventType: "upstream.release",
operation: "dispatch",
status: "started",
upstreamRepo: "openai/codex",
upstreamTag: "rust-v0.130.0",
workspaceRunIds: [],
candidateRefs: [],
createdAt: "2026-05-16T00:00:00.000Z",
updatedAt: "2026-05-16T00:00:00.000Z",
};
}
function flowRun(
id: string,
status: string,
candidateRefs: CandidateRefRecord[] = [],
message = `${id} ${status}`,
): FlowRunView {
return {
id,
eventId: "event-1",
flowName: "test-flow",
stepName: id,
status,
effectiveStatus: status,
completedAt: "2026-05-16T00:10:00.000Z",
resultPayload: {
status,
message,
artifacts: { candidateRefs },
},
} as FlowRunView;
}
function candidate(ref: string, sha: string): CandidateRefRecord {
return {
kind: "branch",
repo: "peezy-tech/codex",
remote: "local",
ref,
sha,
pushed: false,
};
}