Flows and dashboards get ids: the store is addressed by id with a key index, the first start gives every document its key as id, fluksio store migrate --dry-run and ls; the rename routes are gone

This commit is contained in:
2026-11-02 16:54:11 +01:00
parent de13c48c90
commit fae85f5ecb
20 changed files with 1287 additions and 420 deletions
@@ -86,3 +86,29 @@ def test_a_save_based_on_a_version_someone_moved_past_is_refused(
assert stale.status_code == 409
assert stale.json()["detail"]["current_version"] == 1
def test_a_dashboard_is_known_by_an_id_no_save_changes(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
saved = client.put(
f"{PREFIX}/put_identified",
headers=superuser_token_headers,
json=a_dashboard("put_identified"),
).json()
assert saved["id"] == "put_identified"
elsewhere = client.put(
f"{PREFIX}/put_identified",
headers=superuser_token_headers,
json={**saved, "id": "elsewhere"},
)
assert elsewhere.status_code == 422
renamed = client.post(
f"{PREFIX}/put_identified/rename",
headers=superuser_token_headers,
json={"name": "renamed"},
)
assert renamed.status_code in (404, 405)
client.delete(f"{PREFIX}/put_identified", headers=superuser_token_headers)
+39 -98
View File
@@ -77,6 +77,45 @@ def test_name_mismatch_is_rejected(
assert response.status_code == 400
def test_a_flow_is_known_by_an_id_no_save_changes(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
"""For now the id of a new flow is its key, and the URL names both."""
saved = client.put(
f"{PREFIX}/identified",
headers=superuser_token_headers,
json=a_flow("identified"),
).json()["definition"]
assert saved["id"] == "identified"
listed = client.get(f"{PREFIX}/", headers=superuser_token_headers).json()["data"]
assert {"id": "identified", "name": "identified"}.items() <= next(
f for f in listed if f["name"] == "identified"
).items()
elsewhere = client.put(
f"{PREFIX}/identified",
headers=superuser_token_headers,
json={**saved, "id": "elsewhere"},
)
assert elsewhere.status_code == 400
# A key changes by a rename, and that route is not there yet.
renamed = client.post(
f"{PREFIX}/identified/rename",
headers=superuser_token_headers,
json={"new_name": "renamed"},
)
assert renamed.status_code in (404, 405)
# Shaped like an id the server issues, which a new key may not be.
issued = client.put(
f"{PREFIX}/f_k3q7m2xa",
headers=superuser_token_headers,
json=a_flow("f_k3q7m2xa"),
)
assert issued.status_code == 400
client.delete(f"{PREFIX}/identified", headers=superuser_token_headers)
def test_broken_node_is_reported_and_siblings_stay_active(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
@@ -347,68 +386,6 @@ def test_node_types_are_listed(
assert "properties" in by_type["mqtt"]["params_schema"]
def test_rename_flow(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
client.put(f"{PREFIX}/demo", headers=superuser_token_headers, json=a_flow())
response = client.post(
f"{PREFIX}/demo/rename",
headers=superuser_token_headers,
json={"new_name": "demo_renamed"},
)
assert response.status_code == 200
assert response.json()["definition"]["name"] == "demo_renamed"
assert (
client.get(f"{PREFIX}/demo", headers=superuser_token_headers).status_code == 404
)
assert (
client.get(
f"{PREFIX}/demo_renamed", headers=superuser_token_headers
).status_code
== 200
)
# Put it back so the tests that follow find the flow they expect.
client.post(
f"{PREFIX}/demo_renamed/rename",
headers=superuser_token_headers,
json={"new_name": "demo"},
)
def test_rename_onto_a_taken_name_is_refused(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
client.put(f"{PREFIX}/demo", headers=superuser_token_headers, json=a_flow())
client.put(
f"{PREFIX}/occupied", headers=superuser_token_headers, json=a_flow("occupied")
)
response = client.post(
f"{PREFIX}/demo/rename",
headers=superuser_token_headers,
json={"new_name": "occupied"},
)
assert response.status_code == 409
client.delete(f"{PREFIX}/occupied", headers=superuser_token_headers)
def test_rename_rejects_an_invalid_name(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
client.put(f"{PREFIX}/demo", headers=superuser_token_headers, json=a_flow())
response = client.post(
f"{PREFIX}/demo/rename",
headers=superuser_token_headers,
json={"new_name": "Not A Flow Name"},
)
assert response.status_code == 400
def test_a_node_that_raises_answers_with_its_error(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
@@ -467,42 +444,6 @@ def test_acknowledging_a_failure_is_announced_on_the_bus(
client.delete(f"{PREFIX}/acked", headers=superuser_token_headers)
def test_renaming_a_flow_leaves_nothing_under_the_old_name(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
saved = client.put(
f"{PREFIX}/movable", headers=superuser_token_headers, json=a_flow("movable")
).json()
client.put(
f"{PREFIX}/movable/nodes/sensor/source",
headers=superuser_token_headers,
json={"code": WORKING_NODE},
)
client.post(
f"{PREFIX}/movable/publish",
headers=superuser_token_headers,
json={"version": saved["definition"]["version"]},
)
client.post(
f"{PREFIX}/movable/run", headers=superuser_token_headers, json={"inputs": {}}
)
state = client.app.state.flow_controller.state
assert [key for key in state.keys() if "movable." in key]
client.post(
f"{PREFIX}/movable/rename",
headers=superuser_token_headers,
json={"new_name": "moved"},
)
# The values repopulate under the new name on the next run; what the old
# name left behind would sit there for good.
assert [key for key in state.keys() if "movable." in key] == []
client.delete(f"{PREFIX}/moved", headers=superuser_token_headers)
@pytest.mark.anyio
async def test_the_live_socket_ends_when_its_client_goes(
client: TestClient, monkeypatch: pytest.MonkeyPatch
@@ -58,7 +58,6 @@ def test_the_stores_refuse_them_themselves(tmp_path: Path) -> None:
for call in (
lambda: flows.delete_flow(".."),
lambda: flows.read_flow("_lib"),
lambda: flows.rename_flow("..", "elsewhere"),
lambda: flows.write_node_source("demo", "../x", "code"),
lambda: flows.write_lib_source("..", "code"),
lambda: flows.exists("demo\n"),