| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- """The {finish_photo_url} link opens with authentication on too.
- The archive photo route needs a media token when authentication is on, and
- nothing tapping a link in Telegram, CallMeBot or a Home Assistant notification
- has one, so the link answered 401. With authentication on, the link now points
- at a notification photo instead: an unguessable name that opens that one photo
- for 3 days. With authentication off it stays the archive link, which needs no
- login and doesn't expire.
- """
- from types import SimpleNamespace
- from unittest.mock import AsyncMock, patch
- import pytest
- from backend.app.utils import notification_photos
- ARCHIVE_ID = 42
- FILENAME = "finish_20260101_120000_abcd1234.jpg"
- @pytest.fixture
- def photo(tmp_path, monkeypatch):
- """A finish photo on disk, and the notification photo store under tmp_path."""
- monkeypatch.setattr(notification_photos.settings, "base_dir", tmp_path)
- path = tmp_path / FILENAME
- path.write_bytes(b"\xff\xd8finish-photo")
- return path
- async def _call(*, auth_on, external_url="https://bambuddy.example", photo_path=None, auth_error=False):
- from backend.app.main import _finish_photo_for_notification
- auth = AsyncMock(side_effect=RuntimeError("db down")) if auth_error else AsyncMock(return_value=auth_on)
- with (
- patch("backend.app.api.routes.settings.get_setting", AsyncMock(return_value=external_url)),
- patch("backend.app.core.auth.is_auth_enabled", auth),
- patch("backend.app.utils.archive_paths.find_archive_photo", return_value=photo_path),
- ):
- return await _finish_photo_for_notification(AsyncMock(), SimpleNamespace(), ARCHIVE_ID, FILENAME)
- class TestFinishPhotoLink:
- @pytest.mark.asyncio
- async def test_auth_off_keeps_the_archive_link(self, photo):
- url, data = await _call(auth_on=False, photo_path=photo)
- assert url == f"https://bambuddy.example/api/v1/archives/{ARCHIVE_ID}/photos/{FILENAME}"
- assert data == photo.read_bytes()
- @pytest.mark.asyncio
- async def test_auth_off_without_external_url_is_relative(self, photo):
- url, _ = await _call(auth_on=False, external_url=None, photo_path=photo)
- assert url == f"/api/v1/archives/{ARCHIVE_ID}/photos/{FILENAME}"
- @pytest.mark.asyncio
- async def test_auth_on_links_a_notification_photo_that_serves_the_same_bytes(self, photo):
- url, data = await _call(auth_on=True, photo_path=photo)
- prefix = "https://bambuddy.example/api/v1/notifications/photos/"
- assert url.startswith(prefix)
- served = notification_photos.find_notification_photo(url[len(prefix) :])
- assert served is not None
- assert served.read_bytes() == photo.read_bytes()
- assert data == photo.read_bytes()
- @pytest.mark.asyncio
- async def test_auth_check_failing_is_treated_as_auth_on(self, photo):
- url, _ = await _call(auth_on=False, auth_error=True, photo_path=photo)
- assert "/api/v1/notifications/photos/" in url
- @pytest.mark.asyncio
- async def test_auth_on_without_the_photo_gives_no_link(self, photo):
- # A link to the archive route would only answer 401, so none at all.
- assert await _call(auth_on=True, photo_path=None) == (None, None)
- @pytest.mark.asyncio
- async def test_oversized_photo_is_linked_but_not_attached(self, photo):
- photo.write_bytes(b"\xff" * 2_500_001)
- url, data = await _call(auth_on=True, photo_path=photo)
- assert url is not None
- assert data is None
|