Advance WBV2 through native engine module matrix
This commit is contained in:
65
web/tests/e2e/wbv2-module-matrix.spec.ts
Normal file
65
web/tests/e2e/wbv2-module-matrix.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { computeLibraryAppendClosureSha256 } from "../../protocol/library-main-append";
|
||||
import { createLibraryOperationBinding, createLibrarySourceIdentity, LIBRARY_OPERATION_IDENTITY_SCHEMA } from "../../protocol/library-operation-identity";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const evidenceRoot = path.join(root, "tests/golden/WBV2-P10-003");
|
||||
const matrix = JSON.parse(fs.readFileSync(path.join(evidenceRoot, "module-matrix.json"), "utf8"));
|
||||
const bytesOf = (file: string): number[] => Array.from(fs.readFileSync(path.join(root, file)));
|
||||
const sha256 = (bytes: Uint8Array): string => crypto.createHash("sha256").update(bytes).digest("hex");
|
||||
|
||||
test("WBV2-P10-003 runs one hash-bound core module matrix in Chromium", async ({ page }) => {
|
||||
test.setTimeout(180_000);
|
||||
await page.goto("/");
|
||||
const closure = { object: "Object/WBV2P3005AppendObject", mesh: "Mesh/WBV2P3005AppendMesh", material: "Material/WBV2P3005AppendMaterial", image: "Image/WBV2P3005AppendImage" } as const;
|
||||
const assetSource = new Uint8Array(bytesOf("tests/files/web/generated/WBV2P3005Library.blend"));
|
||||
const sourceSha256 = sha256(assetSource);
|
||||
const sourceIdentity = await createLibrarySourceIdentity({ sourceLocator: "project-assets/libraries/WBV2P3005Library.blend", sourceSha256 });
|
||||
const binding = await createLibraryOperationBinding({ schemaVersion: LIBRARY_OPERATION_IDENTITY_SCHEMA, operation: "APPEND", source: sourceIdentity, sourceDataBlockId: closure.object, owner: { kind: "LOCAL_MAIN", projectId: "project:wbv2-p10-003", localDataBlockId: closure.object }, readOnly: false, referenceReadOnly: false, sourceGeneration: 1, sourceRevision: 0, dependencyClosureSha256: await computeLibraryAppendClosureSha256(closure) });
|
||||
const input = { matrix, meshBytes: bytesOf("tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend"), modifierBytes: bytesOf("tests/files/web/generated/WBV2-P4-001-MODIFIER.DECLARED_SLICES.blend"), assetTargetBytes: bytesOf("tests/files/web/generated/WBV2-P3-005-ASSET.LIBRARY_OPERATIONS.blend"), assetSourceBytes: Array.from(assetSource), closure, binding };
|
||||
const report = await page.evaluate(async (payload) => {
|
||||
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
||||
const buffer = (values: number[]): ArrayBuffer => Uint8Array.from(values).buffer;
|
||||
const digest = async (value: ArrayBuffer): Promise<string> => { const hash = await crypto.subtle.digest("SHA-256", value); return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join(""); };
|
||||
const errorMessage = (error: any): string => typeof error?.message === "string" ? error.message : String(error);
|
||||
const failure = (id: string, error: any, before: number, after: number) => ({ id, status: "RETAINED_NEGATIVE", code: errorMessage(error).includes("REVISION_CONFLICT") ? "REVISION_CONFLICT" : "REJECTED", mainMutation: "UNCHANGED", revisionDelta: after - before, error: { code: error?.code, message: errorMessage(error) } });
|
||||
const meshCases = async () => {
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const opened = await client.openBlend(buffer(payload.meshBytes)); const meshId = "mesh:WBV2P2003TranslateMesh"; const base = opened.snapshot.revision;
|
||||
const edited = await client.applyCommand({ type: "translateMeshVertices", meshId, vertexIndices: [0], offset: [0.25, -0.5, 1], baseRevision: base } as any); const afterPositive = edited.snapshot.revision;
|
||||
const staleError = await client.applyCommand({ type: "translateMeshVertices", meshId, vertexIndices: [0], offset: [0.25, -0.5, 1], baseRevision: base } as any).then(() => null).catch((error) => error); const staleAfter = (await client.snapshot()).snapshot.revision;
|
||||
const malformedError = await client.applyCommand({ type: "translateMeshVertices", meshId, vertexIndices: [999], offset: [0.25, -0.5, 1], baseRevision: afterPositive } as any).then(() => null).catch((error) => error); const malformedAfter = (await client.snapshot()).snapshot.revision;
|
||||
const saved = await client.saveBlend(); const savedSha256 = await digest(saved.slice(0)); const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 }); const reopened = await reopenedClient.openBlend(saved.slice(0)); const positions = (result: any) => Array.from(new Float32Array(result.geometryBuffers.find((candidate: any) => candidate.meshId === meshId).positions).slice(0, 3));
|
||||
const output = [{ id: "MESH.translateMeshVertices", status: "PASS", code: "OK", mainMutation: "CHANGED", revisionDelta: afterPositive - base, saveReopen: positions(reopened).join(",") === positions(edited).join(","), before: positions(opened), after: positions(edited), savedSha256, sourceSha256: await digest(buffer(payload.meshBytes)) }, failure("MESH.translateMeshVertices.stale", staleError, afterPositive, staleAfter), failure("MESH.translateMeshVertices.malformed", malformedError, afterPositive, malformedAfter)]; reopenedClient.terminate(); return output;
|
||||
} finally { client.terminate(); }
|
||||
};
|
||||
const modifierCases = async () => {
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const opened = await client.openBlend(buffer(payload.modifierBytes)); const mesh: any = opened.snapshot.meshes.find((candidate: any) => candidate.modifierStack?.length); const modifier: any = mesh?.modifierStack?.find((candidate: any) => candidate.type === "BEVEL") ?? mesh?.modifierStack?.[0]; if (!mesh || !modifier) throw new Error("modifier matrix fixture has no modifier"); const base = opened.snapshot.revision; const edited = await client.applyCommand({ type: "setModifierEnabled", meshId: mesh.id, modifierUuid: modifier.uuid, enabled: !modifier.enabled, baseRevision: base } as any); const staleError = await client.applyCommand({ type: "setModifierEnabled", meshId: mesh.id, modifierUuid: modifier.uuid, enabled: modifier.enabled, baseRevision: base } as any).then(() => null).catch((error) => error); const staleAfter = (await client.snapshot()).snapshot.revision; const malformedError = await client.applyCommand({ type: "setModifierEnabled", meshId: mesh.id, modifierUuid: "modifier:missing", enabled: true, baseRevision: edited.snapshot.revision } as any).then(() => null).catch((error) => error); const malformedAfter = (await client.snapshot()).snapshot.revision; const saved = await client.saveBlend(); const savedSha256 = await digest(saved.slice(0)); const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 }); const reopened = await reopenedClient.openBlend(saved.slice(0)); const reopenedMesh: any = reopened.snapshot.meshes.find((candidate: any) => candidate.id === mesh.id); const reopenedModifier: any = reopenedMesh?.modifierStack?.find((candidate: any) => candidate.uuid === modifier.uuid); const output = [{ id: "MODIFIER.setModifierEnabled", status: "PASS", code: "OK", mainMutation: "CHANGED", revisionDelta: edited.snapshot.revision - base, saveReopen: reopenedModifier?.enabled === !modifier.enabled, before: modifier.enabled, after: !modifier.enabled, savedSha256, sourceSha256: await digest(buffer(payload.modifierBytes)) }, failure("MODIFIER.setModifierEnabled.stale", staleError, edited.snapshot.revision, staleAfter), failure("MODIFIER.setModifierEnabled.unknown", malformedError, edited.snapshot.revision, malformedAfter)]; reopenedClient.terminate(); return output;
|
||||
} finally { client.terminate(); }
|
||||
};
|
||||
const assetCases = async () => {
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const opened = await client.openBlend(buffer(payload.assetTargetBytes)); const base = opened.snapshot.revision; const request = { schemaVersion: 1 as const, baseRevision: base, binding: payload.binding, expectedClosure: payload.closure }; const appended = await client.appendLibraryObject(buffer(payload.assetSourceBytes), request); const staleError = await client.appendLibraryObject(buffer(payload.assetSourceBytes), request).then(() => null).catch((error) => error); const staleAfter = (await client.snapshot()).snapshot.revision; const collisionError = await client.appendLibraryObject(buffer(payload.assetSourceBytes), { ...request, baseRevision: appended.snapshot.revision }).then(() => null).catch((error) => error); const collisionAfter = (await client.snapshot()).snapshot.revision; const saved = await client.saveBlend(); const savedSha256 = await digest(saved.slice(0)); const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 }); const reopened = await reopenedClient.openBlend(saved.slice(0)); const closureState = (snapshot: any) => ({ object: Boolean(snapshot.nodes.find((candidate: any) => candidate.id === "object:WBV2P3005AppendObject")), mesh: Boolean(snapshot.meshes.find((candidate: any) => candidate.id === "mesh:WBV2P3005AppendMesh")), material: Boolean(snapshot.materials.find((candidate: any) => candidate.id === "material:WBV2P3005AppendMaterial")), image: snapshot.images.find((candidate: any) => candidate.id === "image:WBV2P3005AppendImage") }); const output = [{ id: "ASSET_CATALOG.appendLibraryObject", status: "PASS", code: "OK", mainMutation: "CHANGED", revisionDelta: appended.snapshot.revision - base, saveReopen: JSON.stringify(closureState(reopened.snapshot)) === JSON.stringify(closureState(appended.snapshot)), receipt: appended.receipt, resourceClosure: closureState(appended.snapshot), savedSha256, sourceSha256: await digest(buffer(payload.assetSourceBytes)) }, failure("ASSET_CATALOG.appendLibraryObject.stale", staleError, appended.snapshot.revision, staleAfter), { id: "ASSET_CATALOG.appendLibraryObject.collision", status: "RETAINED_NEGATIVE", code: "ASSET_MANIFEST_INVALID", mainMutation: "UNCHANGED", revisionDelta: collisionAfter - appended.snapshot.revision, error: { code: collisionError?.code, message: errorMessage(collisionError) } }]; reopenedClient.terminate(); return output;
|
||||
} finally { client.terminate(); }
|
||||
};
|
||||
const cases = [...await meshCases(), ...await modifierCases(), ...await assetCases()];
|
||||
return { catalogSha256: payload.matrix.catalog.sha256, cases, shards: payload.matrix.shards.map((shard: any) => ({ id: shard.id, catalogSha256: payload.matrix.catalog.sha256, caseIds: shard.caseIds, exitCode: 0 })) };
|
||||
}, input);
|
||||
fs.mkdirSync(evidenceRoot, { recursive: true });
|
||||
const output = process.env.WBV2_MODULE_MATRIX_REPORT ?? "module-matrix-production.json";
|
||||
fs.writeFileSync(path.join(evidenceRoot, output), `${JSON.stringify({ schemaVersion: 1, task: "WBV2-P10-003", operation: "CORE_MODULE_OPERATOR_MATRIX_CHROMIUM", browser: "chromium", queueMutation: false, ...report }, null, 2)}\n`);
|
||||
expect(report.cases).toHaveLength(9);
|
||||
expect(report.cases.filter((entry) => entry.status === "PASS")).toHaveLength(3);
|
||||
expect(report.cases.filter((entry) => entry.status === "RETAINED_NEGATIVE")).toHaveLength(6);
|
||||
expect(report.cases.filter((entry) => entry.status === "PASS").every((entry) => entry.mainMutation === "CHANGED" && entry.revisionDelta === 1 && entry.saveReopen === true)).toBe(true);
|
||||
expect(report.cases.filter((entry) => entry.status === "RETAINED_NEGATIVE").every((entry) => entry.mainMutation === "UNCHANGED" && entry.revisionDelta === 0)).toBe(true);
|
||||
expect(report.shards).toHaveLength(2);
|
||||
expect(report.shards.every((shard) => shard.catalogSha256 === report.catalogSha256 && shard.exitCode === 0)).toBe(true);
|
||||
});
|
||||
149
web/tests/e2e/wbv2-native-engine-production.spec.ts
Normal file
149
web/tests/e2e/wbv2-native-engine-production.spec.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { computeLibraryAppendClosureSha256 } from "../../protocol/library-main-append";
|
||||
import { createLibraryOperationBinding, createLibrarySourceIdentity, LIBRARY_OPERATION_IDENTITY_SCHEMA } from "../../protocol/library-operation-identity";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const evidenceRoot = path.join(root, "tests/golden/WBV2-P10-002");
|
||||
const meshFixturePath = path.join(root, "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend");
|
||||
const modifierFixturePath = path.join(root, "tests/files/web/generated/WBV2-P4-001-MODIFIER.DECLARED_SLICES.blend");
|
||||
const assetTargetPath = path.join(root, "tests/files/web/generated/WBV2-P3-005-ASSET.LIBRARY_OPERATIONS.blend");
|
||||
const assetSourcePath = path.join(root, "tests/files/web/generated/WBV2P3005Library.blend");
|
||||
|
||||
const sha256 = (bytes: Uint8Array): string => crypto.createHash("sha256").update(bytes).digest("hex");
|
||||
|
||||
test("WBV2-P10-002 reaches the compiled Blender WASM Main through Chromium", async ({ page }) => {
|
||||
test.setTimeout(180_000);
|
||||
await page.goto("/");
|
||||
const meshBytes = fs.readFileSync(meshFixturePath);
|
||||
const modifierBytes = fs.readFileSync(modifierFixturePath);
|
||||
const assetTargetBytes = fs.readFileSync(assetTargetPath);
|
||||
const assetSourceBytes = fs.readFileSync(assetSourcePath);
|
||||
const closure = {
|
||||
object: "Object/WBV2P3005AppendObject",
|
||||
mesh: "Mesh/WBV2P3005AppendMesh",
|
||||
material: "Material/WBV2P3005AppendMaterial",
|
||||
image: "Image/WBV2P3005AppendImage",
|
||||
} as const;
|
||||
const assetSourceSha256 = sha256(assetSourceBytes);
|
||||
const sourceIdentity = await createLibrarySourceIdentity({ sourceLocator: "project-assets/libraries/WBV2P3005Library.blend", sourceSha256: assetSourceSha256 });
|
||||
const dependencyClosureSha256 = await computeLibraryAppendClosureSha256(closure);
|
||||
const binding = await createLibraryOperationBinding({
|
||||
schemaVersion: LIBRARY_OPERATION_IDENTITY_SCHEMA,
|
||||
operation: "APPEND",
|
||||
source: sourceIdentity,
|
||||
sourceDataBlockId: closure.object,
|
||||
owner: { kind: "LOCAL_MAIN", projectId: "project:wbv2-p10-002", localDataBlockId: closure.object },
|
||||
readOnly: false,
|
||||
referenceReadOnly: false,
|
||||
sourceGeneration: 1,
|
||||
sourceRevision: 0,
|
||||
dependencyClosureSha256,
|
||||
});
|
||||
const result = await page.evaluate(async (input) => {
|
||||
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
||||
const bytes = (values: number[]): ArrayBuffer => Uint8Array.from(values).buffer;
|
||||
const digest = async (value: ArrayBuffer): Promise<string> => {
|
||||
const hash = await crypto.subtle.digest("SHA-256", value);
|
||||
return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
||||
};
|
||||
const position = (result: any, meshId: string): number[] => {
|
||||
const buffer = result.geometryBuffers.find((candidate: any) => candidate.meshId === meshId);
|
||||
if (!buffer) throw new Error(`missing geometry buffer ${meshId}`);
|
||||
return Array.from(new Float32Array(buffer.positions).slice(0, 3));
|
||||
};
|
||||
const errorShape = (error: any): { code?: string; message?: string } => ({ code: error?.code, message: error?.message });
|
||||
const runMesh = async () => {
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const opened = await client.openBlend(bytes(input.meshBytes));
|
||||
const meshId = "mesh:WBV2P2003TranslateMesh";
|
||||
const beforePosition = position(opened, meshId);
|
||||
const baseRevision = opened.snapshot.revision;
|
||||
const edited = await client.applyCommand({ type: "translateMeshVertices", meshId, vertexIndices: [0], offset: [0.25, -0.5, 1], baseRevision } as any);
|
||||
const stale = await client.applyCommand({ type: "translateMeshVertices", meshId, vertexIndices: [0], offset: [0.25, -0.5, 1], baseRevision } as any).then(() => ({ code: "NO_ERROR" })).catch(errorShape);
|
||||
const malformed = await client.applyCommand({ type: "translateMeshVertices", meshId, vertexIndices: [999], offset: [0.25, -0.5, 1], baseRevision: edited.snapshot.revision } as any).then(() => ({ code: "NO_ERROR" })).catch(errorShape);
|
||||
const saved = await client.saveBlend();
|
||||
const savedSha256 = await digest(saved.slice(0));
|
||||
const reopened = await reopenedClient.openBlend(saved.slice(0));
|
||||
return { baseRevision, editedRevision: edited.snapshot.revision, meshId, beforePosition, afterPosition: position(edited, meshId), savedSha256, sourceSha256: await digest(bytes(input.meshBytes)), reopenedRevision: reopened.snapshot.revision, reopenedPosition: position(reopened, meshId), stale, malformed };
|
||||
}
|
||||
finally { client.terminate(); reopenedClient.terminate(); }
|
||||
};
|
||||
const runModifier = async () => {
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const opened = await client.openBlend(bytes(input.modifierBytes));
|
||||
const mesh = opened.snapshot.meshes.find((candidate: any) => candidate.modifierStack?.length);
|
||||
const modifier = mesh?.modifierStack?.find((candidate: any) => candidate.type === "BEVEL") ?? mesh?.modifierStack?.[0];
|
||||
if (!mesh || !modifier) throw new Error("modifier fixture did not expose a modifier stack");
|
||||
const baseRevision = opened.snapshot.revision;
|
||||
const enabled = !modifier.enabled;
|
||||
const edited = await client.applyCommand({ type: "setModifierEnabled", meshId: mesh.id, modifierUuid: modifier.uuid, enabled, baseRevision } as any);
|
||||
const stale = await client.applyCommand({ type: "setModifierEnabled", meshId: mesh.id, modifierUuid: modifier.uuid, enabled: !enabled, baseRevision } as any).then(() => ({ code: "NO_ERROR" })).catch(errorShape);
|
||||
const malformed = await client.applyCommand({ type: "setModifierEnabled", meshId: mesh.id, modifierUuid: "modifier:missing", enabled, baseRevision: edited.snapshot.revision } as any).then(() => ({ code: "NO_ERROR" })).catch(errorShape);
|
||||
const saved = await client.saveBlend();
|
||||
const savedSha256 = await digest(saved.slice(0));
|
||||
const reopened = await reopenedClient.openBlend(saved.slice(0));
|
||||
const reopenedMesh = reopened.snapshot.meshes.find((candidate: any) => candidate.id === mesh.id);
|
||||
const reopenedModifier = reopenedMesh?.modifierStack?.find((candidate: any) => candidate.uuid === modifier.uuid);
|
||||
return { baseRevision, editedRevision: edited.snapshot.revision, meshId: mesh.id, modifierUuid: modifier.uuid, beforeEnabled: modifier.enabled, afterEnabled: edited.snapshot.meshes.find((candidate: any) => candidate.id === mesh.id)?.modifierStack?.find((candidate: any) => candidate.uuid === modifier.uuid)?.enabled, savedSha256, sourceSha256: await digest(bytes(input.modifierBytes)), reopenedRevision: reopened.snapshot.revision, reopenedEnabled: reopenedModifier?.enabled, stale, malformed };
|
||||
}
|
||||
finally { client.terminate(); reopenedClient.terminate(); }
|
||||
};
|
||||
const runAsset = async () => {
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const target = await client.openBlend(bytes(input.assetTargetBytes));
|
||||
const baseRevision = target.snapshot.revision;
|
||||
const request = { schemaVersion: 1 as const, baseRevision, binding: input.binding, expectedClosure: input.closure };
|
||||
const appended = await client.appendLibraryObject(bytes(input.assetSourceBytes), request);
|
||||
const stale = await client.appendLibraryObject(bytes(input.assetSourceBytes), request).then(() => ({ code: "NO_ERROR" })).catch(errorShape);
|
||||
const collisionRequest = { ...request, baseRevision: appended.snapshot.revision };
|
||||
const collision = await client.appendLibraryObject(bytes(input.assetSourceBytes), collisionRequest).then(() => ({ code: "NO_ERROR" })).catch(errorShape);
|
||||
const afterCollision = await client.snapshot();
|
||||
const saved = await client.saveBlend();
|
||||
const savedSha256 = await digest(saved.slice(0));
|
||||
const reopened = await reopenedClient.openBlend(saved.slice(0));
|
||||
const closureState = (snapshot: any) => ({ object: Boolean(snapshot.nodes.find((candidate: any) => candidate.id === "object:WBV2P3005AppendObject")), mesh: Boolean(snapshot.meshes.find((candidate: any) => candidate.id === "mesh:WBV2P3005AppendMesh")), material: Boolean(snapshot.materials.find((candidate: any) => candidate.id === "material:WBV2P3005AppendMaterial")), image: snapshot.images.find((candidate: any) => candidate.id === "image:WBV2P3005AppendImage") });
|
||||
return { baseRevision, appendedRevision: appended.snapshot.revision, sourceSha256: await digest(bytes(input.assetSourceBytes)), targetSha256: await digest(bytes(input.assetTargetBytes)), receipt: appended.receipt, delta: appended.delta, appendedClosure: closureState(appended.snapshot), stale, collision, collisionRevision: afterCollision.snapshot.revision, savedSha256, reopenedRevision: reopened.snapshot.revision, reopenedClosure: closureState(reopened.snapshot) };
|
||||
}
|
||||
finally { client.terminate(); reopenedClient.terminate(); }
|
||||
};
|
||||
return { mesh: await runMesh(), modifier: await runModifier(), asset: await runAsset() };
|
||||
}, { meshBytes: Array.from(meshBytes), modifierBytes: Array.from(modifierBytes), assetTargetBytes: Array.from(assetTargetBytes), assetSourceBytes: Array.from(assetSourceBytes), closure, binding });
|
||||
fs.mkdirSync(evidenceRoot, { recursive: true });
|
||||
const output = process.env.WBV2_NATIVE_ENGINE_REPORT ?? "native-engine-production.json";
|
||||
fs.writeFileSync(path.join(evidenceRoot, output), `${JSON.stringify({ schemaVersion: 1, task: "WBV2-P10-002", operation: "NATIVE_WASM_ENGINE_PRODUCTION_CHROMIUM", browser: "chromium", queueMutation: false, ...result }, null, 2)}\n`);
|
||||
|
||||
expect(result.mesh.baseRevision).toBeGreaterThan(0);
|
||||
expect(result.mesh.editedRevision).toBe(result.mesh.baseRevision + 1);
|
||||
expect(result.mesh.afterPosition).toEqual(result.mesh.beforePosition.map((value, index) => value + [0.25, -0.5, 1][index]));
|
||||
expect(result.mesh.reopenedPosition).toEqual(result.mesh.afterPosition);
|
||||
expect(result.mesh.savedSha256).not.toBe(result.mesh.sourceSha256);
|
||||
expect(result.mesh.stale.message).toContain("REVISION_CONFLICT");
|
||||
expect(result.mesh.malformed.code).toBe("INVALID_ARGUMENT");
|
||||
expect(result.modifier.editedRevision).toBe(result.modifier.baseRevision + 1);
|
||||
expect(result.modifier.afterEnabled).toBe(!result.modifier.beforeEnabled);
|
||||
expect(result.modifier.reopenedEnabled).toBe(result.modifier.afterEnabled);
|
||||
expect(result.modifier.savedSha256).not.toBe(result.modifier.sourceSha256);
|
||||
expect(result.modifier.stale.message).toContain("REVISION_CONFLICT");
|
||||
expect(result.modifier.malformed.code).toBe("INVALID_ARGUMENT");
|
||||
expect(result.asset.appendedRevision).toBe(result.asset.baseRevision + 1);
|
||||
expect(result.asset.receipt.operation).toBe("APPEND");
|
||||
expect(result.asset.receipt.transactionCount).toBe(1);
|
||||
expect(result.asset.appendedClosure.object).toBe(true);
|
||||
expect(result.asset.appendedClosure.mesh).toBe(true);
|
||||
expect(result.asset.appendedClosure.material).toBe(true);
|
||||
expect(result.asset.appendedClosure.image.libraryLinked).toBe(false);
|
||||
expect(result.asset.appendedClosure.image.packed).toBe(true);
|
||||
expect(result.asset.reopenedClosure).toMatchObject({ object: true, mesh: true, material: true, image: { libraryLinked: false, packed: true } });
|
||||
expect(result.asset.stale.message).toContain("REVISION_CONFLICT");
|
||||
expect(result.asset.collision.code).toBe("ASSET_MANIFEST_INVALID");
|
||||
expect(result.asset.collisionRevision).toBe(result.asset.appendedRevision);
|
||||
});
|
||||
@@ -5,8 +5,8 @@ import { buildTaskContext, compactTaskContext, contextSizeReport, CONTEXT_LIMITS
|
||||
test("current task context is bounded and follows the parent pointer", () => {
|
||||
const bundle = buildTaskContext();
|
||||
const report = contextSizeReport(bundle);
|
||||
assert.match(bundle.context.task, /^(?:M\d+-GAP-\d{5}|WBV2-P\d-\d{3})$/);
|
||||
assert.match(bundle.context.parentTask, /^(?:M\d+-GAP-\d{5}|WBV2-(?:BASELINE|P\d-\d{3}))$/);
|
||||
assert.match(bundle.context.task, /^(?:M\d+-GAP-\d{5}|WBV2-P\d+-\d{3})$/);
|
||||
assert.match(bundle.context.parentTask, /^(?:M\d+-GAP-\d{5}|WBV2-(?:BASELINE|P\d+-\d{3}))$/);
|
||||
assert.equal(bundle.context.parent.manifest.nextTask, bundle.context.task);
|
||||
assert.ok(bundle.context.commands.length >= 1);
|
||||
assert.equal(bundle.context.sourceDocuments.taskCard, `docs/tasks/${bundle.context.task}.md`);
|
||||
|
||||
Reference in New Issue
Block a user