chore: checkpoint web Blender migration source, tools and docs
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

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>
This commit is contained in:
mes123456
2026-11-10 11:18:01 +08:00
parent 122142c26c
commit f3e21c6b98
6887 changed files with 257154 additions and 12294 deletions

View File

@@ -0,0 +1,113 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { once } from "node:events";
import test from "node:test";
import { createDeploymentHttpServer } from "../../../tools/web/deployment-http-server.mjs";
const digest = (value) => crypto.createHash("sha256").update(value).digest("hex");
const canonical = (value) => JSON.stringify(value, (_, item) => item && !Array.isArray(item) && typeof item === "object" ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b))) : item);
test("async HTTP provider lifecycle (transport double, not Blender evidence)", async (t) => {
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "operator-http-test-"));
const options = { workspaceRoot, pythonBinary: path.resolve(import.meta.dirname, "../fixtures/server-operator-runner.py") };
const server = createDeploymentHttpServer({ root: workspaceRoot, serverOperator: options });
server.listen(0, "127.0.0.1");
await once(server, "listening");
const url = `http://127.0.0.1:${server.address().port}/api/server-operator-job`;
const requests = [];
async function call(action, request, body) {
const response = await fetch(action === "read" ? `${url}?jobId=${request.jobId}&requestSha256=${request.requestSha256}` : url, {
method: action === "read" ? "GET" : "POST",
headers: { "X-Server-Operator-Action": action },
body: action === "read" ? undefined : body ?? JSON.stringify(request),
signal: AbortSignal.timeout(5000),
});
return { code: response.status, ...await response.json() };
}
function envelope(mode, overrides = {}) {
const source = Buffer.from("transport-source");
const properties = { mode };
const request = { schemaVersion: 1, route: "SERVER_NATIVE", jobId: crypto.randomUUID(), sourceBlendSha256: digest(source), sourceBlendByteLength: source.length, sourceRevision: 1, blenderBuild: { version: "test", buildSha256: "a".repeat(64) }, operator: "object.test", properties, propertiesSha256: digest(canonical(properties)), timeoutMs: 1000, memoryBytes: 64 * 1024 * 1024, resultBinding: { kind: "snapshot", key: "test" }, context: "BACKGROUND", ...overrides };
request.requestSha256 = digest(canonical(request));
const json = Buffer.from(JSON.stringify(request));
const header = Buffer.alloc(16);
header.write("WBJ1"); header.writeUInt32LE(1, 4); header.writeUInt32LE(json.length, 8); header.writeUInt32LE(source.length, 12);
requests.push(request);
return { request, body: Buffer.concat([header, json, source]) };
}
async function terminal(request) {
for (let i = 0; i < 80; i++) {
const reply = await call("read", request);
if (reply.result.status !== "RUNNING") return reply.result;
await new Promise((resolve) => setTimeout(resolve, 50));
}
assert.fail("job did not reach a terminal state");
}
try {
await t.test("deployment reserves the API and rejects invalid methods/actions before execution", async () => {
const invalid = await call("unknown", {});
assert.equal(invalid.code, 400);
for (const action of ["describe", "submit", "cancel"]) {
const response = await fetch(url, { headers: { "X-Server-Operator-Action": action } });
assert.equal(response.status, 405);
assert.equal(response.headers.get("allow"), "POST");
assert.equal(response.headers.get("cache-control"), "no-store");
assert.equal(response.headers.get("cross-origin-opener-policy"), "same-origin");
}
assert.equal((await fetch(`${url}?jobId=missing&requestSha256=missing`)).status, 404);
assert.equal((await fetch(new URL("/index.html", url), { method: "POST" })).status, 405);
const malformed = await call("submit", {}, Buffer.from("invalid envelope"));
assert.equal(malformed.code, 400);
const versioned = envelope("success"); versioned.body.writeUInt32LE(2, 4);
assert.equal((await call("submit", versioned.request, versioned.body)).code, 400);
assert.equal((await call("read", versioned.request)).code, 404);
assert.deepEqual(fs.readdirSync(workspaceRoot), []);
});
await t.test("202, concurrent reads, success, immutable terminal state and cleanup", async () => {
const { request, body } = envelope("success");
const submitted = await call("submit", request, body);
assert.equal(submitted.code, 202); assert.equal(submitted.result.status, "RUNNING");
assert.equal((await call("read", request)).result.status, "RUNNING");
assert.equal((await call("submit", request, body)).code, 409);
const replies = await Promise.all([terminal(request), terminal(request)]);
assert.deepEqual(replies[0], replies[1]);
assert.equal(replies[0].status, "SUCCEEDED");
assert.equal(Buffer.from(replies[0].resultBlendBase64, "base64").toString(), "transport-result");
assert.equal((await call("cancel", request)).result.status, "SUCCEEDED");
assert.deepEqual(fs.readdirSync(workspaceRoot), []);
});
await t.test("cancel is idempotent; no late success or result bytes", async () => {
const { request, body } = envelope("slow");
await call("submit", request, body);
await Promise.all([call("cancel", request), call("read", request)]);
assert.equal((await call("cancel", request)).result.status, "CANCELLED");
const result = await terminal(request);
assert.equal(result.status, "CANCELLED"); assert.equal(result.resultBlendBase64, undefined);
assert.deepEqual(fs.readdirSync(workspaceRoot), []);
});
for (const mode of ["signal", "slow", "malformed", "bad-hash", "bad-binding"]) {
await t.test(`${mode} fails closed and cleans without polling`, async () => {
const { request, body } = envelope(mode);
assert.equal((await call("submit", request, body)).code, 202);
await new Promise((resolve) => setTimeout(resolve, mode === "slow" ? 1600 : 650));
assert.deepEqual(fs.readdirSync(workspaceRoot), []);
const result = await terminal(request);
assert.equal(result.status, "FAILED"); assert.equal(result.resultBlendBase64, undefined);
});
}
await t.test("missing executable does not crash the HTTP provider", async () => {
options.pythonBinary = path.join(workspaceRoot, "missing-python");
const { request, body } = envelope("success");
assert.equal((await call("submit", request, body)).code, 202);
assert.equal((await terminal(request)).status, "FAILED");
});
} finally {
for (const request of requests) await call("cancel", request).catch(() => {});
server.closeAllConnections();
await new Promise((resolve) => server.close(resolve));
fs.rmSync(workspaceRoot, { recursive: true, force: true });
}
});