159 lines
5.7 KiB
Python
159 lines
5.7 KiB
Python
"""Dashboards over HTTP: creating one under an issued id, and saving it."""
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from fluksio.core.config import settings
|
|
from fluksio.flow.store import ISSUED_ID
|
|
from tests.utils.flows import put_dashboard
|
|
|
|
PREFIX = f"{settings.API_V1_STR}/dashboards"
|
|
|
|
|
|
def a_dashboard(name: str) -> dict:
|
|
return {
|
|
"name": name,
|
|
"title": "Hall",
|
|
"icon": "gauge",
|
|
"widgets": [
|
|
{
|
|
"id": "temperature",
|
|
"type": "stat",
|
|
"title": "Temperature",
|
|
"layout": {"lg": {"x": 0, "y": 0, "w": 3, "h": 2}},
|
|
"config": {"message": "house.temperature", "dtype": "float"},
|
|
}
|
|
],
|
|
"settings": {"theme": {"value": "dark", "message": "", "dtype": "str"}},
|
|
"version": 0,
|
|
}
|
|
|
|
|
|
def test_a_dashboard_is_created_with_an_id_and_saved_by_it(
|
|
client: TestClient, superuser_token_headers: dict[str, str]
|
|
) -> None:
|
|
"""A first draft under an id the server issues, which every path then names."""
|
|
created = client.post(
|
|
f"{PREFIX}/", headers=superuser_token_headers, json={"name": "made_here"}
|
|
)
|
|
assert created.status_code == 201, created.text
|
|
made = created.json()
|
|
assert ISSUED_ID.fullmatch(made["id"]) and made["name"] == "made_here"
|
|
assert made["version"] == 1 and made["has_draft"] is True
|
|
# A draft alone: nothing was published, so no panel can be shown it.
|
|
assert (
|
|
client.get(
|
|
f"{PREFIX}/{made['id']}", headers=superuser_token_headers
|
|
).status_code
|
|
== 404
|
|
)
|
|
body = {**a_dashboard("made_here"), "version": made["version"]}
|
|
saved = client.put(
|
|
f"{PREFIX}/{made['id']}", headers=superuser_token_headers, json=body
|
|
)
|
|
assert saved.status_code == 200, saved.text
|
|
draft = client.get(
|
|
f"{PREFIX}/{made['id']}?draft=true", headers=superuser_token_headers
|
|
)
|
|
assert draft.json()["widgets"] == body["widgets"]
|
|
(listed,) = client.get(
|
|
f"{PREFIX}/?name=made_here", headers=superuser_token_headers
|
|
).json()["data"]
|
|
assert listed["id"] == made["id"]
|
|
|
|
copy = client.post(
|
|
f"{PREFIX}/{made['id']}/duplicate", headers=superuser_token_headers
|
|
).json()
|
|
assert copy["name"] == "made_here_copy" and copy["id"] != made["id"]
|
|
for dashboard_id in (made["id"], copy["id"]):
|
|
client.delete(f"{PREFIX}/{dashboard_id}", headers=superuser_token_headers)
|
|
|
|
|
|
def test_a_save_neither_creates_nor_renames(
|
|
client: TestClient, superuser_token_headers: dict[str, str]
|
|
) -> None:
|
|
missing = client.put(
|
|
f"{PREFIX}/nowhere",
|
|
headers=superuser_token_headers,
|
|
json={**a_dashboard("nowhere"), "version": 0},
|
|
)
|
|
assert missing.status_code == 404
|
|
saved = put_dashboard(
|
|
client, superuser_token_headers, a_dashboard("put_fixed")
|
|
).json()
|
|
renamed = client.put(
|
|
f"{PREFIX}/put_fixed",
|
|
headers=superuser_token_headers,
|
|
json={**saved, "name": "renamed"},
|
|
)
|
|
assert renamed.status_code == 409
|
|
elsewhere = client.put(
|
|
f"{PREFIX}/put_fixed",
|
|
headers=superuser_token_headers,
|
|
json={**saved, "id": "elsewhere"},
|
|
)
|
|
assert elsewhere.status_code == 422
|
|
client.delete(f"{PREFIX}/put_fixed", headers=superuser_token_headers)
|
|
|
|
|
|
def test_a_save_of_an_existing_dashboard_keeps_what_it_did_not_change(
|
|
client: TestClient, superuser_token_headers: dict[str, str]
|
|
) -> None:
|
|
seeded = a_dashboard("put_updates")
|
|
first = put_dashboard(client, superuser_token_headers, seeded).json()
|
|
|
|
renamed = {**first, "widgets": [{**first["widgets"][0], "title": "Outside"}]}
|
|
second = client.put(
|
|
f"{PREFIX}/put_updates", headers=superuser_token_headers, json=renamed
|
|
)
|
|
|
|
assert second.status_code == 200, second.text
|
|
stored = second.json()
|
|
assert stored["version"] == first["version"] + 1
|
|
assert stored["widgets"][0]["title"] == "Outside"
|
|
# Everything the edit did not name is still what was first written.
|
|
assert stored["icon"] == seeded["icon"]
|
|
assert stored["settings"] == seeded["settings"]
|
|
assert stored["widgets"][0]["layout"] == seeded["widgets"][0]["layout"]
|
|
assert stored["widgets"][0]["config"] == seeded["widgets"][0]["config"]
|
|
|
|
|
|
def test_a_save_based_on_a_version_someone_moved_past_is_refused(
|
|
client: TestClient, superuser_token_headers: dict[str, str]
|
|
) -> None:
|
|
body = a_dashboard("put_conflicts")
|
|
put_dashboard(client, superuser_token_headers, body)
|
|
|
|
stale = client.put(
|
|
f"{PREFIX}/put_conflicts",
|
|
headers=superuser_token_headers,
|
|
json={**body, "version": 1},
|
|
)
|
|
|
|
assert stale.status_code == 409
|
|
assert stale.json()["detail"]["current_version"] == 2
|
|
|
|
|
|
def test_only_a_superuser_pins_a_dashboard_id(
|
|
client: TestClient,
|
|
superuser_token_headers: dict[str, str],
|
|
normal_user_token_headers: dict[str, str],
|
|
) -> None:
|
|
pinned = {"id": "pinned_hall", "name": "pinned_hall"}
|
|
refused = client.post(f"{PREFIX}/", headers=normal_user_token_headers, json=pinned)
|
|
assert refused.status_code == 403
|
|
made = client.post(f"{PREFIX}/", headers=superuser_token_headers, json=pinned)
|
|
assert made.status_code == 201 and made.json()["id"] == "pinned_hall"
|
|
again = client.post(
|
|
f"{PREFIX}/",
|
|
headers=superuser_token_headers,
|
|
json={"id": "pinned_hall", "name": "other_hall"},
|
|
)
|
|
assert again.status_code == 409
|
|
# Anyone may make one under an id of the server's.
|
|
issued = client.post(
|
|
f"{PREFIX}/", headers=normal_user_token_headers, json={"name": "their_hall"}
|
|
)
|
|
assert issued.status_code == 201
|
|
for dashboard_id in ("pinned_hall", issued.json()["id"]):
|
|
client.delete(f"{PREFIX}/{dashboard_id}", headers=superuser_token_headers)
|