import subprocess from pathlib import Path import pytest from app.flow.messages import MessageSpec from app.flow.schemas import FlowDef, NodeDef from app.flow.store import FlowExists, FlowNotFound, FlowStore @pytest.fixture def store(tmp_path: Path) -> FlowStore: return FlowStore(tmp_path / "flows") def commit_count(store: FlowStore) -> int: result = subprocess.run( ["git", "-C", str(store.root), "rev-list", "--count", "HEAD"], capture_output=True, text=True, check=True, ) return int(result.stdout.strip()) def a_flow() -> FlowDef: return FlowDef( name="heating", nodes=[NodeDef(id="sensor", provides=[MessageSpec(name="temp")])], ) def test_flow_round_trips(store: FlowStore): store.write_flow(a_flow()) assert store.list_flows() == ["heating"] assert store.read_flow("heating").nodes[0].provides[0].name == "temp" def test_every_change_is_committed(store: FlowStore): before = commit_count(store) store.write_flow(a_flow()) assert commit_count(store) == before + 1 store.write_node_source("heating", "sensor", "def process():\n return {}\n") assert commit_count(store) == before + 2 def test_saving_unchanged_content_does_nothing(store: FlowStore): store.write_flow(a_flow()) commits = commit_count(store) # Autosave repeats the same document; history should not grow. assert store.write_flow(a_flow()) is False assert commit_count(store) == commits def test_missing_flow_is_reported(store: FlowStore): with pytest.raises(FlowNotFound): store.read_flow("nope") def test_deleting_removes_flow_and_its_nodes(store: FlowStore): store.write_flow(a_flow()) store.write_node_source("heating", "sensor", "def process():\n return {}\n") store.delete_flow("heating") assert store.list_flows() == [] assert not (store.root / "heating").exists() def test_renaming_a_flow_carries_its_nodes(store: FlowStore): store.write_flow(a_flow()) store.write_node_source("heating", "sensor", "def process():\n return {}\n") renamed = store.rename_flow("heating", "warmth") assert renamed.name == "warmth" assert store.list_flows() == ["warmth"] assert "def process" in store.read_node_source("warmth", "sensor") def test_renaming_a_flow_repoints_the_flows_reading_it(store: FlowStore): store.write_flow(a_flow()) store.write_flow( FlowDef( name="display", nodes=[ NodeDef( id="gauge", # Reads across the flow boundary, so the name must follow. requires=[MessageSpec(name="heating.temp")], ) ], ) ) store.rename_flow("heating", "warmth") display = store.read_flow("display") assert display.nodes[0].requires[0].name == "warmth.temp" def test_renaming_onto_an_existing_name_is_refused(store: FlowStore): store.write_flow(a_flow()) store.write_flow(FlowDef(name="warmth")) with pytest.raises(FlowExists): store.rename_flow("heating", "warmth") assert store.list_flows() == ["heating", "warmth"]