Files
hyperframes/packages/gcp-cloud-run/src/sdk/renderToCloudRun.test.ts
T
James Russo 1d636f603c refactor(producer): share plan execution builder (#2906)
## What

Refactor distributed planning around one shared local execution-plan builder:

- `buildLocalExecutionPlan()` now owns compile/probe/extract/audio/freeze.
- Legacy `plan()` remains a deprecated v1 transport wrapper.
- Plan v2 calls the shared builder directly and publishes through the existing manifest/CAS contract.
- Add neutral `createPlanV2FromExecutionPlan()`, `publishPlanV2FromExecutionPlan()`, `getPlanV2ExecutionPlanHash()`, and `PLAN_PROTOCOL_V1` names.
- Retain deprecated v1-named exports and wire aliases.
- Recommend explicit Plan v2 opt-in for new producer, Lambda, and Cloud Run integrations.

## Why

Plan v2 previously looked like it invoked a v1 planner even though v1 and v2 share the same frozen local execution representation. This removes that migration-era coupling while preserving the public minor-version compatibility contract.

## How

The shared builder returns neutral internal execution-plan fields. The v1 wrapper maps those fields back to the existing `PlanResult`; the v2 publisher consumes them directly.

Compatibility is intentional and covered by exact shape tests:

- omitted `planProtocol` still serializes/selects `"v1"`;
- v1 layouts, descriptor-less decoding, event unions, workflow branches, and exports remain;
- the v1 descriptor JSON is byte-identical and `CURRENT_PLAN_PROTOCOL` is an identity-preserving alias;
- v2 manifest bytes, key order, hash framing, and `sourcePlanV1Hash` wire key remain unchanged;
- no enumerable neutral hash field was added to manifests or returned result objects;
- v1/v2 result objects, cloud event payloads, and SDK handle key sets remain unchanged.

## Test plan

- Focused Plan v1/v2/protocol/export/size compatibility: 141 passed
- `@hyperframes/core`: 1,419 passed
- `@hyperframes/producer` unit lane: 990 passed
- `@hyperframes/aws-lambda`: 140 passed
- `@hyperframes/gcp-cloud-run`: 101 passed
- Producer, Lambda, and Cloud Run typechecks
- Repository-wide lint, format check, workspace/package-subpath checks
- Full workspace build
- `git diff --check`

- [x] Unit tests added/updated
- [ ] Manual testing performed
- [x] Documentation updated (if applicable)
2026-07-30 17:41:02 -07:00

155 lines
5.1 KiB
TypeScript

/**
* `renderToCloudRun` unit tests — argument assembly, required-field
* validation, and the CreateExecution call over a fake ExecutionsClient.
*/
import { describe, expect, it } from "bun:test";
import type { SerializableDistributedRenderConfig } from "../events.js";
import { type ExecutionsClientLike, renderToCloudRun } from "./renderToCloudRun.js";
import type { SiteHandle } from "./deploySite.js";
const config = {
fps: 30,
width: 1920,
height: 1080,
format: "mp4",
} as SerializableDistributedRenderConfig;
const site: SiteHandle = {
siteId: "abc",
bucketName: "b",
projectGcsUri: "gs://b/sites/abc/project.tar.gz",
bytes: 100,
uploadedAt: "2026-06-06T00:00:00Z",
uploaded: true,
};
class FakeExecutions implements ExecutionsClientLike {
lastArgument: string | null = null;
lastParent: string | null = null;
workflowPath(project: string, location: string, workflow: string): string {
return `projects/${project}/locations/${location}/workflows/${workflow}`;
}
async createExecution(req: {
parent: string;
execution: { argument: string };
}): Promise<[{ name?: string | null; state?: string | null }]> {
this.lastParent = req.parent;
this.lastArgument = req.execution.argument;
return [{ name: `${req.parent}/executions/exec-123`, state: "ACTIVE" }];
}
}
function opts(executions: ExecutionsClientLike) {
return {
siteHandle: site,
config,
bucketName: "b",
projectId: "proj",
location: "us-central1",
workflowId: "hyperframes-render",
serviceUrl: "https://render-abc.run.app",
renderId: "hf-render-fixed",
executions,
};
}
describe("renderToCloudRun", () => {
it("starts an execution and returns a handle", async () => {
const fake = new FakeExecutions();
const handle = await renderToCloudRun(opts(fake));
expect(handle.renderId).toBe("hf-render-fixed");
expect(handle.executionName).toBe(
"projects/proj/locations/us-central1/workflows/hyperframes-render/executions/exec-123",
);
expect(handle.outputGcsUri).toBe("gs://b/renders/hf-render-fixed/output.mp4");
expect(handle.projectGcsUri).toBe("gs://b/sites/abc/project.tar.gz");
expect(Object.keys(handle)).toEqual([
"renderId",
"executionName",
"bucketName",
"workflowId",
"outputGcsUri",
"projectGcsUri",
"startedAt",
]);
});
it("builds the workflow argument the YAML expects", async () => {
const fake = new FakeExecutions();
await renderToCloudRun(opts(fake));
const arg = JSON.parse(fake.lastArgument ?? "{}");
expect(arg).toEqual({
RenderId: "hf-render-fixed",
ProjectGcsUri: "gs://b/sites/abc/project.tar.gz",
PlanOutputGcsPrefix: "gs://b/renders/hf-render-fixed/",
OutputGcsUri: "gs://b/renders/hf-render-fixed/output.mp4",
ServiceUrl: "https://render-abc.run.app",
Config: config,
PlanProtocol: "v1",
});
expect(fake.lastParent).toBe(
"projects/proj/locations/us-central1/workflows/hyperframes-render",
);
});
it("forwards an explicit v2 whole-render opt-in", async () => {
const fake = new FakeExecutions();
await renderToCloudRun({ ...opts(fake), planProtocol: "v2" });
const arg = JSON.parse(fake.lastArgument ?? "{}");
expect(arg).toEqual({
RenderId: "hf-render-fixed",
ProjectGcsUri: "gs://b/sites/abc/project.tar.gz",
PlanOutputGcsPrefix: "gs://b/renders/hf-render-fixed/",
OutputGcsUri: "gs://b/renders/hf-render-fixed/output.mp4",
ServiceUrl: "https://render-abc.run.app",
Config: config,
PlanProtocol: "v2",
});
});
it("derives the output extension from the format", async () => {
const fake = new FakeExecutions();
const handle = await renderToCloudRun({
...opts(fake),
config: { ...config, format: "webm" } as SerializableDistributedRenderConfig,
});
expect(handle.outputGcsUri).toBe("gs://b/renders/hf-render-fixed/output.webm");
});
it("requires serviceUrl", async () => {
const fake = new FakeExecutions();
await expect(renderToCloudRun({ ...opts(fake), serviceUrl: "" })).rejects.toThrow(
/serviceUrl is required/,
);
});
it("requires a siteHandle or projectDir", async () => {
const fake = new FakeExecutions();
const { siteHandle, ...rest } = opts(fake);
void siteHandle;
await expect(renderToCloudRun(rest)).rejects.toThrow(/siteHandle or projectDir/);
});
it("validates the config before any GCP call", async () => {
const fake = new FakeExecutions();
await expect(
renderToCloudRun({ ...opts(fake), config: { ...config, fps: 25 } as never }),
).rejects.toThrow(/config\.fps/);
expect(fake.lastArgument).toBeNull();
});
it("rejects a renderId that could escape the GCS key prefix", async () => {
const fake = new FakeExecutions();
await expect(renderToCloudRun({ ...opts(fake), renderId: "../escape" })).rejects.toThrow(
/renderId must match/,
);
await expect(renderToCloudRun({ ...opts(fake), renderId: "has/slash" })).rejects.toThrow(
/renderId must match/,
);
expect(fake.lastArgument).toBeNull();
});
});