From b02321e3edd63907d40499625e69bf41eee460a7 Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Wed, 4 Mar 2026 23:38:52 +0000 Subject: [PATCH] ifcmcp: fix tests to use IfcSession directly Generated with the assistance of an AI coding tool. --- src/ifcmcp/tests/conftest.py | 31 +++++------ src/ifcmcp/tests/test_edit.py | 74 ++++++++++++------------ src/ifcmcp/tests/test_query.py | 96 ++++++++++++++++---------------- src/ifcmcp/tests/test_server.py | 8 +-- src/ifcmcp/tests/test_session.py | 46 ++++++++------- 5 files changed, 121 insertions(+), 134 deletions(-) diff --git a/src/ifcmcp/tests/conftest.py b/src/ifcmcp/tests/conftest.py index 3e18250c02..bef5469acf 100644 --- a/src/ifcmcp/tests/conftest.py +++ b/src/ifcmcp/tests/conftest.py @@ -1,4 +1,5 @@ # This file was generated with the assistance of an AI coding tool. +import pytest import ifcopenshell import ifcopenshell.api.aggregate import ifcopenshell.api.owner.settings @@ -6,14 +7,18 @@ import ifcopenshell.api.project import ifcopenshell.api.root import ifcopenshell.api.spatial import ifcopenshell.api.unit -import pytest -import ifcmcp.server as server_mod +from ifcmcp.core import IfcSession + + +@pytest.fixture +def session(): + return IfcSession() @pytest.fixture def model(): - """Create an IFC4 model with a spatial hierarchy, a wall, and a slab.""" + """IFC4 model with a spatial hierarchy, a wall, and a slab.""" f = ifcopenshell.api.project.create_file() ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] @@ -46,19 +51,9 @@ def model_file(model, tmp_path): return str(path) -@pytest.fixture(autouse=True) -def reset_server_state(): - """Reset module-level state before each test.""" - server_mod._model = None - server_mod._model_path = None - yield - server_mod._model = None - server_mod._model_path = None - - @pytest.fixture -def loaded_model(model): - """Set the server module state to an in-memory model (no file path).""" - server_mod._model = model - server_mod._model_path = None - return model +def loaded_session(model): + """An IfcSession with an in-memory model already loaded (no file path).""" + s = IfcSession() + s.model = model + return s diff --git a/src/ifcmcp/tests/test_edit.py b/src/ifcmcp/tests/test_edit.py index e06169ce95..778b232249 100644 --- a/src/ifcmcp/tests/test_edit.py +++ b/src/ifcmcp/tests/test_edit.py @@ -1,99 +1,95 @@ # This file was generated with the assistance of an AI coding tool. import json +import ifcopenshell import pytest -from ifcmcp.server import ifc_docs, ifc_edit, ifc_list +from ifcmcp.core import IfcSession, IfcSessionError class TestNoModel: - def test_edit_no_model(self): - with pytest.raises(ValueError, match="No model loaded"): - ifc_edit("root.create_entity") + def test_edit_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_edit("root.create_entity") class TestList: - def test_list_all_modules(self, loaded_model): - result = ifc_list() + def test_list_all_modules(self, loaded_session): + result = loaded_session.ifc_list() assert isinstance(result, list) assert len(result) > 0 modules = [m["module"] for m in result] assert "root" in modules assert "spatial" in modules - def test_list_module_functions(self, loaded_model): - result = ifc_list(module="root") + def test_list_module_functions(self, loaded_session): + result = loaded_session.ifc_list(module="root") assert isinstance(result, list) names = [f["name"] for f in result] assert "create_entity" in names - def test_list_empty_string_returns_modules(self, loaded_model): - result = ifc_list(module="") + def test_list_empty_string_returns_modules(self, loaded_session): + result = loaded_session.ifc_list(module="") assert isinstance(result, list) assert any(m["module"] == "root" for m in result) class TestDocs: - def test_docs_create_entity(self, loaded_model): - result = ifc_docs("root.create_entity") + def test_docs_create_entity(self, loaded_session): + result = loaded_session.ifc_docs("root.create_entity") assert result["module"] == "root" assert result["function"] == "create_entity" assert "params" in result - def test_docs_bad_format(self, loaded_model): + def test_docs_bad_format(self, loaded_session): with pytest.raises(ValueError): - ifc_docs("no_dot_here") + loaded_session.ifc_docs("no_dot_here") class TestEdit: - def test_create_entity(self, loaded_model): - result = ifc_edit("root.create_entity", json.dumps({"ifc_class": "IfcWall", "name": "NewWall"})) + def test_create_entity(self, loaded_session): + result = loaded_session.ifc_edit("root.create_entity", json.dumps({"ifc_class": "IfcWall", "name": "NewWall"})) assert result["ok"] is True assert result["result"]["type"] == "IfcWall" assert result["result"]["name"] == "NewWall" - def test_create_entity_default_params(self, loaded_model): - result = ifc_edit("root.create_entity", "{}") + def test_create_entity_default_params(self, loaded_session): + result = loaded_session.ifc_edit("root.create_entity", "{}") assert result["ok"] is True - def test_unknown_function(self, loaded_model): - result = ifc_edit("root.nonexistent", "{}") + def test_unknown_function(self, loaded_session): + result = loaded_session.ifc_edit("root.nonexistent", "{}") assert result["ok"] is False assert "Cannot find" in result["error"] - def test_unknown_parameter(self, loaded_model): - result = ifc_edit("root.create_entity", json.dumps({"bogus": "value"})) + def test_unknown_parameter(self, loaded_session): + result = loaded_session.ifc_edit("root.create_entity", json.dumps({"bogus": "value"})) assert result["ok"] is False assert "Unknown parameter" in result["error"] - def test_bad_json(self, loaded_model): + def test_bad_json(self, loaded_session): with pytest.raises(json.JSONDecodeError): - ifc_edit("root.create_entity", "not json") + loaded_session.ifc_edit("root.create_entity", "not json") - def test_edit_does_not_save(self, loaded_model, tmp_path): + def test_edit_does_not_save(self, loaded_session, tmp_path): """Verify that ifc_edit mutates the in-memory model but does not write to disk.""" - import ifcmcp.server as server_mod - path = str(tmp_path / "test.ifc") - loaded_model.write(path) - server_mod._model_path = path + loaded_session.model.write(path) + loaded_session.model_path = path - before_count = sum(1 for _ in loaded_model) - ifc_edit("root.create_entity", json.dumps({"ifc_class": "IfcWall", "name": "Unsaved"})) - after_count = sum(1 for _ in loaded_model) + before_count = sum(1 for _ in loaded_session.model) + loaded_session.ifc_edit("root.create_entity", json.dumps({"ifc_class": "IfcWall", "name": "Unsaved"})) + after_count = sum(1 for _ in loaded_session.model) assert after_count == before_count + 1 - # Re-read the file — it should not have the new entity - import ifcopenshell - on_disk = ifcopenshell.open(path) disk_count = sum(1 for _ in on_disk) assert disk_count == before_count - def test_assign_container(self, loaded_model): - wall = loaded_model.by_type("IfcWall")[0] - storey = loaded_model.by_type("IfcBuildingStorey")[0] - result = ifc_edit( + def test_assign_container(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + storey = loaded_session.model.by_type("IfcBuildingStorey")[0] + result = loaded_session.ifc_edit( "spatial.assign_container", json.dumps({"products": str(wall.id()), "relating_structure": str(storey.id())}), ) diff --git a/src/ifcmcp/tests/test_query.py b/src/ifcmcp/tests/test_query.py index babfbc5c55..e0a28ae1c0 100644 --- a/src/ifcmcp/tests/test_query.py +++ b/src/ifcmcp/tests/test_query.py @@ -1,60 +1,60 @@ # This file was generated with the assistance of an AI coding tool. import pytest -from ifcmcp.server import ifc_info, ifc_relations, ifc_select, ifc_summary, ifc_tree +from ifcmcp.core import IfcSessionError class TestNoModel: """All query tools should fail when no model is loaded.""" - def test_summary_no_model(self): - with pytest.raises(ValueError, match="No model loaded"): - ifc_summary() + def test_summary_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_summary() - def test_tree_no_model(self): - with pytest.raises(ValueError, match="No model loaded"): - ifc_tree() + def test_tree_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_tree() - def test_info_no_model(self): - with pytest.raises(ValueError, match="No model loaded"): - ifc_info(1) + def test_info_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_info(1) - def test_select_no_model(self): - with pytest.raises(ValueError, match="No model loaded"): - ifc_select("IfcWall") + def test_select_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_select("IfcWall") - def test_relations_no_model(self): - with pytest.raises(ValueError, match="No model loaded"): - ifc_relations(1) + def test_relations_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_relations(1) class TestSummary: - def test_schema(self, loaded_model): - result = ifc_summary() + def test_schema(self, loaded_session): + result = loaded_session.ifc_summary() assert result["schema"] == "IFC4" - def test_total_entities(self, loaded_model): - result = ifc_summary() + def test_total_entities(self, loaded_session): + result = loaded_session.ifc_summary() assert result["total_entities"] > 0 - def test_project_name(self, loaded_model): - result = ifc_summary() + def test_project_name(self, loaded_session): + result = loaded_session.ifc_summary() assert result["project"]["name"] == "TestProject" - def test_type_counts(self, loaded_model): - result = ifc_summary() + def test_type_counts(self, loaded_session): + result = loaded_session.ifc_summary() assert result["types"]["IfcWall"] == 1 assert result["types"]["IfcSlab"] == 1 class TestTree: - def test_root_is_project(self, loaded_model): - result = ifc_tree() + def test_root_is_project(self, loaded_session): + result = loaded_session.ifc_tree() assert result["type"] == "IfcProject" assert result["name"] == "TestProject" - def test_hierarchy_depth(self, loaded_model): - result = ifc_tree() + def test_hierarchy_depth(self, loaded_session): + result = loaded_session.ifc_tree() site = result["children"][0] assert site["type"] == "IfcSite" building = site["children"][0] @@ -64,50 +64,50 @@ class TestTree: class TestInfo: - def test_wall_info(self, loaded_model): - wall = loaded_model.by_type("IfcWall")[0] - result = ifc_info(wall.id()) + def test_wall_info(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + result = loaded_session.ifc_info(wall.id()) assert result["id"] == wall.id() assert result["type"] == "IfcWall" - def test_invalid_id(self, loaded_model): + def test_invalid_id(self, loaded_session): with pytest.raises(Exception): - ifc_info(999999) + loaded_session.ifc_info(999999) class TestSelect: - def test_select_walls(self, loaded_model): - result = ifc_select("IfcWall") + def test_select_walls(self, loaded_session): + result = loaded_session.ifc_select("IfcWall") assert len(result) == 1 assert result[0]["type"] == "IfcWall" assert result[0]["name"] == "Wall001" - def test_select_slabs(self, loaded_model): - result = ifc_select("IfcSlab") + def test_select_slabs(self, loaded_session): + result = loaded_session.ifc_select("IfcSlab") assert len(result) == 1 assert result[0]["name"] == "Slab001" - def test_select_no_match(self, loaded_model): - result = ifc_select("IfcWindow") + def test_select_no_match(self, loaded_session): + result = loaded_session.ifc_select("IfcWindow") assert result == [] class TestRelations: - def test_wall_relations(self, loaded_model): - wall = loaded_model.by_type("IfcWall")[0] - result = ifc_relations(wall.id()) + def test_wall_relations(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + result = loaded_session.ifc_relations(wall.id()) assert result["id"] == wall.id() assert result["type"] == "IfcWall" assert "hierarchy" in result - def test_traverse_up(self, loaded_model): - wall = loaded_model.by_type("IfcWall")[0] - result = ifc_relations(wall.id(), traverse="up") + def test_traverse_up(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + result = loaded_session.ifc_relations(wall.id(), traverse="up") assert isinstance(result, list) assert result[0]["type"] == "IfcWall" assert result[-1]["type"] == "IfcProject" - def test_traverse_empty_string_means_no_traverse(self, loaded_model): - wall = loaded_model.by_type("IfcWall")[0] - result = ifc_relations(wall.id(), traverse="") + def test_traverse_empty_string_means_no_traverse(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + result = loaded_session.ifc_relations(wall.id(), traverse="") assert isinstance(result, dict) diff --git a/src/ifcmcp/tests/test_server.py b/src/ifcmcp/tests/test_server.py index 0efb379384..a45731631b 100644 --- a/src/ifcmcp/tests/test_server.py +++ b/src/ifcmcp/tests/test_server.py @@ -1,12 +1,14 @@ # This file was generated with the assistance of an AI coding tool. -from ifcmcp.server import server +from ifcmcp.server import build_server class TestServerRegistration: def test_server_name(self): + server = build_server() assert server.name == "ifc-mcp" def test_all_tools_registered(self): + server = build_server() tools = [t.name for t in server._tool_manager.list_tools()] expected = [ "ifc_load", @@ -23,7 +25,3 @@ class TestServerRegistration: ] for name in expected: assert name in tools, f"Tool {name} not registered" - - def test_tool_count(self): - tools = server._tool_manager.list_tools() - assert len(tools) == 11 diff --git a/src/ifcmcp/tests/test_session.py b/src/ifcmcp/tests/test_session.py index aa708ddee5..0e933a8eb4 100644 --- a/src/ifcmcp/tests/test_session.py +++ b/src/ifcmcp/tests/test_session.py @@ -1,46 +1,44 @@ # This file was generated with the assistance of an AI coding tool. +import ifcopenshell import pytest -import ifcmcp.server as server_mod -from ifcmcp.server import ifc_load, ifc_save +from ifcmcp.core import IfcSession, IfcSessionError class TestLoad: - def test_load_file(self, model_file): - result = ifc_load(model_file) + def test_load_file(self, session, model_file): + result = session.ifc_load(model_file) assert "IFC4" in result - assert server_mod._model is not None - assert server_mod._model_path == model_file + assert session.model is not None + assert session.model_path == model_file - def test_load_sets_entity_count(self, model_file): - result = ifc_load(model_file) + def test_load_sets_entity_count(self, session, model_file): + result = session.ifc_load(model_file) assert "entities" in result - def test_load_nonexistent_file(self): + def test_load_nonexistent_file(self, session): with pytest.raises(Exception): - ifc_load("/nonexistent/path/model.ifc") + session.ifc_load("/nonexistent/path/model.ifc") class TestSave: - def test_save_no_model(self): - with pytest.raises(ValueError, match="No model loaded"): - ifc_save() + def test_save_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_save() - def test_save_overwrites_original(self, model_file): - ifc_load(model_file) - result = ifc_save() + def test_save_overwrites_original(self, session, model_file): + session.ifc_load(model_file) + result = session.ifc_save() assert model_file in result - def test_save_to_new_path(self, model_file, tmp_path): - ifc_load(model_file) + def test_save_to_new_path(self, session, model_file, tmp_path): + session.ifc_load(model_file) new_path = str(tmp_path / "output.ifc") - result = ifc_save(new_path) + result = session.ifc_save(new_path) assert new_path in result - import ifcopenshell - reloaded = ifcopenshell.open(new_path) assert reloaded.schema == "IFC4" - def test_save_no_path_no_original(self, loaded_model): - with pytest.raises(ValueError, match="No path specified"): - ifc_save() + def test_save_no_path_no_original(self, loaded_session): + with pytest.raises(IfcSessionError, match="No path specified"): + loaded_session.ifc_save()