Files
zopu-code/packages/backend/convex/workExecutionWorkflow.test.ts
2026-07-28 16:52:49 +05:30

382 lines
13 KiB
TypeScript

import { convexTest } from "convex-test";
import { anyApi } from "convex/server";
import { describe, expect, test } from "vitest";
import schema from "./schema";
const modules = import.meta.glob("./**/*.ts");
const api = anyApi;
type TestContext = ReturnType<typeof convexTest>;
interface Seeded {
attemptId: string;
runId: string;
t: TestContext;
workId: string;
}
// Seed an in-flight real attempt so each mutation under test starts from a
// realistic running state: Work "executing", Run "running", Attempt "running".
const seedRunningAttempt = async (
overrides: {
attemptStatus?: "queued" | "claimed" | "running" | "terminal";
runStatus?: "ready" | "running" | "terminal" | "cancelled";
} = {}
): Promise<Seeded> => {
const t = convexTest({ modules, schema });
const seeded = await t.run(async (ctx) => {
const organizationId = await ctx.db.insert("organizations", {
createdAt: 1,
createdBy: "user",
kind: "personal",
name: "Personal",
});
const projectId = await ctx.db.insert("projects", {
createdAt: 1,
name: "Zopu",
normalizedSourceUrl: "https://github.com/puter/zopu",
organizationId,
repositoryPath: "puter/zopu",
sourceHost: "github.com",
sourceUrl: "https://github.com/puter/zopu",
updatedAt: 1,
});
const workId = await ctx.db.insert("works", {
createdAt: 1,
objective: "Implement a real slice",
organizationId,
projectId,
status: "executing",
title: "Real execution",
updatedAt: 1,
});
const sliceRowId = await ctx.db.insert("workSlices", {
designVersion: 1,
objective: "Change the repo",
observableBehavior: "A diff exists",
ordinal: 0,
payloadJson: "{}",
sliceId: "slice-1",
status: "running",
title: "Implementation",
workId,
});
const runId = await ctx.db.insert("workRuns", {
createdAt: 1,
designVersion: 1,
executionKind: "real",
kitId: "coding-v0",
kitVersion: "1",
scenario: "success",
sliceId: "slice-1",
sliceRowId,
status: overrides.runStatus ?? "running",
workId,
});
const attemptId = await ctx.db.insert("workAttempts", {
number: 1,
runId,
status: overrides.attemptStatus ?? "running",
workId,
workspaceKey: "workspace-1",
});
return { attemptId, runId, workId };
});
return { ...seeded, t };
};
const successResult = {
baseRevision: "base123",
candidateRevision: "candidate456",
changedFiles: ["src/index.ts"],
diff: "+export const ready = true;",
environmentId: "workspace-1",
events: [
{
kind: "runtime.completed",
message: "completed",
metadata: {},
occurredAt: 2,
sequence: 0,
},
],
summary: "Changed one file",
};
describe("real work execution persistence", () => {
test("records revisions, activity, diff, and terminal state atomically", async () => {
const { t, ...seeded } = await seedRunningAttempt();
await t.mutation(api.workExecutionWorkflow.completeAttempt, {
attemptId: seeded.attemptId,
result: successResult,
});
const state = await t.run(async (ctx) => ({
artifacts: await ctx.db.query("workArtifacts").collect(),
attempt: await ctx.db.get(seeded.attemptId as any),
run: await ctx.db.get(seeded.runId as any),
work: await ctx.db.get(seeded.workId as any),
}));
expect(state.attempt?.classification).toBe("Succeeded");
expect(state.run).toMatchObject({
baseRevision: "base123",
candidateRevision: "candidate456",
terminalClassification: "Succeeded",
});
expect(state.work?.status).toBe("completed");
expect(state.artifacts[0]).toMatchObject({
kind: "diff",
sourceRevision: "candidate456",
});
});
});
describe("classified failure mapping", () => {
test("maps a transient runtime reason to a retryable classification", async () => {
const { t, ...seeded } = await seedRunningAttempt();
await t.mutation(api.workExecutionWorkflow.failAttempt, {
attemptId: seeded.attemptId,
reason: "ProviderUnavailable",
retryable: true,
summary: "model provider timed out",
});
const attempt = await t.run((ctx) => ctx.db.get(seeded.attemptId as any));
expect(attempt?.classification).toBe("RetryableFailure");
expect(attempt?.failureReason).toBe("ProviderUnavailable");
});
test("queues a new attempt without terminalizing a retryable run", async () => {
const { t, ...seeded } = await seedRunningAttempt();
await t.mutation(api.workExecutionWorkflow.failAttempt, {
attemptId: seeded.attemptId,
reason: "ProviderUnavailable",
retryable: true,
summary: "provider unavailable",
});
const state = await t.run(async (ctx) => ({
attempts: await ctx.db.query("workAttempts").collect(),
run: await ctx.db.get(seeded.runId as any),
work: await ctx.db.get(seeded.workId as any),
}));
expect(state.attempts).toHaveLength(2);
expect(state.attempts[1]).toMatchObject({
number: 2,
status: "queued",
workspaceKey: "workspace-1",
});
expect(state.run?.status).toBe("running");
expect(state.work?.status).toBe("executing");
});
test("maps an authentication reason to a permanent failure", async () => {
const { t, ...seeded } = await seedRunningAttempt();
await t.mutation(api.workExecutionWorkflow.failAttempt, {
attemptId: seeded.attemptId,
reason: "Authentication",
retryable: false,
summary: "token rejected",
});
const state = await t.run(async (ctx) => ({
attempt: await ctx.db.get(seeded.attemptId as any),
run: await ctx.db.get(seeded.runId as any),
work: await ctx.db.get(seeded.workId as any),
}));
expect(state.attempt?.classification).toBe("PermanentFailure");
expect(state.attempt?.failureReason).toBe("Authentication");
expect(state.run?.terminalClassification).toBe("PermanentFailure");
expect(state.work?.status).toBe("failed");
});
});
describe("cancellation fencing", () => {
test("a cancelled attempt cannot later settle success", async () => {
const { t, ...seeded } = await seedRunningAttempt();
// Simulate cancellation marking the attempt/run terminal as Cancelled.
await t.run(async (ctx) => {
await ctx.db.patch(seeded.attemptId as any, {
classification: "Cancelled",
endedAt: 5,
status: "terminal",
summary: "Execution cancelled",
});
await ctx.db.patch(seeded.runId as any, {
endedAt: 5,
status: "cancelled",
terminalClassification: "Cancelled",
terminalSummary: "Execution cancelled",
});
});
// A late-arriving completion must be ignored.
await t.mutation(api.workExecutionWorkflow.completeAttempt, {
attemptId: seeded.attemptId,
result: successResult,
});
const attempt = await t.run((ctx) => ctx.db.get(seeded.attemptId as any));
expect(attempt?.classification).toBe("Cancelled");
expect(attempt?.status).toBe("terminal");
});
test("a late failure does not overwrite a cancelled run", async () => {
const { t, ...seeded } = await seedRunningAttempt();
await t.run(async (ctx) => {
await ctx.db.patch(seeded.attemptId as any, {
classification: "Cancelled",
endedAt: 5,
status: "terminal",
});
await ctx.db.patch(seeded.runId as any, { status: "cancelled" });
});
await t.mutation(api.workExecutionWorkflow.failAttempt, {
attemptId: seeded.attemptId,
reason: "HarnessFailed",
retryable: true,
summary: "late failure",
});
const attempt = await t.run((ctx) => ctx.db.get(seeded.attemptId as any));
expect(attempt?.classification).toBe("Cancelled");
});
});
describe("empty-change rejection", () => {
test("a no-op result with no changed files fails as permanent", async () => {
const { t, ...seeded } = await seedRunningAttempt();
await t.mutation(api.workExecutionWorkflow.completeAttempt, {
attemptId: seeded.attemptId,
result: {
...successResult,
changedFiles: [],
candidateRevision: "base123",
},
});
const state = await t.run(async (ctx) => ({
attempt: await ctx.db.get(seeded.attemptId as any),
work: await ctx.db.get(seeded.workId as any),
}));
expect(state.attempt?.classification).toBe("PermanentFailure");
expect(state.attempt?.failureReason).toBe("InvalidInput");
expect(state.work?.status).toBe("failed");
});
test("identical base and candidate revisions are rejected", async () => {
const { t, ...seeded } = await seedRunningAttempt();
await t.mutation(api.workExecutionWorkflow.completeAttempt, {
attemptId: seeded.attemptId,
result: {
...successResult,
baseRevision: "same",
candidateRevision: "same",
},
});
const attempt = await t.run((ctx) => ctx.db.get(seeded.attemptId as any));
expect(attempt?.classification).toBe("PermanentFailure");
});
});
describe("attempt re-entry", () => {
test("markAttemptRunning re-claims an already-running attempt", async () => {
const { t, ...seeded } = await seedRunningAttempt();
// First claim (already "running" from seed) must still succeed so a
// workflow replay can re-enter the same live attempt.
const first = await t.mutation(
api.workExecutionWorkflow.markAttemptRunning,
{ attemptId: seeded.attemptId }
);
expect(first).toBe(true);
const attempt = await t.run((ctx) => ctx.db.get(seeded.attemptId as any));
expect(attempt?.status).toBe("running");
expect(attempt?.leaseExpiresAt).toBeGreaterThan(0);
});
test("a terminal attempt is not re-runnable", async () => {
const { t, ...seeded } = await seedRunningAttempt({
attemptStatus: "terminal",
});
const result = await t.mutation(
api.workExecutionWorkflow.markAttemptRunning,
{ attemptId: seeded.attemptId }
);
expect(result).toBe(false);
});
test("a cancelled run cannot re-enter a queued attempt", async () => {
const { t, ...seeded } = await seedRunningAttempt({
attemptStatus: "queued",
runStatus: "cancelled",
});
const result = await t.mutation(
api.workExecutionWorkflow.markAttemptRunning,
{ attemptId: seeded.attemptId }
);
expect(result).toBe(false);
const attempt = await t.run((ctx) => ctx.db.get(seeded.attemptId as any));
expect(attempt?.status).toBe("queued");
});
});
describe("forge credential validation", () => {
const identity = { tokenIdentifier: "https://convex.test|forge-user" };
const seedForgableProject = async (
provider: "github" | "gitea",
sourceHost: string
) => {
const t = convexTest({ modules, schema }).withIdentity(identity);
const ids = await t.run(async (ctx) => {
const organizationId = await ctx.db.insert("organizations", {
createdAt: 1,
createdBy: identity.tokenIdentifier,
kind: "personal",
name: "Personal",
});
await ctx.db.insert("organizationMembers", {
createdAt: 1,
organizationId,
role: "owner",
userId: identity.tokenIdentifier,
});
const projectId = await ctx.db.insert("projects", {
createdAt: 1,
name: "Zopu",
normalizedSourceUrl: `https://${sourceHost}/puter/zopu`,
organizationId,
repositoryPath: "puter/zopu",
sourceHost,
sourceUrl: `https://${sourceHost}/puter/zopu`,
updatedAt: 1,
});
const connectionId = await ctx.db.insert("gitConnections", {
connectedAt: 1,
credentialCiphertext: "x",
credentialIv: "y",
credentialKind: provider === "github" ? "oauth" : "token",
organizationId,
provider,
serverUrl: `https://${sourceHost}`,
updatedAt: 1,
username: "zopu",
});
return { connectionId, projectId };
});
return { t, ids };
};
test("rejects a git connection whose provider mismatches the project forge", async () => {
// Gitea token attached to a GitHub-hosted project.
const { t, ids } = await seedForgableProject("gitea", "github.com");
await expect(
t.mutation(api.gitConnectionData.attachToProject, {
connectionId: ids.connectionId,
projectId: ids.projectId,
})
).rejects.toThrow(/does not match this project's forge/u);
});
test("accepts a matching provider for the project forge", async () => {
const { t, ids } = await seedForgableProject("github", "github.com");
const result = await t.mutation(api.gitConnectionData.attachToProject, {
connectionId: ids.connectionId,
projectId: ids.projectId,
});
expect(result).toMatchObject({ attached: true });
});
});