The server now records exactly what was last sent to the device on
every device-facing render (/frame/image, /frame/advance, /frame/back,
and the global hold actions), persisted as Frame.last_displayed_image/
_at and served back via GET /api/frames/{id}/now-displaying. The
header thumbnail is split into that frozen "now displaying" snapshot
and the existing live "up next" re-render, with an arrow between them
-- so editing a layout shows the change immediately on the right while
the left stays exactly what's actually on the panel until the device's
next real wake.
95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
"""GET /api/frames/{id}/now-displaying -- the frozen half of the header
|
|
preview pair (see routers/device.py's _record_last_displayed). Distinct
|
|
from /preview (test_frame_preview.py): that one always live-renders,
|
|
this one serves back exactly whatever bytes a device-facing endpoint
|
|
last actually sent, recorded as a side effect of /frame/image,
|
|
/frame/advance, and /frame/back."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
|
|
from PIL import Image
|
|
|
|
from app.image_pipeline import logical_render_size
|
|
from app.models import Frame
|
|
|
|
from .conftest import link_user, login, make_user
|
|
|
|
|
|
def test_now_displaying_404s_before_any_device_fetch(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
|
|
resp = client.get("/api/frames/1/now-displaying")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_frame_image_records_now_displaying(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
frame = db_session.get(Frame, 1)
|
|
|
|
resp = client.get("/frame/image")
|
|
assert resp.status_code == 200
|
|
|
|
resp = client.get("/api/frames/1/now-displaying")
|
|
assert resp.status_code == 200
|
|
assert resp.headers["content-type"] == "image/png"
|
|
assert "X-Displayed-At" in resp.headers
|
|
assert float(resp.headers["X-Displayed-At"]) > 0
|
|
|
|
img = Image.open(io.BytesIO(resp.content))
|
|
assert img.size == logical_render_size(frame.orientation)
|
|
|
|
|
|
def test_advance_and_back_also_update_now_displaying(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
db_session.get(Frame, 1)
|
|
|
|
client.get("/frame/image")
|
|
first = client.get("/api/frames/1/now-displaying")
|
|
assert first.status_code == 200
|
|
|
|
resp = client.post("/frame/advance")
|
|
assert resp.status_code == 200
|
|
after_advance = client.get("/api/frames/1/now-displaying")
|
|
assert after_advance.status_code == 200
|
|
|
|
resp = client.post("/frame/back")
|
|
assert resp.status_code == 200
|
|
after_back = client.get("/api/frames/1/now-displaying")
|
|
assert after_back.status_code == 200
|
|
assert float(after_back.headers["X-Displayed-At"]) >= float(first.headers["X-Displayed-At"])
|
|
|
|
|
|
def test_now_displaying_visible_to_linked_user(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
bob = make_user(db_session, "bob")
|
|
frame = db_session.get(Frame, 1)
|
|
link_user(db_session, bob, frame)
|
|
client.get("/frame/image")
|
|
|
|
client.cookies.clear()
|
|
login(client, "bob")
|
|
resp = client.get("/api/frames/1/now-displaying")
|
|
assert resp.status_code == 200
|
|
|
|
|
|
def test_now_displaying_hidden_from_unrelated_user(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
make_user(db_session, "mallory")
|
|
client.get("/frame/image")
|
|
|
|
client.cookies.clear()
|
|
login(client, "mallory")
|
|
resp = client.get("/api/frames/1/now-displaying")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_now_displaying_requires_login(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
client.get("/frame/image")
|
|
|
|
client.cookies.clear()
|
|
resp = client.get("/api/frames/1/now-displaying")
|
|
assert resp.status_code in (401, 403)
|