Files

181 lines
6.3 KiB
Python
Raw Permalink Normal View History

from datetime import UTC, datetime, timedelta
import pytest
from PIL import Image
from pigway_frame_flow.errors import ErrorCode as E
from pigway_frame_flow.errors import FlowError
from pigway_frame_flow.models import JobInput, Schedule
from pigway_frame_flow.providers.photo_sources import PhotoSourceInput
from pigway_frame_flow.storage.database import encode
def upload_random(client, service, settings):
root = settings.data_dir.parent / "photos"
root.mkdir()
settings.photo_root = root
Image.new("RGB", (300, 200)).save(root / "a.jpg")
service.photo_sources.save(
"family", PhotoSourceInput(name="Family", kind="local", location=".")
)
r = client.post(
"/api/v1/display",
json={
"device_id": "living-room",
"content": {"type": "photo_source", "source_id": "family"},
"options": {"cleanup_unstarred": True},
},
)
assert r.status_code == 202
service.process_one()
op = client.get("/api/v1/operations/" + r.json()["operation_id"]).json()
return op["result"]["image_id"]
def due(service, image_id):
service.db.execute(
"UPDATE image_retention SET expires_at=? WHERE image_id=?",
((datetime.now(UTC) - timedelta(days=1)).isoformat(), image_id),
)
# Complete the upload before simulating expiry days later and switching away.
service.sync()
# The test driver switches to a different image, not a real device.
driver = service.registry.get("living-room")
driver.display(1)
service.db.execute(
"UPDATE device_state SET previous_image_id=NULL,base_image_id=NULL WHERE device_id=?",
("living-room",),
)
def test_register_new_random_upload_with_seven_day_expiry(client, service, settings):
image_id = upload_random(client, service, settings)
row = service.db.one("SELECT * FROM image_retention WHERE image_id=?", (image_id,))
delta = datetime.fromisoformat(row["expires_at"]) - datetime.now(UTC)
assert 6.99 < delta.total_seconds() / 86400 <= 7
assert row["upload_operation"] and not row["starred"]
def test_star_api_and_gallery_persist(client, service, settings):
image_id = upload_random(client, service, settings)
r = client.put(
f"/api/v1/devices/living-room/images/{image_id}/star", json={"starred": True}
)
assert r.status_code == 200 and r.json()["starred"]
items = client.get("/api/v1/devices/living-room/images").json()
assert next(x for x in items if x["image_id"] == image_id)["starred"]
due(service, image_id)
service.retention.sweep()
assert (
service.db.one(
"SELECT cleanup_operation FROM image_retention WHERE image_id=?",
(image_id,),
)["cleanup_operation"]
is None
)
def test_due_cleanup_deletes_only_owned_upload(client, service, settings):
image_id = upload_random(client, service, settings)
due(service, image_id)
service.retention.sweep()
row = service.db.one("SELECT * FROM image_retention WHERE image_id=?", (image_id,))
assert row["cleanup_operation"]
service.process_one()
assert (
service.db.one("SELECT * FROM image_retention WHERE image_id=?", (image_id,))
is None
)
assert 1 in {x.image_id for x in service.registry.get("living-room").images()}
def test_current_and_restore_and_playlist_protected(client, service, settings):
image_id = upload_random(client, service, settings)
service.db.execute(
"UPDATE image_retention SET expires_at=? WHERE image_id=?",
("2000-01-01T00:00:00+00:00", image_id),
)
service.retention.sweep()
assert not service.db.one(
"SELECT cleanup_operation FROM image_retention WHERE image_id=?", (image_id,)
)["cleanup_operation"]
due(service, image_id)
service.db.execute("UPDATE device_state SET previous_image_id=?", (image_id,))
assert not service.retention.eligible("living-room", image_id)
service.db.execute("UPDATE device_state SET previous_image_id=NULL")
service.db.execute(
"INSERT INTO playlists VALUES(?,?,0,NULL)",
("p", encode({"device_id": "living-room", "image_ids": [image_id]})),
)
assert not service.retention.eligible("living-room", image_id)
def test_star_between_queue_and_execution_cancels_delete(client, service, settings):
image_id = upload_random(client, service, settings)
due(service, image_id)
service.retention.sweep()
assert (
client.put(
f"/api/v1/devices/living-room/images/{image_id}/star",
json={"starred": True},
).status_code
== 200
)
service.process_one()
assert image_id in {
x.image_id for x in service.registry.get("living-room").images()
}
def test_unknown_delete_is_not_retried(client, service, settings, monkeypatch):
image_id = upload_random(client, service, settings)
due(service, image_id)
calls = []
def unknown(id):
calls.append(id)
raise FlowError(E.WRITE_RESULT_UNKNOWN, "Unknown delete result")
monkeypatch.setattr(service.registry.get("living-room"), "delete", unknown)
service.retention.sweep()
service.process_one()
service.retention.checked = 0
service.retention.sweep()
assert calls == [image_id]
assert service.blocked("living-room")
def test_cleanup_option_requires_random_photo_job():
with pytest.raises(ValueError):
JobInput(
name="x",
schedule=Schedule(type="daily", time="07:00"),
source={"type": "template", "template": "quote"},
cleanup_unstarred=True,
)
def test_first_cleanup_runs_after_recent_host_boot(
client, service, settings, monkeypatch
):
image_id = upload_random(client, service, settings)
due(service, image_id)
monkeypatch.setattr(
"pigway_frame_flow.display.retention.time.monotonic", lambda: 120
)
service.retention.sweep()
row = service.db.one("SELECT * FROM image_retention WHERE image_id=?", (image_id,))
assert row["cleanup_operation"]
assert service.retention.checked == 120
service.retention.sweep()
assert (
service.db.one("SELECT COUNT(*) AS n FROM operations WHERE action='delete'")[
"n"
]
== 1
)
# Legacy algorithms remain covered; production filesystem access is tested separately.
pytestmark = pytest.mark.usefixtures("legacy_photo_engine")