Restart safety: transient credential checks are 1013/503, not a refusal
- A device credential that cannot be checked now (device store not running, nonces unreadable) raises DeviceUnavailable: 503 over HTTP, and the /flows/ws handshake and the tunnel stream accept and close with 1013. 1008 (a 403 before accepting) stays for a revoked or invalid one - Device files are written through a temporary file, so a read mid-save no longer looks like a deleted device - The first report turns Touch friendly on only for a device paired since pairings are noted (DeviceState.paired_at), so devices paired before keep the switch they were given - NodeDef reads `device`/`device_policy` from older clients as the worker label, for a release - Tests: transient vs revoked socket, the touch default, the old worker keys, and a restart with a device node and a node_health rule sending no push
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from starlette.websockets import WebSocketDisconnect
|
||||
|
||||
from fluksio.api.routes import devices as devices_route
|
||||
from fluksio.cloud import config as cloud_config
|
||||
@@ -1053,6 +1055,64 @@ def test_unpairing_a_screen_leaves_its_panel_standing(
|
||||
assert client.post(f"{PREFIX}/kitchen/unpair").status_code == 401
|
||||
|
||||
|
||||
def test_a_device_is_refused_only_when_its_credential_is_gone(
|
||||
client: TestClient,
|
||||
superuser_token_headers: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
""" "Cannot say right now" is no refusal: a refused device pairs again."""
|
||||
_panels(client, superuser_token_headers, {"panels": [{"id": "hallway"}]})
|
||||
device = _pair(client, superuser_token_headers, "hallway")
|
||||
socket = f"{settings.API_V1_STR}/flows/ws"
|
||||
store = app.state.device_store
|
||||
|
||||
def unreadable(_: str) -> int:
|
||||
raise ValueError("torn")
|
||||
|
||||
# The nonces do not read: the socket is accepted and told to try again
|
||||
# later, and the device's own read answers 503.
|
||||
monkeypatch.setattr(store, "nonce", unreadable)
|
||||
with (
|
||||
client.websocket_connect(socket, headers=device) as ws,
|
||||
pytest.raises(WebSocketDisconnect) as later,
|
||||
):
|
||||
ws.receive_json()
|
||||
assert later.value.code == 1013
|
||||
assert client.get(f"{PREFIX}/hallway", headers=device).status_code == 503
|
||||
monkeypatch.undo()
|
||||
with client.websocket_connect(socket, headers=device) as ws:
|
||||
assert "values" in ws.receive_json()
|
||||
|
||||
# Unpaired, it is refused before it is accepted, a 403 on the wire.
|
||||
client.post(f"{PREFIX}/hallway/unpair", headers=superuser_token_headers)
|
||||
with (
|
||||
pytest.raises(WebSocketDisconnect) as refused,
|
||||
client.websocket_connect(socket, headers=device),
|
||||
):
|
||||
pass
|
||||
assert refused.value.code == 1008
|
||||
assert client.get(f"{PREFIX}/hallway", headers=device).status_code == 401
|
||||
|
||||
|
||||
def test_only_a_device_paired_here_turns_touch_on_with_its_report(
|
||||
client: TestClient, superuser_token_headers: dict[str, str]
|
||||
) -> None:
|
||||
"""One paired before reports for the first time with an update, its
|
||||
switch set by hand: a knob left off on purpose stays off."""
|
||||
_panels(client, superuser_token_headers, {"panels": [{"id": "knob_old"}]})
|
||||
device = _pair(client, superuser_token_headers, "knob_old")
|
||||
store = app.state.device_store
|
||||
with store._state_lock:
|
||||
states = store._read_states()
|
||||
states["knob_old"].paired_at = None
|
||||
store._write_states(states)
|
||||
|
||||
put = client.put(f"{PREFIX}/knob_old/report", headers=device, json=REPORT)
|
||||
assert put.status_code == 200, put.text
|
||||
read = client.get(f"{PREFIX}/knob_old", headers=device).json()
|
||||
assert read["touch"] is False
|
||||
|
||||
|
||||
class _FakeRedis:
|
||||
"""Just enough Redis for the pending-code store: strings and one zset.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user