Files
app/backend/tests/flow/test_library.py
T

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