mirror of
https://github.com/brunopostle/ifcurl.git
synced 2026-07-12 02:08:13 +00:00
133 lines
4.9 KiB
Python
133 lines
4.9 KiB
Python
|
|
"""Tests for ifcurl.diff — step ID extraction and entity expansion."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from ifcurl.diff import expand_step_ids, step_ids_from_diff
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# step_ids_from_diff
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
SAMPLE_DIFF = """\
|
||
|
|
diff --git a/model.ifc b/model.ifc
|
||
|
|
--- a/model.ifc
|
||
|
|
+++ b/model.ifc
|
||
|
|
@@ -1,5 +1,5 @@
|
||
|
|
#1=IFCPROJECT('abc');
|
||
|
|
-#10=IFCWALL('old_wall');
|
||
|
|
-#11=IFCWALLSTANDARDCASE('deleted_wall');
|
||
|
|
+#10=IFCWALL('new_wall');
|
||
|
|
+#99=IFCWALLSTANDARDCASE('added_wall');
|
||
|
|
#20=IFCSLAB('slab');
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
class TestStepIdsFromDiff:
|
||
|
|
def test_modified_id_in_both_sets(self):
|
||
|
|
ids = step_ids_from_diff(SAMPLE_DIFF)
|
||
|
|
assert 10 in ids["modified"]
|
||
|
|
|
||
|
|
def test_added_id_only_in_inserted(self):
|
||
|
|
ids = step_ids_from_diff(SAMPLE_DIFF)
|
||
|
|
assert 99 in ids["added"]
|
||
|
|
|
||
|
|
def test_removed_id_only_in_deleted(self):
|
||
|
|
ids = step_ids_from_diff(SAMPLE_DIFF)
|
||
|
|
assert 11 in ids["removed"]
|
||
|
|
|
||
|
|
def test_unchanged_id_absent(self):
|
||
|
|
ids = step_ids_from_diff(SAMPLE_DIFF)
|
||
|
|
assert 1 not in ids["added"]
|
||
|
|
assert 1 not in ids["modified"]
|
||
|
|
assert 1 not in ids["removed"]
|
||
|
|
assert 20 not in ids["added"]
|
||
|
|
assert 20 not in ids["modified"]
|
||
|
|
assert 20 not in ids["removed"]
|
||
|
|
|
||
|
|
def test_modified_not_in_added_or_removed(self):
|
||
|
|
ids = step_ids_from_diff(SAMPLE_DIFF)
|
||
|
|
assert 10 not in ids["added"]
|
||
|
|
assert 10 not in ids["removed"]
|
||
|
|
|
||
|
|
def test_empty_diff(self):
|
||
|
|
ids = step_ids_from_diff("")
|
||
|
|
assert ids == {"added": set(), "modified": set(), "removed": set()}
|
||
|
|
|
||
|
|
def test_context_lines_ignored(self):
|
||
|
|
diff = " #5=IFCSITE('site');\n"
|
||
|
|
ids = step_ids_from_diff(diff)
|
||
|
|
assert 5 not in ids["added"] and 5 not in ids["removed"] and 5 not in ids["modified"]
|
||
|
|
|
||
|
|
def test_git_header_lines_ignored(self):
|
||
|
|
diff = "--- a/model.ifc\n+++ b/model.ifc\n"
|
||
|
|
ids = step_ids_from_diff(diff)
|
||
|
|
assert ids == {"added": set(), "modified": set(), "removed": set()}
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# expand_step_ids — requires a real ifcopenshell model
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
class TestExpandStepIds:
|
||
|
|
def test_product_promoted_to_modified(self, model_with_geometry):
|
||
|
|
"""An IfcProduct step ID in 'modified' stays in modified after expansion."""
|
||
|
|
# Find a real product step ID in the model
|
||
|
|
products = list(model_with_geometry.by_type("IfcProduct"))
|
||
|
|
assert products, "fixture must contain at least one IfcProduct"
|
||
|
|
pid = products[0].id()
|
||
|
|
|
||
|
|
raw = {"added": set(), "modified": {pid}, "removed": set()}
|
||
|
|
expanded = expand_step_ids(model_with_geometry, raw)
|
||
|
|
|
||
|
|
assert pid in expanded["modified"]
|
||
|
|
|
||
|
|
def test_added_ids_not_moved_to_modified(self, model_with_geometry):
|
||
|
|
"""IDs in 'added' must not be demoted into 'modified' by expansion."""
|
||
|
|
products = list(model_with_geometry.by_type("IfcProduct"))
|
||
|
|
assert products
|
||
|
|
pid = products[0].id()
|
||
|
|
|
||
|
|
raw = {"added": {pid}, "modified": set(), "removed": set()}
|
||
|
|
expanded = expand_step_ids(model_with_geometry, raw)
|
||
|
|
|
||
|
|
assert pid in expanded["added"]
|
||
|
|
assert pid not in expanded["modified"]
|
||
|
|
|
||
|
|
def test_removed_ids_unchanged(self, model_with_geometry):
|
||
|
|
"""The 'removed' set is never altered by expansion."""
|
||
|
|
raw = {"added": set(), "modified": set(), "removed": {9999}}
|
||
|
|
expanded = expand_step_ids(model_with_geometry, raw)
|
||
|
|
assert expanded["removed"] == {9999}
|
||
|
|
|
||
|
|
def test_nonexistent_id_silently_skipped(self, model_with_geometry):
|
||
|
|
"""Step IDs absent from the model don't cause errors; they pass through unchanged."""
|
||
|
|
raw = {"added": set(), "modified": {99999999}, "removed": set()}
|
||
|
|
expanded = expand_step_ids(model_with_geometry, raw)
|
||
|
|
# The ID passes through unmodified — expand adds no extra products for it
|
||
|
|
assert expanded["modified"] == {99999999}
|
||
|
|
|
||
|
|
def test_shape_representation_expands_to_product(self, model_with_geometry):
|
||
|
|
"""A changed IfcShapeRepresentation expands to its parent IfcProduct."""
|
||
|
|
reps = list(model_with_geometry.by_type("IfcShapeRepresentation"))
|
||
|
|
if not reps:
|
||
|
|
pytest.skip("no IfcShapeRepresentation in fixture")
|
||
|
|
|
||
|
|
rep = reps[0]
|
||
|
|
products_via_rep = set()
|
||
|
|
for prod_rep in rep.OfProductRepresentation:
|
||
|
|
for product in prod_rep.ShapeOfProduct:
|
||
|
|
products_via_rep.add(product.id())
|
||
|
|
|
||
|
|
if not products_via_rep:
|
||
|
|
pytest.skip("representation not linked to any product in fixture")
|
||
|
|
|
||
|
|
raw = {"added": set(), "modified": {rep.id()}, "removed": set()}
|
||
|
|
expanded = expand_step_ids(model_with_geometry, raw)
|
||
|
|
|
||
|
|
for pid in products_via_rep:
|
||
|
|
assert pid in expanded["modified"]
|