From ef9471546dc624f693d34fb405601eb5dabe4b3d Mon Sep 17 00:00:00 2001 From: jaylfc Date: Mon, 26 Oct 2026 01:09:58 +0000 Subject: [PATCH] Session mode availability must require both units Docs-Reviewed: behavior change limited to device gate logic, no API contract change requiring doc updates ``` FAILED tests/test_session_mode.py::test_state_unavailable_on_desk_without_fallback - assert True is False FAILED tests/test_session_mode.py::test_switch_refused_on_desk_without_fallback - AttributeError: 'dict' object has no attribute 'status_code' 2 failed, 4 passed in 0.29s ``` All tests pass after fix. --- .../tsk-mtf4dv-session-mode-needs-fallback.md | 1 + tests/test_session_mode.py | 48 +++++++++++++++++++ tinyagentos/routes/system.py | 6 ++- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 changelog.d/tsk-mtf4dv-session-mode-needs-fallback.md diff --git a/changelog.d/tsk-mtf4dv-session-mode-needs-fallback.md b/changelog.d/tsk-mtf4dv-session-mode-needs-fallback.md new file mode 100644 index 000000000..0a18b0c69 --- /dev/null +++ b/changelog.d/tsk-mtf4dv-session-mode-needs-fallback.md @@ -0,0 +1 @@ +### Fixed\n- Session mode availability now requires both taos-kiosk.service and plasma-mobile.service; desks without the fallback unit are not offered a switch. diff --git a/tests/test_session_mode.py b/tests/test_session_mode.py index c748bd721..a741222bc 100644 --- a/tests/test_session_mode.py +++ b/tests/test_session_mode.py @@ -92,3 +92,51 @@ async def json(self): resp = await system_routes.set_session_mode(_Req()) assert resp.status_code == 400 assert started == [] + + +@pytest.mark.asyncio +async def test_state_unavailable_on_desk_without_fallback(monkeypatch): + async def fake(*args): + if args[0] == "cat": + unit = args[-1] + if unit == "taos-kiosk.service": + return 0, "# unit" + if unit == "plasma-mobile.service": + return 1, "No files found" + return 0, "# unit" + return 3, "inactive" + + monkeypatch.setattr(system_routes, "_systemctl", fake) + state = await system_routes._session_mode_state() + assert state["available"] is False + + +@pytest.mark.asyncio +async def test_switch_refused_on_desk_without_fallback(monkeypatch): + started: list[str] = [] + + async def fake(*args): + if args[0] == "start": + started.append(args[-1]) + return 0, "" + if args[0] == "cat": + unit = args[-1] + if unit == "taos-kiosk.service": + return 0, "# unit" + if unit == "plasma-mobile.service": + return 1, "No files found" + return 1, "No files found" + return 3, "inactive" + + monkeypatch.setattr(system_routes, "_systemctl", fake) + + class _Req: + async def body(self): + return b'{"mode":"kiosk"}' + + async def json(self): + return {"mode": "kiosk"} + + resp = await system_routes.set_session_mode(_Req()) + assert resp.status_code == 404 + assert started == [] diff --git a/tinyagentos/routes/system.py b/tinyagentos/routes/system.py index 2947bb279..36ad015f1 100644 --- a/tinyagentos/routes/system.py +++ b/tinyagentos/routes/system.py @@ -358,7 +358,9 @@ async def _session_mode_state() -> dict: # and rc is not. An unknown unit reports "inactive" too, so availability is # probed separately with `cat`, which fails only when the unit is absent. cat_rc, _ = await _systemctl("cat", _SESSION_UNITS["kiosk"]) - available = cat_rc == 0 + # availability needs both units because a desk has the kiosk unit and no fallback + plasma_cat_rc, _ = await _systemctl("cat", _SESSION_UNITS["plasma"]) + available = cat_rc == 0 and plasma_cat_rc == 0 _, plasma_state = await _systemctl("is-active", _SESSION_UNITS["plasma"]) if kiosk_state == "active": current = "kiosk" @@ -397,7 +399,7 @@ async def set_session_mode(request: Request): state = await _session_mode_state() if not state["available"]: return JSONResponse( - {"error": "this device has no taOS kiosk session installed"}, status_code=404, + {"error": "this device has no second session to switch to"}, status_code=404, ) # Starting the target stops the other: the units Conflict. Doing it as two # jobs (stop-then-start) would leave a window with NO session on screen.