148 lines
5.0 KiB
Python
148 lines
5.0 KiB
Python
"""Nodes shared across flows: one source, many instances."""
|
|
|
|
import asyncio
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from fluksio.flow.controller import FlowController
|
|
from fluksio.flow.messages import MessageSpec
|
|
from fluksio.flow.schemas import FlowDef, NodeDef
|
|
from fluksio.flow.store import FlowStore, LibExists, LibNotFound
|
|
from tests.utils.stores import created
|
|
|
|
SOURCE = "def process():\n return {'temp': 1}\n"
|
|
|
|
|
|
@pytest.fixture
|
|
def store(tmp_path: Path) -> FlowStore:
|
|
return FlowStore(tmp_path / "flows")
|
|
|
|
|
|
def a_flow(name: str = "heating") -> FlowDef:
|
|
return FlowDef(
|
|
name=name,
|
|
nodes=[NodeDef(id="sensor", provides=[MessageSpec(name="temp")])],
|
|
)
|
|
|
|
|
|
def test_sharing_moves_the_source_and_points_the_node_at_it(store: FlowStore):
|
|
store.write_flow(a_flow())
|
|
store.write_node_source("heating", "sensor", SOURCE)
|
|
|
|
store.share_node("heating", "sensor", "read_temp")
|
|
|
|
assert store.list_lib() == ["read_temp"]
|
|
assert store.read_lib_source("read_temp") == SOURCE
|
|
node = store.read_flow("heating", draft=True).nodes[0]
|
|
assert node.source_ref == "read_temp"
|
|
# The private copy is gone; the library one is what it runs.
|
|
assert not (store.root / "heating" / "nodes" / "sensor.py").exists()
|
|
|
|
|
|
def test_the_library_is_not_mistaken_for_a_flow(store: FlowStore):
|
|
store.write_flow(a_flow())
|
|
store.write_node_source("heating", "sensor", SOURCE)
|
|
store.share_node("heating", "sensor", "read_temp")
|
|
|
|
assert store.list_flows() == ["heating"]
|
|
assert [flow.name for flow in store.read_all()] == ["heating"]
|
|
|
|
|
|
def test_a_second_flow_can_use_the_same_source(store: FlowStore):
|
|
store.write_flow(a_flow())
|
|
store.write_node_source("heating", "sensor", SOURCE)
|
|
store.share_node("heating", "sensor", "read_temp")
|
|
|
|
store.write_flow(
|
|
FlowDef(
|
|
name="cooling",
|
|
nodes=[
|
|
NodeDef(
|
|
id="sensor",
|
|
source_ref="read_temp",
|
|
provides=[MessageSpec(name="temp")],
|
|
)
|
|
],
|
|
)
|
|
)
|
|
|
|
assert store.usages("read_temp") == ["cooling.sensor", "heating.sensor"]
|
|
|
|
|
|
def test_a_shared_source_change_rebuilds_only_what_is_published(store: FlowStore):
|
|
store.write_flow(a_flow())
|
|
store.write_node_source("heating", "sensor", SOURCE)
|
|
store.share_node("heating", "sensor", "read_temp")
|
|
# Published with the shared node, which the draft has since dropped.
|
|
store.publish_flow("heating", store.read_flow("heating", draft=True).version)
|
|
store.unshare_node("heating", "sensor")
|
|
# Shared in a draft that was never published.
|
|
cooling = a_flow("cooling")
|
|
cooling.nodes[0].source_ref = "read_temp"
|
|
created(store, cooling)
|
|
|
|
controller = FlowController(store)
|
|
rebuilt: list[str] = []
|
|
|
|
async def reload_flow(name: str) -> None:
|
|
rebuilt.append(name)
|
|
|
|
controller.reload_flow = reload_flow # type: ignore[method-assign]
|
|
asyncio.run(controller.reload_lib_users("read_temp"))
|
|
|
|
assert rebuilt == ["heating"]
|
|
|
|
|
|
def test_a_shared_name_is_not_taken_twice(store: FlowStore):
|
|
store.write_flow(a_flow())
|
|
store.write_node_source("heating", "sensor", SOURCE)
|
|
store.share_node("heating", "sensor", "read_temp")
|
|
|
|
store.write_flow(FlowDef(name="cooling", nodes=[NodeDef(id="sensor")]))
|
|
with pytest.raises(LibExists):
|
|
store.share_node("cooling", "sensor", "read_temp")
|
|
|
|
|
|
def test_unsharing_takes_a_private_copy_back(store: FlowStore):
|
|
store.write_flow(a_flow())
|
|
store.write_node_source("heating", "sensor", SOURCE)
|
|
store.share_node("heating", "sensor", "read_temp")
|
|
|
|
store.unshare_node("heating", "sensor")
|
|
|
|
node = store.read_flow("heating", draft=True).nodes[0]
|
|
assert node.source_ref is None
|
|
assert store.read_node_source("heating", "sensor", draft=True) == SOURCE
|
|
# The library keeps its copy for whoever else is using it.
|
|
assert store.list_lib() == ["read_temp"]
|
|
|
|
|
|
def test_a_missing_shared_source_is_reported(store: FlowStore):
|
|
with pytest.raises(LibNotFound):
|
|
store.read_lib_source("nothing_here")
|
|
|
|
|
|
def test_retiring_a_shared_source_leaves_each_user_a_copy(store: FlowStore):
|
|
store.write_flow(a_flow())
|
|
store.write_node_source("heating", "sensor", SOURCE)
|
|
store.share_node("heating", "sensor", "read_temp")
|
|
store.publish_flow("heating", store.read_flow("heating", draft=True).version)
|
|
version = store.read_flow("heating").version
|
|
# Shared in a draft that was never published.
|
|
cooling = a_flow("cooling")
|
|
cooling.nodes[0].source_ref = "read_temp"
|
|
created(store, cooling)
|
|
|
|
store.detach_lib("read_temp")
|
|
|
|
assert store.list_lib() == []
|
|
heating = store.read_flow("heating")
|
|
assert heating.nodes[0].source_ref is None
|
|
assert heating.version == version + 1
|
|
assert store.read_node_source("heating", "sensor") == SOURCE
|
|
# What runs is the same code, so there is nothing to publish.
|
|
assert not store.has_draft("heating")
|
|
assert store.read_flow("cooling", draft=True).nodes[0].source_ref is None
|
|
assert store.read_node_source("cooling", "sensor", draft=True) == SOURCE
|