"""Editing writes drafts; only publishing changes what the engine reads.""" from pathlib import Path import pytest from fluksio.flow.messages import MessageSpec from fluksio.flow.schemas import FlowDef, NodeDef from fluksio.flow.store import FlowStore, StaleVersion SOURCE = "def process():\n return {}\n" EDITED = "def process():\n return {'temp': 1}\n" @pytest.fixture def store(tmp_path: Path) -> FlowStore: return FlowStore(tmp_path / "flows") def a_flow(title: str = "") -> FlowDef: return FlowDef( name="heating", title=title, nodes=[NodeDef(id="sensor", provides=[MessageSpec(name="temp")])], ) def test_a_draft_is_invisible_to_the_engine(store: FlowStore): store.write_draft(a_flow(), 0) assert store.has_draft("heating") assert store.list_flows() == ["heating"] # the editor sees it assert store.read_all() == [] # the engine does not def test_the_version_counter_advances_per_save(store: FlowStore): first = store.write_draft(a_flow(), 0) assert first.version == 1 second = store.write_draft(a_flow(title="Warm"), first.version) assert second.version == 2 def test_resaving_identical_content_neither_bumps_nor_commits(store: FlowStore): stored = store.write_draft(a_flow(), 0) again = store.write_draft(a_flow(), stored.version) assert again.version == stored.version def test_a_stale_write_is_refused(store: FlowStore): store.write_draft(a_flow(), 0) store.write_draft(a_flow(title="Mine"), 1) # A second client still holding version 1 must not overwrite version 2. with pytest.raises(StaleVersion) as excinfo: store.write_draft(a_flow(title="Theirs"), 1) assert excinfo.value.current == 2 def test_publishing_promotes_the_draft_and_its_sources(store: FlowStore): stored = store.write_draft(a_flow(title="Warm"), 0) store.write_node_source("heating", "sensor", EDITED, draft=True) published = store.publish_flow("heating", stored.version) assert published.title == "Warm" assert not store.has_draft("heating") assert store.read_flow("heating").title == "Warm" assert store.read_node_source("heating", "sensor") == EDITED assert [flow.name for flow in store.read_all()] == ["heating"] def test_publishing_a_stale_draft_is_refused(store: FlowStore): store.write_draft(a_flow(), 0) with pytest.raises(StaleVersion): store.publish_flow("heating", 99) def test_discarding_goes_back_to_what_is_running(store: FlowStore): store.write_flow(a_flow(title="Published")) store.write_node_source("heating", "sensor", SOURCE) store.write_draft(a_flow(title="Edited"), a_flow().version) store.write_node_source("heating", "sensor", EDITED, draft=True) restored = store.discard_draft("heating") assert restored.title == "Published" assert not store.has_draft("heating") assert store.read_node_source("heating", "sensor", draft=True) == SOURCE def test_an_edited_source_alone_counts_as_a_draft(store: FlowStore): """Editing only code still has to be published before the engine runs it.""" store.write_flow(a_flow()) store.write_node_source("heating", "sensor", SOURCE) store.write_node_source("heating", "sensor", EDITED, draft=True) assert store.has_draft("heating") assert store.read_node_source("heating", "sensor") == SOURCE assert store.read_node_source("heating", "sensor", draft=True) == EDITED store.publish_flow("heating", store.read_flow("heating").version) assert store.read_node_source("heating", "sensor") == EDITED def test_resaving_the_published_source_creates_no_draft(store: FlowStore): store.write_flow(a_flow()) store.write_node_source("heating", "sensor", SOURCE) assert store.write_node_source("heating", "sensor", SOURCE, draft=True) is False assert not store.has_draft("heating") def test_renaming_carries_the_draft_and_repoints_other_drafts(store: FlowStore): store.write_draft(a_flow(), 0) store.write_draft( FlowDef( name="display", nodes=[NodeDef(id="gauge", requires=[MessageSpec(name="heating.temp")])], ), 0, ) renamed = store.rename_flow("heating", "warmth") assert renamed.name == "warmth" assert store.has_draft("warmth") assert store.read_flow("display", draft=True).nodes[0].requires[0].name == ( "warmth.temp" )