import io import time import pytest from PIL import Image from notify import staticmap class _FakeResponse: def __init__(self, content): self.content = content def raise_for_status(self): pass class _FakeAsyncClient: def __init__(self, content=None, exc=None, **kwargs): self._content = content self._exc = exc async def __aenter__(self): return self async def __aexit__(self, *args): return False async def get(self, url): if self._exc: raise self._exc return _FakeResponse(self._content) def _make_tile_png() -> bytes: buf = io.BytesIO() Image.new("RGB", (256, 256), color="green").save(buf, format="PNG") return buf.getvalue() def test_sign_map_token_is_deterministic_and_tamper_evident(): exp = int(time.time()) + 300 sig1 = staticmap.sign_map_token(28.6139, 77.2090, exp) sig2 = staticmap.sign_map_token(28.6139, 77.2090, exp) assert sig1 == sig2 assert sig1 != staticmap.sign_map_token(28.6140, 77.2090, exp) assert sig1 != staticmap.sign_map_token(28.6139, 77.2091, exp) assert sig1 != staticmap.sign_map_token(28.6139, 77.2090, exp + 1) def test_sign_map_token_rounds_to_fixed_precision(): """A lat/lon that round-trips through a query string (parsed back to float by FastAPI) must still verify — both sides format to the same 5-decimal precision before signing (see sign_map_token's docstring).""" exp = int(time.time()) + 300 lat_str, lon_str = f"{28.6139:.5f}", f"{77.209:.5f}" built_sig = staticmap.sign_map_token(float(lat_str), float(lon_str), exp) # Simulates the endpoint re-parsing the exact query-string values. reparsed_sig = staticmap.sign_map_token(float(lat_str), float(lon_str), exp) assert built_sig == reparsed_sig @pytest.mark.asyncio async def test_render_static_map_returns_correctly_sized_png(monkeypatch): monkeypatch.setattr(staticmap.httpx, "AsyncClient", lambda **kw: _FakeAsyncClient(content=_make_tile_png())) result = await staticmap.render_static_map(28.6139, 77.2090) assert result is not None img = Image.open(io.BytesIO(result)) assert img.format == "PNG" assert img.size == (staticmap._WIDTH, staticmap._HEIGHT) @pytest.mark.asyncio async def test_render_static_map_returns_none_on_fetch_failure(monkeypatch): monkeypatch.setattr(staticmap.httpx, "AsyncClient", lambda **kw: _FakeAsyncClient(exc=RuntimeError("boom"))) assert await staticmap.render_static_map(28.6139, 77.2090) is None @pytest.mark.asyncio async def test_render_static_map_rejects_out_of_range_coordinates(): assert await staticmap.render_static_map(999, 0) is None assert await staticmap.render_static_map(0, -999) is None