88 lines
3.4 KiB
Python
88 lines
3.4 KiB
Python
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import re
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
from fastapi.testclient import TestClient
|
|||
|
|
|
|||
|
|
from server.aps_domain.drawing_dxf import inspect_dxf, render_dxf_preview
|
|||
|
|
from server.gateway.app import create_app
|
|||
|
|
|
|||
|
|
|
|||
|
|
_DEFAULT_SAMPLE = Path(r"D:\ItemSpace\14.工业智核\锐扬\5060102101-001-e(1).dxf")
|
|||
|
|
_FORBIDDEN_SVG = re.compile(r"<(?:script|foreignObject)\b|(?:href|xlink:href)\s*=|\bon\w+\s*=", re.I)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _sample_dxf() -> Path:
|
|||
|
|
try:
|
|||
|
|
import ezdxf # noqa: F401
|
|||
|
|
except ImportError as exc:
|
|||
|
|
pytest.skip(f"DXF 运行依赖未完整安装:{exc}; 需要 ezdxf 及其 fontTools 依赖")
|
|||
|
|
path = Path(os.environ.get("APS_DXF_SAMPLE", str(_DEFAULT_SAMPLE)))
|
|||
|
|
if not path.is_file():
|
|||
|
|
pytest.skip(f"真实 DXF 样例不存在:{path};可通过 APS_DXF_SAMPLE 指定")
|
|||
|
|
return path
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_real_dxf_metadata_entities_texts_and_dimensions_contract():
|
|||
|
|
parsed = inspect_dxf(_sample_dxf())
|
|||
|
|
|
|||
|
|
drawing = parsed["drawing"]
|
|||
|
|
assert drawing["metadata"]["acadVersion"].startswith("AC")
|
|||
|
|
assert drawing["modelspaceEntityCount"] >= 9_000
|
|||
|
|
assert drawing["layerCount"] > 0
|
|||
|
|
assert drawing["bbox"]["width"] > 0
|
|||
|
|
assert drawing["bbox"]["height"] > 0
|
|||
|
|
assert sum(parsed["entityStatistics"].values()) == drawing["modelspaceEntityCount"]
|
|||
|
|
assert parsed["entityStatistics"].get("LINE", 0) > 0
|
|||
|
|
assert parsed["entityStatistics"].get("ARC", 0) > 0
|
|||
|
|
assert len(parsed["texts"]) >= 10
|
|||
|
|
assert all(row["text"] and row["evidence"].get("entityHandle") for row in parsed["texts"][:10])
|
|||
|
|
assert len(parsed["dimensions"]) >= 50
|
|||
|
|
assert any(row.get("measurement") is not None for row in parsed["dimensions"])
|
|||
|
|
assert any(row["field"] == "drawingNumber" and row["value"] == "5060102101-001"
|
|||
|
|
for row in parsed["fieldCandidates"])
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_real_dxf_svg_preview_is_standalone_and_safe():
|
|||
|
|
svg = render_dxf_preview(_sample_dxf())
|
|||
|
|
|
|||
|
|
assert svg.startswith("<svg ")
|
|||
|
|
assert svg.endswith("</svg>")
|
|||
|
|
assert "viewBox=" in svg
|
|||
|
|
assert "sourceSha256=" in svg
|
|||
|
|
assert "data-handle=" in svg
|
|||
|
|
assert len(svg.encode("utf-8")) <= 2_000_000
|
|||
|
|
assert not _FORBIDDEN_SVG.search(svg), "SVG 不得包含脚本、事件处理器或外部引用"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_drawing_http_api_contract_when_gateway_routes_are_merged(monkeypatch):
|
|||
|
|
sample = _sample_dxf()
|
|||
|
|
monkeypatch.setenv("APS_AUTH_ENABLED", "0")
|
|||
|
|
monkeypatch.setenv("APS_DRAWING_ALLOWED_ROOTS", str(sample.parent))
|
|||
|
|
app = create_app()
|
|||
|
|
paths = app.openapi().get("paths", {})
|
|||
|
|
required = {"/api/drawings/inspect", "/api/drawings/{drawing_id}", "/api/drawings/{drawing_id}/preview"}
|
|||
|
|
missing = sorted(required - set(paths))
|
|||
|
|
if missing:
|
|||
|
|
pytest.skip(f"依赖未合并:Drawing HTTP API 缺少 {missing}")
|
|||
|
|
|
|||
|
|
client = TestClient(app)
|
|||
|
|
response = client.post("/api/drawings/inspect", json={"path": str(sample)})
|
|||
|
|
assert response.status_code == 200, response.text
|
|||
|
|
data = response.json()
|
|||
|
|
parsed = data.get("parsed") or data.get("drawing") or data
|
|||
|
|
asset = parsed["asset"]
|
|||
|
|
assert asset["kind"] == "DXF"
|
|||
|
|
assert parsed["drawing"]["drawingNumber"] == "5060102101-001"
|
|||
|
|
|
|||
|
|
drawing_id = asset["id"]
|
|||
|
|
detail = client.get(f"/api/drawings/{drawing_id}")
|
|||
|
|
assert detail.status_code == 200, detail.text
|
|||
|
|
preview = client.get(f"/api/drawings/{drawing_id}/preview")
|
|||
|
|
assert preview.status_code == 200, preview.text
|
|||
|
|
assert "<svg" in preview.text
|
|||
|
|
assert not _FORBIDDEN_SVG.search(preview.text)
|