"""Prove native workspace/broker before PCB/placement stop importing pipeline.py.""" from __future__ import annotations import ast import inspect from pathlib import Path import pytest from backend.services.job_workspace import ( EventBroker, PipelineWorkspace, set_broker, ) from backend.services.job_workspace import broker as native_broker from backend.services.pipeline import EventBroker as InheritedBroker from backend.services.pipeline import PipelineWorkspace as InheritedWorkspace from backend.services.storage import LocalStorageBackend def test_native_job_workspace_does_not_import_pipeline(): import backend.services.job_workspace as jw tree = ast.parse(Path(jw.__file__).read_text()) imported = [ node.module for node in ast.walk(tree) if isinstance(node, ast.ImportFrom) and node.module ] assert "backend.services.pipeline" not in imported assert "backend.periscopex.graph" not in imported assert "backend.periscopex.parsers" not in imported def test_event_broker_publish_subscribe_and_replay(): b = EventBroker() b.publish("p1", "pcb_step_update", {"stage": "parse_pcb"}) q = b.subscribe("p1") msg = q.get_nowait() assert msg == {"event": "pcb_step_update", "data": {"stage": "parse_pcb"}} b.publish("p1", "pcb_complete", {"findings": 1}) msg2 = q.get_nowait() assert msg2["event"] == "pcb_complete" b.unsubscribe("p1", q) b.clear_history("p1") q2 = b.subscribe("p1") assert q2.empty() def test_set_broker_swaps_module_singleton(): original = native_broker replacement = EventBroker() set_broker(replacement) import backend.services.job_workspace as jw assert jw.broker is replacement set_broker(original) assert jw.broker is original @pytest.mark.asyncio async def test_workspace_mirrors_storage_and_netlist_like_inherited(tmp_path): storage = LocalStorageBackend(tmp_path) prefix = "users/local/projects/ws1" storage.write_json(f"{prefix}/project.json", {"id": "ws1"}) bom = tmp_path / "bom.csv" bom.write_text("Reference,MPN\nU1,PART\n") storage.upload_from_local(bom, f"{prefix}/uploads/bom.csv") net = tmp_path / "netlist.edn" net.write_text("(edif dummy)") storage.upload_from_local(net, f"{prefix}/uploads/netlist.edn") native_net = None inherited_net = None async with PipelineWorkspace(storage, "local", "ws1") as nws: native_net = nws.netlist_local_path() assert nws.local_path("uploads/bom.csv").is_file() assert native_net.name == "netlist.edn" nws.local_path("pcb_report.json").write_text("{}") nws._upload_file("pcb_report.json") async with InheritedWorkspace(storage, "local", "ws1") as iws: inherited_net = iws.netlist_local_path() assert inherited_net.name == native_net.name assert storage.exists(f"{prefix}/pcb_report.json") def test_pcb_and_placement_do_not_import_pipeline_py(): import backend.services.pcb_pipeline as pcb import backend.services.placement_pipeline as place for mod in (pcb, place): tree = ast.parse(Path(mod.__file__).read_text()) imported = [ node.module for node in ast.walk(tree) if isinstance(node, ast.ImportFrom) and node.module ] assert "backend.services.pipeline" not in imported assert "backend.services.job_workspace" in imported def test_pipeline_reexports_native_workspace(): from backend.services import job_workspace as jw from backend.services import pipeline as pipe assert pipe.PipelineWorkspace is jw.PipelineWorkspace assert pipe.EventBroker is jw.EventBroker assert inspect.signature(EventBroker.publish) == inspect.signature( InheritedBroker.publish ) native_methods = {"local_path", "netlist_local_path", "_upload_file", "_upload_dir"} for name in native_methods: assert hasattr(InheritedWorkspace, name) assert hasattr(PipelineWorkspace, name)