64 tests covering: auth/setup and the CSRF gate, the "owner adds their own data, anyone linked can mute it" permission pattern shared across calendar-select/tasks-source/whiteboard-source, migration correctness (fresh install, idempotent re-run, expected columns), battery estimate outlier rejection, calendar_feed's fetch/merge/partial-failure handling, webdav_client's fetch/list-directory, the whiteboard force-refresh throttle bypass and browse endpoint, and render-size invariants across calendar views/orientations. No DB/HTTP fixtures need Docker, Node, or a real Immich/CalDAV/WebDAV server -- a fresh temp SQLite file plus a couple of small local HTTP servers as test doubles cover it all. Table data is wiped and reseeded between tests rather than relying on SQLAlchemy's transaction-rollback isolation pattern, which needs a pysqlite event-listener workaround app/db.py's engine doesn't have and has no reason to gain just for tests. Wired into .gitea/workflows/server-docker-build.yml as its own job that build-and-push now depends on, so a failing suite blocks the image push rather than just running alongside it for show.
68 lines
2.9 KiB
Python
68 lines
2.9 KiB
Python
"""Every renderer that produces a device-facing frame must return
|
|
exactly EPD_WIDTH*EPD_HEIGHT/2 bytes (the panel's packed 2px/byte
|
|
format) -- firmware writes this straight to the display with no length
|
|
checking of its own, so a renderer that's off by even one byte is a
|
|
silent on-device corruption bug, not a clean error. This is a cheap,
|
|
high-value regression guard: pure PIL rendering, no DB/HTTP/Node."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.calendar_render import CALENDAR_VIEWS, render_calendar
|
|
from app.image_pipeline import EPD_HEIGHT, EPD_WIDTH, render_placeholder
|
|
|
|
EXPECTED_BYTES = EPD_WIDTH * EPD_HEIGHT // 2
|
|
ORIENTATIONS = ["landscape", "landscape_flipped", "portrait", "portrait_flipped"]
|
|
|
|
_SAMPLE_EVENTS = [
|
|
{
|
|
"summary": "Dentist", "start": "2026-08-01T14:00:00+00:00", "end": "2026-08-01T15:00:00+00:00",
|
|
"all_day": False, "sources": [{"owner_display_name": "Alice", "color_index": None}],
|
|
},
|
|
{
|
|
"summary": "Team Offsite", "start": "2026-08-03T00:00:00", "end": "2026-08-04T00:00:00",
|
|
"all_day": True, "sources": [{"owner_display_name": "Bob", "color_index": 2}],
|
|
},
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("orientation", ORIENTATIONS)
|
|
def test_placeholder_render_size(orientation):
|
|
data = render_placeholder(["Not configured yet"], orientation=orientation)
|
|
assert len(data) == EXPECTED_BYTES
|
|
|
|
|
|
@pytest.mark.parametrize("view", CALENDAR_VIEWS)
|
|
def test_calendar_render_size_across_views(view):
|
|
data = render_calendar(_SAMPLE_EVENTS, view, browse_offset=0, orientation="landscape",
|
|
palette_rgb=None, timezone="UTC")
|
|
assert len(data) == EXPECTED_BYTES
|
|
|
|
|
|
@pytest.mark.parametrize("orientation", ORIENTATIONS)
|
|
def test_calendar_render_size_across_orientations(orientation):
|
|
data = render_calendar(_SAMPLE_EVENTS, "agenda", browse_offset=0, orientation=orientation,
|
|
palette_rgb=None, timezone="UTC")
|
|
assert len(data) == EXPECTED_BYTES
|
|
|
|
|
|
def test_calendar_render_size_empty_events():
|
|
data = render_calendar([], "week", browse_offset=0, orientation="landscape",
|
|
palette_rgb=None, timezone="UTC")
|
|
assert len(data) == EXPECTED_BYTES
|
|
|
|
|
|
def test_calendar_render_size_with_fetch_summary_and_tasks():
|
|
tasks = [{"summary": "Buy milk", "completed": False}, {"summary": "Walk the dog", "completed": True}]
|
|
data = render_calendar(_SAMPLE_EVENTS, "week", browse_offset=0, orientation="landscape",
|
|
palette_rgb=None, timezone="UTC", fetch_summary="1 of 2 calendars unavailable",
|
|
week_days=5, week_layout="vertical", tasks=tasks)
|
|
assert len(data) == EXPECTED_BYTES
|
|
|
|
|
|
def test_calendar_render_size_with_week_start_offset():
|
|
data = render_calendar(_SAMPLE_EVENTS, "week", browse_offset=0, orientation="landscape",
|
|
palette_rgb=None, timezone="UTC", week_days=3, week_start_offset=2)
|
|
assert len(data) == EXPECTED_BYTES
|