Files
app/backend/tests/flow/test_drafts.py
T
stroblmeandClaude Opus 5 640654bd66 Rename the import package app to fluksio
A wheel whose top-level module is `app` collides with anything else in a
user's venv, so the package that is about to be published takes the name
it is published under. Only the Python package moves; the repo, the
Docker WORKDIR and the compose project keep theirs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 21:48:05 +02:00

134 lines
4.3 KiB
Python

"""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"
)