Files
app/backend/tests/flow/test_drafts.py
T
stroblmeandClaude Opus 5 3508713e85 Node settings arrive as keyword arguments, not a params dict
A python node's settings are constants of its own function, so they are passed
the way its ports are: by name. The controller binds them to the compiled
function, the `params` field is gone from the worker and remote protocols, and
a setting sharing a port's name is reported as a node error rather than
shadowing it. The panel's scaffold follows suit and keeps the header in step
with both ports and settings.

The demo's `pace` moves from a flow input to a setting of the training node,
which is what it always was.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUb8YpL2s3gmN9WTACTt4q
2026-08-20 17:47:45 +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 app.flow.messages import MessageSpec
from app.flow.schemas import FlowDef, NodeDef
from app.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"
)