Files
MajoorWaldi-ComfyUI-Majoor-…/tests/features/test_extractors_3d.py
T
MajoorWaldi 4411c72e4e Refactor test imports and clean up unused code across multiple test files
- Removed unnecessary imports from various test files to improve readability and maintainability.
- Added new settings for the Generated Feed display in the settingsFeed.js file.
- Introduced a new configuration file for Pyright type checking.
2026-03-28 13:26:28 +01:00

189 lines
7.1 KiB
Python

"""
Tests for 3D model metadata extraction (extractors_3d.py).
Covers:
- GLB JSON chunk happy path (embedded workflow/prompt)
- GLB JSON chunk too large (skipped without crash)
- GLB with empty/no extras (no workflow found)
- Sidecar JSON fallback when GLB has no embedded data
- Malformed sidecar JSON (graceful no-op)
- Missing sidecar + valid GLB extras (only embedded data used)
- .gltf text file parsing
- Non-GLB formats fall through to sidecar only
"""
import json
import struct
from pathlib import Path
from mjr_am_backend.features.metadata.extractors_3d import (
_read_glb_json_chunk,
_read_sidecar_json,
extract_model3d_metadata,
)
# ---------------------------------------------------------------------------
# Helpers to build minimal GLB binary blobs
# ---------------------------------------------------------------------------
_GLB_MAGIC = 0x46546C67
_GLB_CHUNK_TYPE_JSON = 0x4E4F534A
_GLB_CHUNK_TYPE_BIN = 0x004E4942
def _make_glb(json_payload: dict | None, *, version: int = 2) -> bytes:
"""Build a minimal GLB binary container with an optional JSON chunk."""
if json_payload is None:
# No chunks — truncated file
return struct.pack("<III", _GLB_MAGIC, version, 12)
json_bytes = json.dumps(json_payload).encode("utf-8")
# Pad to 4-byte boundary with spaces per glTF spec
pad = (4 - len(json_bytes) % 4) % 4
json_bytes += b" " * pad
total = 12 + 8 + len(json_bytes)
header = struct.pack("<III", _GLB_MAGIC, version, total)
chunk_header = struct.pack("<II", len(json_bytes), _GLB_CHUNK_TYPE_JSON)
return header + chunk_header + json_bytes
# ---------------------------------------------------------------------------
# _read_glb_json_chunk
# ---------------------------------------------------------------------------
def test_read_glb_json_chunk_happy_path(tmp_path: Path) -> None:
payload = {"asset": {"version": "2.0"}, "extras": {"comfyui_workflow": {"nodes": []}}}
glb = tmp_path / "test.glb"
glb.write_bytes(_make_glb(payload))
result = _read_glb_json_chunk(str(glb))
assert result is not None
assert result.get("extras", {}).get("comfyui_workflow") == {"nodes": []}
def test_read_glb_json_chunk_returns_none_for_non_glb(tmp_path: Path) -> None:
f = tmp_path / "notglb.glb"
f.write_bytes(b"not a glb file at all")
assert _read_glb_json_chunk(str(f)) is None
def test_read_glb_json_chunk_returns_none_for_truncated(tmp_path: Path) -> None:
f = tmp_path / "trunc.glb"
f.write_bytes(_make_glb(None)) # header only, no chunk
assert _read_glb_json_chunk(str(f)) is None
def test_read_glb_json_chunk_skips_oversized_chunk(tmp_path: Path, monkeypatch) -> None:
import mjr_am_backend.features.metadata.extractors_3d as m
monkeypatch.setattr(m, "_MAX_GLB_JSON_CHUNK_BYTES", 4) # tiny limit
payload = {"asset": {"version": "2.0"}}
glb = tmp_path / "big.glb"
glb.write_bytes(_make_glb(payload))
assert _read_glb_json_chunk(str(glb)) is None
def test_read_glb_json_chunk_returns_none_for_missing_file() -> None:
assert _read_glb_json_chunk("/nonexistent/path/model.glb") is None
def test_read_glb_json_chunk_returns_none_for_old_version(tmp_path: Path) -> None:
payload = {"asset": {"version": "1.0"}}
glb = tmp_path / "v1.glb"
glb.write_bytes(_make_glb(payload, version=1))
assert _read_glb_json_chunk(str(glb)) is None
# ---------------------------------------------------------------------------
# _read_sidecar_json
# ---------------------------------------------------------------------------
def test_read_sidecar_json_happy_path(tmp_path: Path) -> None:
model = tmp_path / "mesh.obj"
model.write_bytes(b"# obj")
sidecar = tmp_path / "mesh.obj.json"
sidecar.write_text(json.dumps({"workflow": {"nodes": []}}), encoding="utf-8")
result = _read_sidecar_json(str(model))
assert result is not None
assert "workflow" in result
def test_read_sidecar_json_returns_none_when_missing(tmp_path: Path) -> None:
model = tmp_path / "mesh.obj"
model.write_bytes(b"# obj")
assert _read_sidecar_json(str(model)) is None
def test_read_sidecar_json_returns_none_for_malformed_json(tmp_path: Path) -> None:
model = tmp_path / "mesh.ply"
model.write_bytes(b"ply")
sidecar = tmp_path / "mesh.ply.json"
sidecar.write_text("{ this is not valid json", encoding="utf-8")
assert _read_sidecar_json(str(model)) is None
def test_read_sidecar_json_returns_none_for_non_dict(tmp_path: Path) -> None:
model = tmp_path / "mesh.stl"
model.write_bytes(b"stl")
sidecar = tmp_path / "mesh.stl.json"
sidecar.write_text(json.dumps([1, 2, 3]), encoding="utf-8")
assert _read_sidecar_json(str(model)) is None
# ---------------------------------------------------------------------------
# extract_model3d_metadata — integration
# ---------------------------------------------------------------------------
def test_extract_model3d_metadata_glb_with_embedded_workflow(tmp_path: Path) -> None:
workflow = {"nodes": [{"id": 1, "type": "KSampler"}], "links": []}
payload = {"asset": {"version": "2.0"}, "extras": {"comfyui_workflow": workflow}}
glb = tmp_path / "mesh.glb"
glb.write_bytes(_make_glb(payload))
result = extract_model3d_metadata(str(glb))
assert result.ok
assert result.data["quality"] == "full"
assert result.data["workflow"] == workflow
def test_extract_model3d_metadata_glb_no_extras_falls_to_sidecar(tmp_path: Path) -> None:
payload = {"asset": {"version": "2.0"}}
glb = tmp_path / "mesh.glb"
glb.write_bytes(_make_glb(payload))
workflow = {"nodes": [{"id": 1, "type": "CLIPTextEncode"}], "links": []}
sidecar = tmp_path / "mesh.glb.json"
sidecar.write_text(json.dumps({"workflow": workflow}), encoding="utf-8")
result = extract_model3d_metadata(str(glb))
assert result.ok
assert result.data["quality"] == "full"
assert result.data["workflow"] == workflow
def test_extract_model3d_metadata_glb_no_extras_no_sidecar_returns_none_quality(tmp_path: Path) -> None:
payload = {"asset": {"version": "2.0"}}
glb = tmp_path / "empty.glb"
glb.write_bytes(_make_glb(payload))
result = extract_model3d_metadata(str(glb))
assert result.ok
assert result.data["quality"] == "none"
assert result.data["workflow"] is None
assert result.data["prompt"] is None
def test_extract_model3d_metadata_obj_sidecar_fallback(tmp_path: Path) -> None:
obj = tmp_path / "mesh.obj"
obj.write_bytes(b"# obj file")
workflow = {"nodes": [{"id": 2, "type": "VAEDecode"}], "links": []}
sidecar = tmp_path / "mesh.obj.json"
sidecar.write_text(json.dumps({"workflow": workflow}), encoding="utf-8")
result = extract_model3d_metadata(str(obj))
assert result.ok
assert result.data["quality"] == "full"
def test_extract_model3d_metadata_malformed_sidecar_returns_none_quality(tmp_path: Path) -> None:
obj = tmp_path / "bad.obj"
obj.write_bytes(b"# obj")
sidecar = tmp_path / "bad.obj.json"
sidecar.write_text("!!invalid json!!", encoding="utf-8")
result = extract_model3d_metadata(str(obj))
assert result.ok
assert result.data["quality"] == "none"