"""routers/manage.py -- the no-login "scan to manage" surface -- against the widget-scoped photo state it was just repointed at. No prior test coverage existed for this router at all before this file; it's exercised here specifically because of how much its photo-queue endpoints changed in the widget-system cutover (frame.current_asset_id/ frame.queue -> the frame's photo widget's own PhotoWidgetConfig).""" from __future__ import annotations from app.models import Frame, PhotoWidgetConfig _ASSETS = [{"id": "asset-1"}, {"id": "asset-2"}, {"id": "asset-3"}] def _mock_immich(monkeypatch): """manage.py imports immich_client_for/list_assets into its own module namespace (`from .common import ...`) -- patching those names, not app.widgets.photos' separate copies, since manage.py's endpoints call its own bound names directly, never through app/widgets/.""" monkeypatch.setattr("app.routers.manage.immich_client_for", lambda frame: object()) monkeypatch.setattr("app.routers.manage.list_assets", lambda client, album_id: _ASSETS) def _configure_photo_widget(db_session): from app.models import Widget frame = db_session.get(Frame, 1) # photo_widget_config_or_404 (see routers/common.py) checks Immich # creds directly, not through the mocked immich_client_for below. frame.immich_url = "http://immich.example.com" frame.immich_api_key = "key" widget = db_session.query(Widget).filter_by(frame_id=frame.id, widget_type="photos").one() cfg = db_session.get(PhotoWidgetConfig, widget.id) cfg.album_id = "album-1" db_session.commit() return frame, widget def test_manage_queue_requires_configured_photo_widget(client, db_session): client.post("/setup", data={"username": "alice", "password": "hunter22"}) frame = db_session.get(Frame, 1) resp = client.get(f"/api/m/{frame.manage_token}/queue") assert resp.status_code == 400 def test_manage_queue_returns_current_and_upcoming(client, db_session, monkeypatch): client.post("/setup", data={"username": "alice", "password": "hunter22"}) frame, widget = _configure_photo_widget(db_session) _mock_immich(monkeypatch) resp = client.get(f"/api/m/{frame.manage_token}/queue") assert resp.status_code == 200, resp.text data = resp.json() assert data["current"]["id"] == "asset-1" assert [u["id"] for u in data["upcoming"]] == ["asset-2", "asset-3"] def test_manage_advance_and_back(client, db_session, monkeypatch): client.post("/setup", data={"username": "alice", "password": "hunter22"}) frame, widget = _configure_photo_widget(db_session) _mock_immich(monkeypatch) # establish a current photo first client.get(f"/api/m/{frame.manage_token}/queue") resp = client.post(f"/api/m/{frame.manage_token}/advance") assert resp.status_code == 200 cfg = db_session.get(PhotoWidgetConfig, widget.id) first_current = cfg.current_asset_id assert first_current != "" resp = client.post(f"/api/m/{frame.manage_token}/advance") assert resp.status_code == 200 cfg = db_session.get(PhotoWidgetConfig, widget.id) assert cfg.current_asset_id != first_current resp = client.post(f"/api/m/{frame.manage_token}/back") assert resp.status_code == 200 cfg = db_session.get(PhotoWidgetConfig, widget.id) assert cfg.current_asset_id == first_current def test_manage_promote(client, db_session, monkeypatch): client.post("/setup", data={"username": "alice", "password": "hunter22"}) frame, widget = _configure_photo_widget(db_session) _mock_immich(monkeypatch) client.get(f"/api/m/{frame.manage_token}/queue") # populate the queue cfg = db_session.get(PhotoWidgetConfig, widget.id) assert "asset-3" in cfg.queue resp = client.post(f"/api/m/{frame.manage_token}/promote", json={"asset_id": "asset-3"}) assert resp.status_code == 200, resp.text cfg = db_session.get(PhotoWidgetConfig, widget.id) assert cfg.queue[0] == "asset-3" def test_manage_thumbnail_scoped_to_showing_or_queued(client, db_session, monkeypatch): client.post("/setup", data={"username": "alice", "password": "hunter22"}) frame, widget = _configure_photo_widget(db_session) _mock_immich(monkeypatch) client.get(f"/api/m/{frame.manage_token}/queue") resp = client.get(f"/api/m/{frame.manage_token}/thumbnail/not-on-this-frame") assert resp.status_code == 404 monkeypatch.setattr( "app.routers.manage.immich_client_for", lambda frame: type("C", (), { "download_asset_thumbnail": lambda self, asset_id: (b"jpegbytes", "image/jpeg"), })(), ) resp = client.get(f"/api/m/{frame.manage_token}/thumbnail/asset-1") assert resp.status_code == 200 assert resp.content == b"jpegbytes" def test_unknown_manage_token_404s(client, db_session): resp = client.get("/api/m/not-a-real-token/queue") assert resp.status_code == 404