ARCH - TASKER BIM: серверный CAD-шлюз и независимый просмотр из Ops

This commit is contained in:
DCCONSTRUCTIONS
2026-07-30 07:35:23 +03:00
parent 24481ea432
commit ad04910209
10 changed files with 1252 additions and 499 deletions
@@ -0,0 +1,102 @@
# Copyright (c) 2023-present Plane Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.
import base64
import json
from types import SimpleNamespace
from urllib.parse import parse_qs, urlsplit
import pytest
from plane.utils.nodedc_bim_gateway import (
BimGatewayError,
build_bim_embed_url,
get_bim_direct_model_type,
get_bim_model_type,
resolve_bim_viewer_model,
to_bim_relative_asset_url,
)
@pytest.mark.unit
def test_cad_extensions_are_normalized():
assert get_bim_model_type("part.STEP") == "step"
assert get_bim_model_type("part.stp") == "step"
assert get_bim_model_type("part.IGES") == "iges"
assert get_bim_model_type("part.igs") == "iges"
assert get_bim_direct_model_type("part.glb") == "gltf"
assert get_bim_direct_model_type("part.step") is None
@pytest.mark.unit
def test_only_managed_public_bim_sources_are_accepted(monkeypatch):
monkeypatch.setenv("PLANE_NODEDC_BIM_PUBLIC_URL", "https://bim.example")
assert (
to_bim_relative_asset_url("https://bim.example/uploads/project/model.glb?version=2")
== "/uploads/project/model.glb?version=2"
)
assert to_bim_relative_asset_url("/data/project/model.xkt") == "/data/project/model.xkt"
assert to_bim_relative_asset_url("https://attacker.example/uploads/model.glb") is None
assert to_bim_relative_asset_url("/api/auth/session") is None
@pytest.mark.unit
def test_embed_grant_is_short_lived_and_bound_to_ops_context(monkeypatch):
monkeypatch.setenv("PLANE_NODEDC_BIM_PUBLIC_URL", "https://bim.example")
monkeypatch.setenv("PLANE_NODEDC_BIM_EMBED_SECRET", "test-embed-secret")
monkeypatch.setenv("PLANE_NODEDC_BIM_EMBED_TTL_SECONDS", "180")
model = {
"url": "/uploads/project/model.glb",
"settingsSrc": "/uploads/project/model.glb",
"type": "gltf",
"name": "model.glb",
}
viewer_url, expires_at = build_bim_embed_url(
model,
SimpleNamespace(id="user-1"),
"workspace",
"project-1",
"issue-1",
"attachment-1",
)
query = parse_qs(urlsplit(viewer_url).query)
encoded_payload = query["token"][0].split(".", 1)[0]
encoded_payload += "=" * (-len(encoded_payload) % 4)
claims = json.loads(base64.urlsafe_b64decode(encoded_payload).decode("utf-8"))
assert urlsplit(viewer_url).path == "/embed/tasker"
assert claims["sub"] == "user-1"
assert claims["workspace"] == "workspace"
assert claims["project"] == "project-1"
assert claims["issue"] == "issue-1"
assert claims["attachment"] == "attachment-1"
assert claims["model"] == model
assert expires_at - claims["iat"] == 180
@pytest.mark.unit
def test_viewer_model_rejects_unmanaged_or_unready_sources(monkeypatch):
monkeypatch.setenv("PLANE_NODEDC_BIM_PUBLIC_URL", "https://bim.example")
with pytest.raises(BimGatewayError, match="still being prepared"):
resolve_bim_viewer_model(
{
"originalFilename": "part.step",
"previewAvailable": False,
"src": "https://bim.example/uploads/project/part.step",
"type": "step",
}
)
with pytest.raises(BimGatewayError, match="outside the managed BIM storage"):
resolve_bim_viewer_model(
{
"originalFilename": "model.glb",
"previewAvailable": True,
"src": "https://attacker.example/uploads/model.glb",
"type": "gltf",
}
)