150 lines
6.8 KiB
Python
150 lines
6.8 KiB
Python
"""routers/api_widgets.py's POST .../border endpoint (models.Widget.
|
|
border_style/border_thickness/border_color_index) -- a Widget-level
|
|
property, not a per-type config field, so this is exercised independently
|
|
of any specific widget_type. End-to-end rendering (does a saved border
|
|
actually show up in the composited panel) is covered separately in
|
|
test_device_widget_dispatch.py-style tests below via the frame preview
|
|
endpoint, the same real render_panel path /frame/image uses."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
|
|
from PIL import Image
|
|
|
|
from app.models import Frame, Widget
|
|
|
|
from .conftest import csrf_headers, link_user, login, make_user
|
|
|
|
|
|
def _widget_id(db_session, widget_type="photos") -> int:
|
|
return db_session.query(Widget).filter_by(frame_id=1, widget_type=widget_type).one().id
|
|
|
|
|
|
def test_new_widget_defaults_to_no_border(db_session):
|
|
widget = db_session.query(Widget).filter_by(frame_id=1).one()
|
|
assert widget.border_style == "none"
|
|
assert widget.border_thickness == 3
|
|
assert widget.border_color_index == 0
|
|
|
|
|
|
def test_set_border_persists(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
widget_id = _widget_id(db_session)
|
|
resp = client.post(f"/api/frames/1/widgets/{widget_id}/border",
|
|
json={"border_style": "dashed", "border_thickness": 5, "border_color_index": 3},
|
|
headers=csrf_headers(client))
|
|
assert resp.status_code == 200, resp.text
|
|
assert resp.json() == {
|
|
"id": widget_id, "widget_type": "photos", "x": 0, "y": 0, "w": 8, "h": 5, "sort_order": 0,
|
|
"border_style": "dashed", "border_thickness": 5, "border_color_index": 3, "locked": False,
|
|
}
|
|
widget = db_session.get(Widget, widget_id)
|
|
assert (widget.border_style, widget.border_thickness, widget.border_color_index) == ("dashed", 5, 3)
|
|
|
|
|
|
def test_set_border_thickness_clamps_to_range(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
widget_id = _widget_id(db_session)
|
|
resp = client.post(f"/api/frames/1/widgets/{widget_id}/border",
|
|
json={"border_style": "solid", "border_thickness": 999, "border_color_index": 0},
|
|
headers=csrf_headers(client))
|
|
assert resp.status_code == 200, resp.text
|
|
assert resp.json()["border_thickness"] == 8 # image_pipeline.MAX_BORDER_THICKNESS
|
|
|
|
resp = client.post(f"/api/frames/1/widgets/{widget_id}/border",
|
|
json={"border_style": "solid", "border_thickness": -5, "border_color_index": 0},
|
|
headers=csrf_headers(client))
|
|
assert resp.status_code == 200, resp.text
|
|
assert resp.json()["border_thickness"] == 1 # image_pipeline.MIN_BORDER_THICKNESS
|
|
|
|
|
|
def test_set_border_rejects_unknown_style(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
widget_id = _widget_id(db_session)
|
|
resp = client.post(f"/api/frames/1/widgets/{widget_id}/border",
|
|
json={"border_style": "sparkly", "border_thickness": 3, "border_color_index": 0},
|
|
headers=csrf_headers(client))
|
|
assert resp.status_code == 400
|
|
assert "border_style" in resp.json()["detail"]
|
|
|
|
|
|
def test_set_border_rejects_out_of_range_color_index(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
widget_id = _widget_id(db_session)
|
|
resp = client.post(f"/api/frames/1/widgets/{widget_id}/border",
|
|
json={"border_style": "solid", "border_thickness": 3, "border_color_index": 6},
|
|
headers=csrf_headers(client))
|
|
assert resp.status_code == 400
|
|
assert "border_color_index" in resp.json()["detail"]
|
|
|
|
|
|
def test_set_border_404s_for_unknown_widget(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
resp = client.post("/api/frames/1/widgets/999999/border",
|
|
json={"border_style": "solid", "border_thickness": 3, "border_color_index": 0},
|
|
headers=csrf_headers(client))
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_set_border_unrelated_user_404s(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
make_user(db_session, "mallory")
|
|
widget_id = _widget_id(db_session)
|
|
|
|
client.cookies.clear()
|
|
login(client, "mallory")
|
|
resp = client.post(f"/api/frames/1/widgets/{widget_id}/border",
|
|
json={"border_style": "solid", "border_thickness": 3, "border_color_index": 0},
|
|
headers=csrf_headers(client))
|
|
assert resp.status_code == 404
|
|
assert db_session.get(Widget, widget_id).border_style == "none"
|
|
|
|
|
|
def test_set_border_linked_but_not_controlling_user_409s(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)
|
|
widget_id = _widget_id(db_session)
|
|
|
|
client.cookies.clear()
|
|
login(client, "bob")
|
|
resp = client.post(f"/api/frames/1/widgets/{widget_id}/border",
|
|
json={"border_style": "solid", "border_thickness": 3, "border_color_index": 0},
|
|
headers=csrf_headers(client))
|
|
assert resp.status_code == 409
|
|
assert resp.json()["detail"]["error"] == "not_controller"
|
|
|
|
|
|
# --- actually renders (real render_panel path, same as /frame/image) ------
|
|
|
|
def _preview_pixels(client) -> Image.Image:
|
|
resp = client.get("/api/frames/1/preview")
|
|
assert resp.status_code == 200, resp.text
|
|
return Image.open(io.BytesIO(resp.content)).convert("RGB")
|
|
|
|
|
|
def test_solid_border_draws_the_configured_palette_color(client, db_session):
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
widget_id = _widget_id(db_session)
|
|
# color_index 3 == Red, see image_pipeline.PALETTE_LABELS/DEFAULT_PALETTE_RGB
|
|
resp = client.post(f"/api/frames/1/widgets/{widget_id}/border",
|
|
json={"border_style": "solid", "border_thickness": 6, "border_color_index": 3},
|
|
headers=csrf_headers(client))
|
|
assert resp.status_code == 200, resp.text
|
|
|
|
img = _preview_pixels(client)
|
|
assert img.getpixel((0, 0)) == (207, 0, 15) # DEFAULT_PALETTE_RGB[3], top-left corner of the stroke
|
|
|
|
|
|
def test_no_border_leaves_the_edge_unmarked(client, db_session):
|
|
"""The default "none" style is a true no-op -- this is the negative
|
|
case for test_solid_border_draws_the_configured_palette_color, using
|
|
the exact same photo-widget fixture minus the border, so a red edge
|
|
pixel there can only be the border, not something else in the
|
|
render path already painting it that color."""
|
|
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
|
img = _preview_pixels(client)
|
|
assert img.getpixel((0, 0)) != (207, 0, 15)
|