Files
app/backend/tests/flow/test_store.py
T
stroblmeandClaude Opus 5 ff612623a1 Give both summaries a modified time, and the dashboard list a footprint
Home's "recently modified" order was a proxy — drafts first, then the
version counter, then the name. Both summaries now carry `updated_at`,
read as the mtime of the working copy: every write in the store commits
immediately, so a file's mtime is its commit time, and `git log -1 --
<path>` costs ~990ms across this instance's 15 documents (it walks the
history back to the last commit touching each one, so it is slowest for
the stalest) against ~1.8ms for the stats. No cache needed.

`DashboardSummary` also carries `footprint`: each widget as its type plus
the placement a panel resolves, which is the whole of what the mosaic
draws. That drops the document fetch per tile and with it the cap of
eight, past which tiles showed a name and nothing else. Verified against
this instance's 8 dashboards: the blocks are identical to what the old
client-side derivation produced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CL9zvnnvcp1mvA8o7impxk
2026-09-06 15:26:48 +02:00

134 lines
3.8 KiB
Python

import os
import subprocess
from datetime import UTC, datetime
from pathlib import Path
import pytest
from fluksio.flow.messages import MessageSpec
from fluksio.flow.schemas import FlowDef, NodeDef
from fluksio.flow.store import FlowExists, FlowNotFound, FlowStore
@pytest.fixture
def store(tmp_path: Path) -> FlowStore:
return FlowStore(tmp_path / "flows")
def commit_count(store: FlowStore) -> int:
result = subprocess.run(
["git", "-C", str(store.root), "rev-list", "--count", "HEAD"],
capture_output=True,
text=True,
check=True,
)
return int(result.stdout.strip())
def a_flow() -> FlowDef:
return FlowDef(
name="heating",
nodes=[NodeDef(id="sensor", provides=[MessageSpec(name="temp")])],
)
def test_flow_round_trips(store: FlowStore):
store.write_flow(a_flow())
assert store.list_flows() == ["heating"]
assert store.read_flow("heating").nodes[0].provides[0].name == "temp"
def test_every_change_is_committed(store: FlowStore):
before = commit_count(store)
store.write_flow(a_flow())
assert commit_count(store) == before + 1
store.write_node_source("heating", "sensor", "def process():\n return {}\n")
assert commit_count(store) == before + 2
def test_saving_unchanged_content_does_nothing(store: FlowStore):
store.write_flow(a_flow())
commits = commit_count(store)
# Autosave repeats the same document; history should not grow.
assert store.write_flow(a_flow()) is False
assert commit_count(store) == commits
def test_a_flow_is_modified_when_only_its_node_code_is(store: FlowStore):
"""Node code is saved without touching the flow document."""
store.write_flow(a_flow())
# Backdated so the answer can only have come from the node file.
os.utime(store.root / "heating" / "flow.json", (0, 0))
store.write_node_source(
"heating", "sensor", "def process():\n return {'a': 1}\n"
)
updated = store.updated_at("heating")
assert updated is not None and updated > datetime.fromtimestamp(0, UTC)
def test_a_flow_that_is_not_there_has_no_modified_time(store: FlowStore):
assert store.updated_at("nope") is None
def test_missing_flow_is_reported(store: FlowStore):
with pytest.raises(FlowNotFound):
store.read_flow("nope")
def test_deleting_removes_flow_and_its_nodes(store: FlowStore):
store.write_flow(a_flow())
store.write_node_source("heating", "sensor", "def process():\n return {}\n")
store.delete_flow("heating")
assert store.list_flows() == []
assert not (store.root / "heating").exists()
def test_renaming_a_flow_carries_its_nodes(store: FlowStore):
store.write_flow(a_flow())
store.write_node_source("heating", "sensor", "def process():\n return {}\n")
renamed = store.rename_flow("heating", "warmth")
assert renamed.name == "warmth"
assert store.list_flows() == ["warmth"]
assert "def process" in store.read_node_source("warmth", "sensor")
def test_renaming_a_flow_repoints_the_flows_reading_it(store: FlowStore):
store.write_flow(a_flow())
store.write_flow(
FlowDef(
name="display",
nodes=[
NodeDef(
id="gauge",
# Reads across the flow boundary, so the name must follow.
requires=[MessageSpec(name="heating.temp")],
)
],
)
)
store.rename_flow("heating", "warmth")
display = store.read_flow("display")
assert display.nodes[0].requires[0].name == "warmth.temp"
def test_renaming_onto_an_existing_name_is_refused(store: FlowStore):
store.write_flow(a_flow())
store.write_flow(FlowDef(name="warmth"))
with pytest.raises(FlowExists):
store.rename_flow("heating", "warmth")
assert store.list_flows() == ["heating", "warmth"]