Spaces:
Running
Running
| """Headless render verification: bundle the fixture factory exactly like the | |
| runtime does and execute it in node (scene-graph smoke, no WebGL needed).""" | |
| from __future__ import annotations | |
| import json | |
| import shutil | |
| import subprocess | |
| from pathlib import Path | |
| import pytest | |
| from app import forge_bridge | |
| REPO_ROOT = Path(__file__).resolve().parents[1] | |
| ESBUILD = REPO_ROOT / "node_modules" / ".bin" / "esbuild" | |
| NODE = shutil.which("node") | |
| pytestmark = pytest.mark.skipif( | |
| not (ESBUILD.exists() and NODE), reason="node/esbuild not installed (run npm ci)") | |
| def test_fixture_factory_bundles_and_executes(tmp_path, factory_fixture_ts): | |
| entry = tmp_path / "entry.js" | |
| entry.write_text( | |
| 'export { createMugModel as makeModel, createMugLookDevLights as makeLights } ' | |
| f'from "{factory_fixture_ts}";\n' | |
| f'export {{ mountViewer }} from "{REPO_ROOT / "app" / "static" / "viewer-core.js"}";\n', | |
| encoding="utf-8") | |
| bundle = tmp_path / "model.bundle.js" | |
| link = tmp_path / "node_modules" | |
| link.symlink_to(REPO_ROOT / "node_modules", target_is_directory=True) | |
| build = subprocess.run( | |
| [str(ESBUILD), str(entry), "--bundle", "--format=esm", | |
| "--target=es2022", "--minify", f"--outfile={bundle}"], | |
| cwd=tmp_path, capture_output=True, text=True, timeout=120) | |
| assert build.returncode == 0, build.stderr | |
| assert bundle.stat().st_size > 100_000 | |
| smoke = subprocess.run( | |
| [NODE, str(REPO_ROOT / "scripts" / "node_smoke.mjs"), str(bundle), | |
| str(REPO_ROOT / "tests" / "fixtures" / "canned_spec.json")], | |
| capture_output=True, text=True, timeout=120) | |
| assert smoke.returncode == 0, f"{smoke.stdout}\n{smoke.stderr}" | |
| report = json.loads(smoke.stdout.splitlines()[0]) | |
| assert report["meshes"] >= 1 | |
| assert report["children"] >= 1 | |
| assert report["runtimeNodes"] >= 1 | |
| assert all(isinstance(v, (int, float)) for v in report["boundingBox"]) | |
| bad_spec = json.loads( | |
| (REPO_ROOT / "tests" / "fixtures" / "canned_spec.json").read_text( | |
| encoding="utf-8")) | |
| body = next(component for component in bad_spec["componentTree"] | |
| if component["id"] == "body") | |
| body["dimensions"]["width"] *= 2 | |
| bad_spec_path = tmp_path / "bad-spec.json" | |
| bad_spec_path.write_text(json.dumps(bad_spec), encoding="utf-8") | |
| rejected = subprocess.run( | |
| [NODE, str(REPO_ROOT / "scripts" / "node_smoke.mjs"), str(bundle), | |
| str(bad_spec_path)], | |
| capture_output=True, text=True, timeout=120) | |
| assert rejected.returncode == 1 | |
| assert "body dimensions disagree with spec" in rejected.stderr | |
| def test_axis_attachment_preserves_declared_dimensions(tmp_path, canned_spec): | |
| """Attachment endpoints orient axial primitives without silently replacing | |
| their declared physical dimensions.""" | |
| spec = json.loads(json.dumps(canned_spec)) | |
| handle = next(component for component in spec["componentTree"] | |
| if component["id"] == "handle") | |
| handle["primitive"] = "cylinder" | |
| # Deliberately disagree with the 0.06 m declared height. The compiler must | |
| # retain dimensions as the geometry contract even when endpoints are used | |
| # to position and orient the mesh. | |
| handle["attachment"]["localStart"] = [0.04, 0.07, 0.0] | |
| handle["attachment"]["localEnd"] = [0.04, 0.03, 0.0] | |
| preview = forge_bridge.prepare_hosted_preview(spec) | |
| spec_path = tmp_path / "compile-spec.json" | |
| spec_path.write_text(json.dumps(preview), encoding="utf-8") | |
| factory = tmp_path / "factory.ts" | |
| forge_bridge.generate_factory( | |
| spec_path, factory, pass_id=forge_bridge.HOSTED_PREVIEW_PASS) | |
| entry = tmp_path / "entry.js" | |
| entry.write_text( | |
| f'export {{ {forge_bridge.factory_export_name(factory.read_text(encoding="utf-8"))} ' | |
| f'as makeModel }} from "{factory}";\n', | |
| encoding="utf-8") | |
| bundle = tmp_path / "model.bundle.js" | |
| (tmp_path / "node_modules").symlink_to( | |
| REPO_ROOT / "node_modules", target_is_directory=True) | |
| build = subprocess.run( | |
| [str(ESBUILD), str(entry), "--bundle", "--format=esm", | |
| "--target=es2022", "--minify", f"--outfile={bundle}"], | |
| cwd=tmp_path, capture_output=True, text=True, timeout=120) | |
| assert build.returncode == 0, build.stderr | |
| smoke = subprocess.run( | |
| [NODE, str(REPO_ROOT / "scripts" / "node_smoke.mjs"), str(bundle), | |
| str(spec_path)], | |
| capture_output=True, text=True, timeout=120) | |
| assert smoke.returncode == 0, f"{smoke.stdout}\n{smoke.stderr}" | |