"""Tests for the static server's kernel-side local-file route (Range support).
The route backs add_raster's local-GeoTIFF support: the in-iframe GeoTIFF
reader needs HTTP Range, which the stdlib static handler does not implement.
"""
from __future__ import annotations
import urllib.error
import urllib.request
from pathlib import Path
import pytest
from geolibre import _server
@pytest.fixture
def served(tmp_path, monkeypatch):
"""Boot the static server against a stub bundle and yield helpers.
Resets the module-level server singleton so the test owns a fresh server,
then registers a known-content file and yields its URL plus the payload.
The server is shut down on teardown so its background thread/socket does not
leak across the suite.
"""
monkeypatch.setattr(_server, "_server", None)
monkeypatch.setattr(_server, "_base_url", None)
monkeypatch.setattr(_server, "_port", None)
monkeypatch.setattr(_server, "_local_files", {})
monkeypatch.setattr(_server, "_raster_tiles", {})
bundle = tmp_path / "app"
bundle.mkdir()
(bundle / "index.html").write_text("<html></html>", encoding="utf-8")
_server.serve_app(bundle)
payload = bytes(range(256)) * 40
raster = tmp_path / "dem.tif"
raster.write_bytes(payload)
url = _server.register_local_file(raster)
try:
yield url, payload
finally:
server = _server._server
if server is not None:
server.shutdown()
server.server_close()
def _get(url, headers=None):
request = urllib.request.Request(url, headers=headers or {})
with urllib.request.urlopen(request, timeout=5) as response:
return response.status, dict(response.headers), response.read()
def test_full_get_returns_whole_file(served):
url, payload = served
status, headers, body = _get(url)
assert status == 200
assert body == payload
assert headers["Accept-Ranges"] == "bytes"
def test_range_request_returns_206_slice(served):
url, payload = served
status, headers, body = _get(url, {"Range": "bytes=100-199"})
assert status == 206
assert headers["Content-Range"] == f"bytes 100-199/{len(payload)}"
assert body == payload[100:200]
def test_suffix_range(served):
url, payload = served
status, _headers, body = _get(url, {"Range": "bytes=-50"})
assert status == 206
assert body == payload[-50:]
def test_open_ended_range(served):
url, payload = served
status, headers, body = _get(url, {"Range": "bytes=10200-"})
assert status == 206
assert headers["Content-Range"] == f"bytes 10200-{len(payload) - 1}/{len(payload)}"
assert body == payload[10200:]
def test_non_bytes_range_unit_serves_the_whole_file(served):
"""An unparsable range unit falls back to a plain 200, with no Content-Range."""
status, headers, body = _get(served[0], {"Range": "items=0-99"})
assert status == 200
assert body == served[1]
assert "Content-Range" not in headers
def test_unsatisfiable_range_returns_416(served):
url, payload = served
with pytest.raises(urllib.error.HTTPError) as excinfo:
_get(url, {"Range": "bytes=999999-"})
assert excinfo.value.code == 416
assert excinfo.value.headers["Content-Range"] == f"bytes */{len(payload)}"
def test_unknown_token_404s(served):
url, _payload = served
base = url.split("/_geolibre_local/", 1)[0]
with pytest.raises(urllib.error.HTTPError) as excinfo:
_get(f"{base}/_geolibre_local/deadbeeftoken/x.tif")
assert excinfo.value.code == 404
def test_register_same_file_reuses_token(served):
url, _payload = served
path = url.split("/_geolibre_local/", 1)[1].split("/", 1)[0]
again = _server.register_local_file(
next(p for tok, p in _server._local_files.items() if tok == path)
)
assert again == url
assert len(_server._local_files) == 1
def test_registered_raster_tile_returns_png(served, tmp_path):
rasterio = pytest.importorskip("rasterio")
pytest.importorskip("rio_tiler")
np = pytest.importorskip("numpy")
from rasterio.transform import from_bounds
raster = tmp_path / "world.tif"
with rasterio.open(
raster,
"w",
driver="GTiff",
width=8,
height=8,
count=1,
dtype="float32",
crs="EPSG:4326",
transform=from_bounds(-180, -85, 180, 85, 8, 8),
) as dst:
dst.write(np.arange(64, dtype="float32").reshape(1, 8, 8))
template = _server.register_raster_tiles(raster, bands=[1], colormap="turbo", rescale=[[0, 63]])
status, headers, body = _get(template.format(z=0, x=0, y=0))
assert status == 200
assert headers["Content-Type"] == "image/png"
assert body.startswith(b"\x89PNG\r\n\x1a\n")
def test_unregister_local_file_drops_the_token(served, tmp_path):
url, _payload = served
assert len(_server._local_files) == 1
registered = next(iter(_server._local_files.values()))
_server.unregister_local_file(registered)
assert _server._local_files == {}
with pytest.raises(urllib.error.HTTPError) as excinfo:
_get(url)
assert excinfo.value.code == 404
def test_unregister_unknown_file_is_a_no_op(served, tmp_path):
_server.unregister_local_file(tmp_path / "never-registered.tif")
assert len(_server._local_files) == 1
def test_options_preflight_allows_range(served):
url, _payload = served
request = urllib.request.Request(url, method="OPTIONS")
with urllib.request.urlopen(request, timeout=5) as response:
assert response.status == 200
assert response.headers["Access-Control-Allow-Origin"] == "*"
assert "Range" in response.headers["Access-Control-Allow-Headers"]
def test_register_missing_file_raises(served):
with pytest.raises(ValueError, match="not found"):
_server.register_local_file(Path("/no/such/raster.tif"))
def test_register_requires_running_server(monkeypatch, tmp_path):
monkeypatch.setattr(_server, "_base_url", None)
existing = tmp_path / "f.tif"
existing.write_bytes(b"x")
with pytest.raises(RuntimeError, match="not running"):
_server.register_local_file(existing)