7df9ebf22c
Sync labels / sync (push) Failing after 1m9s
Publish Container Image / Build and publish container image (push) Has been cancelled
Deploy Website / Deploy to GitHub Pages (push) Has been cancelled
Deploy Website / Build website and demo (push) Has been cancelled
Deploy viewer / Deploy viewer proxy to Cloudflare Workers (push) Has been cancelled
Deploy tiles / Deploy planetary tile proxy to Cloudflare Workers (push) Has been cancelled
Deploy collab / Deploy collaboration relay to Cloudflare Workers (push) Has been cancelled
CI / Dependency audit (push) Has been cancelled
CI / E2E smoke (Playwright) (push) Has been cancelled
CI / Validate CITATION.cff (push) Has been cancelled
CI / Build and test (push) Has been cancelled
137 lines
4.7 KiB
Python
137 lines
4.7 KiB
Python
"""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.
|
|
"""
|
|
# Force a fresh singleton: other tests/imports may have started one already.
|
|
monkeypatch.setattr(_server, "_server", None)
|
|
monkeypatch.setattr(_server, "_base_url", None)
|
|
monkeypatch.setattr(_server, "_port", None)
|
|
monkeypatch.setattr(_server, "_local_files", {})
|
|
|
|
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 # 10_240 deterministic bytes
|
|
raster = tmp_path / "dem.tif"
|
|
raster.write_bytes(payload)
|
|
url = _server.register_local_file(raster)
|
|
try:
|
|
yield url, payload
|
|
finally:
|
|
# serve_app set the module-level singleton to this test's server; stop it
|
|
# before monkeypatch restores the originals.
|
|
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: # noqa: S310 - loopback
|
|
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_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
|
|
# Re-registering the same file returns the identical URL (no registry growth).
|
|
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_options_preflight_allows_range(served):
|
|
url, _payload = served
|
|
request = urllib.request.Request(url, method="OPTIONS")
|
|
with urllib.request.urlopen(request, timeout=5) as response: # noqa: S310 - loopback
|
|
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)
|