diff --git a/server/app/routers/api_layouts.py b/server/app/routers/api_layouts.py index 048b53b..b594a18 100644 --- a/server/app/routers/api_layouts.py +++ b/server/app/routers/api_layouts.py @@ -63,6 +63,10 @@ LAYOUT_CONFIG_FIELDS: dict[str, tuple[str, ...]] = { "text": ("content", "font_size", "font_family", "align", "background_color"), "whiteboard": ("user_id", "url"), "battery": ("mode",), + "weather": ( + "mode", "provider", "units", "city_label", "city_latitude", "city_longitude", + "hourly_interval_hours", "daily_days", "cities", + ), } # widget_type -> (FrameCalendar|FrameTaskList model, SavedLayoutSource.kind) diff --git a/server/tests/test_saved_layouts.py b/server/tests/test_saved_layouts.py index 75444f8..2bb40d8 100644 --- a/server/tests/test_saved_layouts.py +++ b/server/tests/test_saved_layouts.py @@ -27,6 +27,7 @@ from app.models import ( TaskWidgetConfig, User, Widget, + WeatherWidgetConfig, ) from .conftest import csrf_headers, link_user, login, make_user @@ -56,6 +57,16 @@ def _add_tasks_widget(db_session, frame_id=1, x=3, y=0, w=2, h=2, sort_order=2) return widget +def _add_weather_widget(db_session, frame_id=1, x=0, y=0, w=2, h=2, sort_order=1) -> Widget: + widget = Widget(frame_id=frame_id, widget_type="weather", x=x, y=y, w=w, h=h, + sort_order=sort_order, created_at=time.time()) + db_session.add(widget) + db_session.flush() + db_session.add(WeatherWidgetConfig(widget_id=widget.id)) + db_session.commit() + return widget + + def _setup_alice(client) -> None: resp = client.post("/setup", data={"username": "alice", "password": "hunter22"}) assert resp.status_code == 303, resp.text @@ -89,6 +100,48 @@ def test_save_captures_placement_and_photo_settings_but_not_queue_state(client, "queue_target_len": 30} +def test_save_and_apply_round_trip_weather_settings(client, db_session): + _setup_alice(client) + weather = _add_weather_widget(db_session) + with db_session.no_autoflush: + wcfg = db_session.get(WeatherWidgetConfig, weather.id) + wcfg.mode = "daily" + wcfg.provider = "nws" + wcfg.units = "celsius" + wcfg.city_label = "Boston, MA" + wcfg.city_latitude = 42.36 + wcfg.city_longitude = -71.06 + wcfg.hourly_interval_hours = 6 + wcfg.daily_days = 7 + wcfg.checked_at = 12345.0 + wcfg.cached = {"stale": "runtime state, not a setting"} + db_session.commit() + db_session.expunge(wcfg) + + save_resp = client.post("/api/frames/1/layouts", json={"name": "Weather Layout"}, headers=csrf_headers(client)) + assert save_resp.status_code == 200, save_resp.text + layout = db_session.query(SavedLayout).filter_by(user_id=1, name="Weather Layout").one() + snap = db_session.query(SavedLayoutWidget).filter_by(saved_layout_id=layout.id, widget_type="weather").one() + assert snap.config == { + "mode": "daily", "provider": "nws", "units": "celsius", "city_label": "Boston, MA", + "city_latitude": 42.36, "city_longitude": -71.06, "hourly_interval_hours": 6, "daily_days": 7, + "cities": None, + } + + client.delete("/api/frames/1/widgets", headers=csrf_headers(client)) + apply_resp = client.post(f"/api/frames/1/layouts/{layout.id}/apply", headers=csrf_headers(client)) + assert apply_resp.status_code == 200, apply_resp.text + + new_widget = db_session.query(Widget).filter_by(frame_id=1, widget_type="weather").one() + new_cfg = db_session.get(WeatherWidgetConfig, new_widget.id) + assert (new_cfg.mode, new_cfg.provider, new_cfg.units) == ("daily", "nws", "celsius") + assert (new_cfg.city_label, new_cfg.city_latitude, new_cfg.city_longitude) == ("Boston, MA", 42.36, -71.06) + assert (new_cfg.hourly_interval_hours, new_cfg.daily_days) == (6, 7) + # Runtime fetch-cache state is never captured/restored by a saved layout. + assert new_cfg.checked_at == 0.0 + assert new_cfg.cached is None + + def test_save_captures_calendar_sources_and_button_actions(client, db_session): _setup_alice(client) db_session.query(Widget).filter_by(frame_id=1, widget_type="photos").delete()