"""The optional R2 LiDAR tile cache (A3) + its L1/L2/origin fall-through in _stream_download. All fakes — no boto3, no network (the autouse guard would fail a real socket). The point is that the cache is a transparent no-op when R2 env is unset (byte-identical to before) and a correct L2 layer when it's set. """ from __future__ import annotations from lawn_estimator.sources import lidar from lawn_estimator.sources.tile_cache import TileCache _R2_ENV = ["R2_BUCKET", "R2_ENDPOINT", "R2_ACCOUNT_ID", "R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY"] def _clear_r2(monkeypatch): for k in _R2_ENV: monkeypatch.delenv(k, raising=False) # --- env gating ------------------------------------------------------------ def test_cache_disabled_without_env(monkeypatch): _clear_r2(monkeypatch) assert TileCache().enabled is False def test_cache_enabled_and_derives_endpoint_from_account(monkeypatch): _clear_r2(monkeypatch) monkeypatch.setenv("R2_BUCKET", "tiles") monkeypatch.setenv("R2_ACCESS_KEY_ID", "key") monkeypatch.setenv("R2_SECRET_ACCESS_KEY", "secret") monkeypatch.setenv("R2_ACCOUNT_ID", "acct123") tc = TileCache() assert tc.enabled is True assert tc._endpoint == "https://acct123.r2.cloudflarestorage.com" assert tc._obj_key("douglas/x.las") == "lidar/douglas/x.las" # default prefix def test_disabled_cache_methods_are_noops_and_never_touch_boto3(monkeypatch, tmp_path): # A disabled cache must not import boto3 or write anything — fetch is a miss, store a no-op. _clear_r2(monkeypatch) tc = TileCache() dest = tmp_path / "x.las" assert tc.fetch("douglas/x.las", dest) is False assert not dest.exists() tc.store("douglas/x.las", tmp_path) # no source needed; returns without error # --- L1 / L2 / origin fall-through in _stream_download ---------------------- class _FakeResp: def __init__(self, data: bytes): self._data = data self.headers = {"content-length": str(len(data))} def __enter__(self): return self def __exit__(self, *_): return False def raise_for_status(self): pass def iter_content(self, chunk_size=1): for i in range(0, len(self._data), chunk_size): yield self._data[i : i + chunk_size] class _FakeSession: def __init__(self, data: bytes = b"ORIGIN-TILE-BYTES"): self.data = data self.calls = 0 def get(self, url, stream=False, timeout=None): self.calls += 1 return _FakeResp(self.data) class _FakeCache: def __init__(self, hit: bool = False): self.hit = hit self.fetched: list[str] = [] self.stored: list[str] = [] def fetch(self, key, dest): self.fetched.append(key) if self.hit: dest.write_bytes(b"FROM-R2") return True return False def store(self, key, src): self.stored.append(key) def test_l1_hit_uses_local_disk_and_skips_cache_and_origin(monkeypatch, tmp_path): fake = _FakeCache(hit=True) monkeypatch.setattr(lidar, "TILE_CACHE", fake) session = _FakeSession() dest = tmp_path / "t.las" dest.write_bytes(b"ALREADY-LOCAL") out = lidar._stream_download(session, "http://x/t.las", dest, "test", cache_key="douglas/t.las") assert out == dest and dest.read_bytes() == b"ALREADY-LOCAL" assert fake.fetched == [] and session.calls == 0 # neither R2 nor origin touched def test_l2_hit_skips_origin_download(monkeypatch, tmp_path): fake = _FakeCache(hit=True) monkeypatch.setattr(lidar, "TILE_CACHE", fake) session = _FakeSession() dest = tmp_path / "t.las" out = lidar._stream_download(session, "http://x/t.las", dest, "test", cache_key="douglas/t.las") assert out == dest and dest.read_bytes() == b"FROM-R2" assert fake.fetched == ["douglas/t.las"] assert session.calls == 0 # origin never hit — the whole point of A3 def test_origin_download_populates_the_cache(monkeypatch, tmp_path): fake = _FakeCache(hit=False) # R2 miss monkeypatch.setattr(lidar, "TILE_CACHE", fake) session = _FakeSession(b"ORIGIN-TILE-BYTES") dest = tmp_path / "t.las" out = lidar._stream_download(session, "http://x/t.las", dest, "test", cache_key="douglas/t.las") assert out == dest and dest.read_bytes() == b"ORIGIN-TILE-BYTES" assert fake.fetched == ["douglas/t.las"] # checked R2 first (miss) assert session.calls == 1 # then fell through to origin assert fake.stored == ["douglas/t.las"] # and populated R2 for next cold start def test_no_cache_key_means_local_only(monkeypatch, tmp_path): # The tile-index zip passes no cache_key -> R2 is never consulted or written. fake = _FakeCache(hit=True) monkeypatch.setattr(lidar, "TILE_CACHE", fake) session = _FakeSession(b"IDX") dest = tmp_path / "index.zip" lidar._stream_download(session, "http://x/index.zip", dest, "test") assert session.calls == 1 and fake.fetched == [] and fake.stored == []