diff --git a/tests/runner-e2e/daytona-image.test.ts b/tests/runner-e2e/daytona-image.test.ts index c046f37b4c3..f7d1ce33b77 100644 --- a/tests/runner-e2e/daytona-image.test.ts +++ b/tests/runner-e2e/daytona-image.test.ts @@ -32,12 +32,14 @@ describe("runner E2E Daytona image contract", () => { } for (const transport of ["dial_ws_loopback", "dial_wss", "listen_ws"]) { expect(dockerfile).toContain(transport); - expect(workflow).toContain(transport); } - expect(workflow).toContain('"binaryContractVersion":2'); + expect(dockerfile).toContain('metadata="$(paperclip-runnerd --build-metadata)"'); + expect(dockerfile).toContain("provider-pack.json"); expect(workflow).toContain("--platform linux/amd64"); expect(workflow).toContain("cosign sign --yes"); - expect(workflow).toContain(".runnerSourceRevision == $revision"); + expect(workflow).toContain("docker image inspect"); + expect(workflow).toContain('.Config.User == "daytona"'); + expect(workflow).toContain("PAPERCLIP_RUNNER_PROVIDER_PACK_ROOT="); expect(workflow).toContain("anonymous_config"); }); }); diff --git a/tests/runner-e2e/live-fixtures.test.ts b/tests/runner-e2e/live-fixtures.test.ts new file mode 100644 index 00000000000..bb0b8815b6d --- /dev/null +++ b/tests/runner-e2e/live-fixtures.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from "vitest"; +import type { RunnerApi } from "./api.js"; +import { runnerMatrix } from "./catalog.js"; +import { setupLiveFixtures } from "./live-fixtures.js"; + +describe("live runner fixtures", () => { + it("installs the Daytona provider through the public API before creating its environment", async () => { + const calls: string[] = []; + const api = { + async post(path: string, data?: Record) { + calls.push(`POST ${path}`); + if (path === "/api/plugins/install") { + expect(data).toMatchObject({ isLocalPath: true }); + expect(data?.packageName).toEqual( + expect.stringContaining( + "packages/plugins/sandbox-providers/daytona", + ), + ); + return { + id: "plugin-daytona", + pluginKey: "paperclip.daytona-sandbox-provider", + status: "ready", + }; + } + if (path === "/api/companies") { + return { id: "company-1", name: "Runner E2E" }; + } + if (path.endsWith("/environments")) { + expect(calls).toContain("POST /api/plugins/install"); + return { id: "environment-1", driver: "sandbox" }; + } + if (path.endsWith("/agents")) { + return { id: "agent-1", name: "Agent", companyId: "company-1" }; + } + throw new Error(`Unexpected POST ${path}`); + }, + async postSensitive(path: string, data?: Record) { + calls.push(`POST ${path}`); + return { id: `secret-${String(data?.key).toLowerCase()}` }; + }, + async delete(path: string) { + calls.push(`DELETE ${path}`); + }, + } as unknown as RunnerApi; + const execution = runnerMatrix.find( + (candidate) => + candidate.id === "legacy-codex.daytona.message-marker", + ); + expect(execution).toBeDefined(); + + const fixtures = await setupLiveFixtures({ + api, + execution: execution!, + executionNonce: "nonce", + workspacePath: "/tmp/workspace", + credentials: { + OPENAI_API_KEY: "openai-test-value", + DAYTONA_API_KEY: "daytona-test-value", + }, + daytonaImage: "ghcr.io/paperclip/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }); + + expect(calls.indexOf("POST /api/plugins/install")).toBeLessThan( + calls.indexOf("POST /api/companies/company-1/environments"), + ); + await fixtures.teardown(); + expect(calls).toContain( + "DELETE /api/environments/environment-1?destroyReusableSandboxLeases=true", + ); + }); +}); diff --git a/tests/runner-e2e/live-fixtures.ts b/tests/runner-e2e/live-fixtures.ts index 0efa5c7f434..4ab928c4000 100644 --- a/tests/runner-e2e/live-fixtures.ts +++ b/tests/runner-e2e/live-fixtures.ts @@ -1,3 +1,4 @@ +import path from "node:path"; import { FixtureRegistry } from "./fixture-registry.js"; import type { RunnerApi } from "./api.js"; import type { @@ -16,6 +17,11 @@ interface CompanyRecord { interface SecretRecord { id: string; } +interface PluginRecord { + id: string; + pluginKey: string; + status: string; +} interface EnvironmentRecord { id: string; driver: string; @@ -74,6 +80,26 @@ export async function setupLiveFixtures(input: { const { api, execution } = input; const registry = new FixtureRegistry(); + if (execution.environment.id === "daytona") { + registry.register({ + id: "sandbox-provider", + async setup() { + return api.post("/api/plugins/install", { + packageName: path.resolve( + import.meta.dirname, + "../../packages/plugins/sandbox-providers/daytona", + ), + isLocalPath: true, + }); + }, + async teardown() { + // The plugin is installed only in the isolated instance/database. The + // launcher removes that complete instance after the environment lease + // has been destroyed, so no global uninstall mutation is necessary. + }, + }); + } + registry.register({ id: "company", async setup() { @@ -120,7 +146,13 @@ export async function setupLiveFixtures(input: { registry.register({ id: "environment", - dependencies: ["company", "secrets"], + dependencies: [ + "company", + "secrets", + ...(execution.environment.id === "daytona" + ? ["sandbox-provider"] + : []), + ], async setup(resolved) { const company = value(resolved, "company"); const secretRefs = value(resolved, "secrets");