import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; import fsp from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import { VDBJobService, createVDBJobHttpServer } from "../vdb/server/vdb-job-service.mjs"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const converter = path.join(root, "build_vdb_tools/vdb_to_nanovdb"); const resourceRoot = path.resolve(process.env.VDB_RESOURCE_ROOT ?? path.join(os.homedir(), "resource-library/blender-web-vdb")); const source = path.join(resourceRoot, "generated/generated-smoke.vdb"); const secret = "vdb-server-test-signing-key-000000000000000000000000"; assert.ok(fs.existsSync(converter), "native converter is missing"); assert.ok(fs.existsSync(source), "real OpenVDB fixture is missing"); assert.equal(spawnSync("/usr/bin/bwrap", ["--version"], { encoding: "utf8" }).status, 0, "bwrap is unavailable"); const temporary = await fsp.mkdtemp(path.join(os.tmpdir(), "vdb-server-test-")); const service = new VDBJobService({ converter, root: temporary, secret, timeoutMs: 30_000 }); const server = createVDBJobHttpServer(service); await new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", resolve); }); const address = server.address(); assert.ok(address && typeof address !== "string"); const origin = `http://127.0.0.1:${address.port}`; const sourceBytes = fs.readFileSync(source); const sourceSha256 = crypto.createHash("sha256").update(sourceBytes).digest("hex"); const headers = { "Content-Type": "application/x-openvdb", "X-VDB-Project-Id": "vdb-server-test", "X-VDB-Source-Path": "//volumes/generated-smoke.vdb", "X-VDB-Source-SHA256": sourceSha256, "X-VDB-Grids": "density", "X-VDB-Quantization": "LOSSLESS", "X-VDB-Chunk-Bytes": String(4 * 1024 * 1024), }; async function submit() { const response = await fetch(`${origin}/v1/vdb/jobs`, { method: "POST", headers, body: sourceBytes, duplex: "half" }); if (response.status !== 200 && response.status !== 202) throw new Error(await response.text()); return { status: response.status, body: await response.json() }; } async function waitFor(id, states, timeoutMs = 30_000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const response = await fetch(`${origin}/v1/vdb/jobs/${id}`); assert.equal(response.status, 200); const body = await response.json(); if (states.includes(body.state)) return body; await new Promise((resolve) => setTimeout(resolve, 25)); } throw new Error(`VDB server job ${id} timed out in test`); } try { const health = await (await fetch(`${origin}/healthz`)).json(); assert.equal(health.sandbox, true); assert.match(health.converterSha256, /^[a-f0-9]{64}$/); const first = await submit(); assert.equal(first.status, 202); assert.equal(first.body.deduplicated, false); const completed = await waitFor(first.body.id, ["SUCCEEDED", "FAILED"]); assert.equal(completed.state, "SUCCEEDED", completed.error); assert.equal(completed.sandbox, "bwrap-unshare-all+readonly-root+prlimit"); const manifestResponse = await fetch(`${origin}${completed.artifacts.manifest}`); const bundleResponse = await fetch(`${origin}${completed.artifacts.bundle}`); assert.equal(manifestResponse.status, 200); assert.equal(bundleResponse.status, 200); const manifestBytes = Buffer.from(await manifestResponse.arrayBuffer()); const bundleBytes = Buffer.from(await bundleResponse.arrayBuffer()); const manifest = JSON.parse(manifestBytes.toString("utf8")); assert.equal(manifest.converter.target, "SERVER"); assert.equal(manifest.sourceSha256, sourceSha256); assert.deepEqual(manifest.grids.map((grid) => grid.name), ["density"]); assert.equal(crypto.createHash("sha256").update(bundleBytes).digest("hex"), manifest.bundleSha256); const expectedSignature = crypto.createHmac("sha256", secret).update(`${completed.id}:${crypto.createHash("sha256").update(manifestBytes).digest("hex")}:${manifest.bundleSha256}`).digest("hex"); assert.equal(manifestResponse.headers.get("x-vdb-signature"), expectedSignature); assert.equal(bundleResponse.headers.get("x-vdb-signature"), expectedSignature); const desktopBundle = path.join(temporary, "desktop-density.nvdb"); const desktopReport = path.join(temporary, "desktop-density.json"); const desktopConversion = spawnSync(converter, [ "--input", source, "--output", desktopBundle, "--report", desktopReport, "--grid", "density", "--quantization", "LOSSLESS", ], { encoding: "utf8" }); assert.equal(desktopConversion.status, 0, desktopConversion.stderr); assert.equal(crypto.createHash("sha256").update(fs.readFileSync(desktopBundle)).digest("hex"), manifest.bundleSha256, "desktop and isolated server conversion produced different NanoVDB bytes"); const repeated = await submit(); assert.equal(repeated.status, 200); assert.equal(repeated.body.deduplicated, true); assert.equal(repeated.body.id, completed.id); const cancellationHeaders = { ...headers, "X-VDB-Grids": "color,density,temperature,velocity", "X-VDB-Quantization": "FP16" }; const cancellationResponse = await fetch(`${origin}/v1/vdb/jobs`, { method: "POST", headers: cancellationHeaders, body: sourceBytes, duplex: "half" }); assert.equal(cancellationResponse.status, 202); const cancellation = await cancellationResponse.json(); const cancelledResponse = await fetch(`${origin}/v1/vdb/jobs/${cancellation.id}`, { method: "DELETE" }); assert.equal(cancelledResponse.status, 200); const cancelled = await waitFor(cancellation.id, ["CANCELLED"]); assert.equal(cancelled.state, "CANCELLED"); assert.equal(fs.existsSync(path.join(temporary, "jobs", cancellation.id, "bundle.nvdb")), false); const cancelFile = path.join(temporary, "native-cancel"); fs.writeFileSync(cancelFile, "cancel\n"); const cancelledNative = spawnSync(converter, ["--input", source, "--output", path.join(temporary, "cancelled.nvdb"), "--report", path.join(temporary, "cancelled.json"), "--cancel-file", cancelFile, "--timeout-ms", "30000"], { encoding: "utf8" }); assert.notEqual(cancelledNative.status, 0); assert.match(cancelledNative.stderr, /conversion cancelled/); assert.equal(fs.existsSync(path.join(temporary, "cancelled.nvdb")), false); process.stdout.write(`vdb-server-job-ok job=${completed.id} bytes=${bundleBytes.length} idempotent=1 signed=1 isolated=1 cancelled=1 atomic=1 desktop-server-hash=equal\n`); } finally { await new Promise((resolve) => server.close(resolve)); await fsp.rm(temporary, { recursive: true, force: true }); }