Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
70 lines
4.9 KiB
TypeScript
70 lines
4.9 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import path from "node:path";
|
|
|
|
// HTTP/client behavior only. Main is stubbed, so these are not native parity evidence.
|
|
for (const mode of ["success", "queued", "failed", "cancelled", "bad-hash", "missing-hash", "bad-binding", "unknown-status", "stale-main", "timeout", "cancel", "http-error"]) {
|
|
test(`SERVER_NATIVE async client: ${mode}`, async ({ page }) => {
|
|
await page.goto("/");
|
|
const result = await page.evaluate(async ({ mode, protocolUrl }) => {
|
|
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
|
const { canonicalServerOperatorJobRequest, canonicalServerOperatorProperties } = await import(protocolUrl);
|
|
const hash = async (bytes: ArrayBuffer) => Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (n) => n.toString(16).padStart(2, "0")).join("");
|
|
const encode = (value: string) => new TextEncoder().encode(value).buffer;
|
|
const source = encode("transport-source");
|
|
const output = encode("transport-result");
|
|
const request: any = { schemaVersion: 1, route: "SERVER_NATIVE", jobId: crypto.randomUUID(), sourceBlendSha256: await hash(source), sourceBlendByteLength: source.byteLength, sourceRevision: 1, blenderBuild: { version: "test", buildSha256: "a".repeat(64) }, operator: "object.test", properties: {}, propertiesSha256: await hash(encode(canonicalServerOperatorProperties({}))), timeoutMs: 1000, memoryBytes: 64 * 1024 * 1024, resultBinding: { kind: "snapshot", key: "test" }, context: "BACKGROUND" };
|
|
request.requestSha256 = await hash(encode(canonicalServerOperatorJobRequest(request)));
|
|
const final: any = { ...request, status: "SUCCEEDED", resultSha256: "a".repeat(64), resultBlendSha256: await hash(output), resultBlendByteLength: output.byteLength, resultBlendBase64: btoa("transport-result") };
|
|
if (mode === "failed") final.status = "FAILED";
|
|
if (mode === "cancelled") final.status = "CANCELLED";
|
|
if (mode === "bad-hash") final.resultBlendSha256 = "0".repeat(64);
|
|
if (mode === "missing-hash") delete final.resultBlendSha256;
|
|
if (mode === "bad-binding") final.requestSha256 = "0".repeat(64);
|
|
if (mode === "unknown-status") final.status = "UNKNOWN";
|
|
const originalFetch = window.fetch;
|
|
let reads = 0, cancels = 0, opened = 0, sourceSaved = 0;
|
|
window.fetch = async (_url, init) => {
|
|
const action = new Headers(init?.headers).get("X-Server-Operator-Action");
|
|
if (action === "cancel") cancels++;
|
|
if (action === "read") {
|
|
reads++;
|
|
if (mode === "timeout" || mode === "cancel") {
|
|
return new Promise<Response>((_resolve, reject) => {
|
|
if (init?.signal?.aborted) reject(init.signal.reason);
|
|
else init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), { once: true });
|
|
});
|
|
}
|
|
if (mode === "http-error") return new Response("unavailable", { status: 503 });
|
|
}
|
|
return Response.json({ status: { ready: true }, result: action === "submit" ? { ...request, status: mode === "queued" ? "QUEUED" : "RUNNING" } : action === "cancel" ? { ...request, status: "CANCELLED" } : final }, { status: action === "submit" ? 202 : 200 });
|
|
};
|
|
const client = new WebEngineClient({ timeoutMs: 5000 });
|
|
client.saveBlend = async () => { sourceSaved++; return source.slice(0); };
|
|
client.snapshot = async () => ({ snapshot: { revision: mode === "stale-main" ? 2 : 1 } } as any);
|
|
client.applyServerBlendResult = async () => { opened++; return {} as any; };
|
|
const controller = new AbortController();
|
|
const started = performance.now();
|
|
const cancellation = mode === "cancel" ? setTimeout(() => controller.abort(new Error("user cancelled")), 400) : undefined;
|
|
try {
|
|
const job = await client.submitServerOperatorJob(request, controller.signal);
|
|
return { status: job.job.status, reads, cancels, opened, sourceSaved, elapsed: performance.now() - started, error: null };
|
|
} catch (error: any) {
|
|
return { status: null, reads, cancels, opened, sourceSaved, elapsed: performance.now() - started, error: error.message ?? String(error) };
|
|
} finally {
|
|
clearTimeout(cancellation);
|
|
window.fetch = originalFetch;
|
|
client.terminate();
|
|
}
|
|
}, { mode, protocolUrl: `/@fs${path.resolve(import.meta.dirname, "../../protocol/server-operator-job.ts")}` });
|
|
expect(result.reads).toBeGreaterThan(0);
|
|
expect(result.sourceSaved).toBe(1);
|
|
if (mode === "success" || mode === "queued") {
|
|
expect(result.status).toBe("SUCCEEDED"); expect(result.opened).toBe(1); expect(result.cancels).toBe(0);
|
|
} else {
|
|
expect(result.error).toBeTruthy(); expect(result.opened).toBe(0); expect(result.cancels).toBeGreaterThan(0);
|
|
}
|
|
if (mode === "timeout") expect(result.elapsed).toBeLessThan(4500);
|
|
if (mode === "cancel") expect(result.elapsed).toBeLessThan(1500);
|
|
});
|
|
}
|