Files
Shadowbroker/backend/tests/test_basemap_config.py
T
C3B2W23andClaude Fable 5.1 667f51cb7a fix(basemap): serve CARTO key from backend, bound the map gate, add source attribution
Review follow-up:

- Drop the Next.js route. CARTO_API_KEY is now a regular backend registry
  key (env, .env, or the API Keys panel) served by public
  GET /api/basemap-config. Every frontend mode already proxies /api/* to
  the backend (Next.js proxy in web mode, companion server in packaged
  desktop), so this covers web and desktop with one mechanism and leaves
  the static export untouched. Also removes the invalid non-handler
  export from the route module by removing the module.
- useBasemapConfig: fail open to the unkeyed style after 3 s, abort the
  request at 15 s, apply a late key when it arrives, cache successes per
  page and retry failures on the next mount.
- Declare OSM/CARTO attribution on the raster source (same markup as the
  viewer's existing AttributionControl so MapLibre de-duplicates it).
- Tests: backend endpoint (unset / set+trimmed / persisted operator key /
  registry), hook behaviour (success, non-OK, network error, soft timeout
  then late key, hard abort, shared request and retry), attribution and
  gating source checks.
- CARTO_API_KEY moves to the backend service in docker-compose.yml; docs
  updated accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 15:16:47 -07:00

56 lines
2.0 KiB
Python

"""GET /api/basemap-config serves the CARTO basemap key to the browser.
The key is public by nature (the browser sends it to CARTO on every tile
request), so the endpoint needs no admin auth. It must read the key at
request time so Docker operators can set it without a rebuild, and it must
honor the persisted operator key file like every other registry key.
"""
import pytest
from fastapi.testclient import TestClient
from services import api_settings
@pytest.fixture
def client(tmp_path, monkeypatch):
monkeypatch.setattr(api_settings, "OPERATOR_KEYS_ENV_PATH", tmp_path / "operator_api_keys.env")
monkeypatch.delenv("CARTO_API_KEY", raising=False)
import main
return TestClient(main.app, raise_server_exceptions=False)
def test_unconfigured_when_env_unset(client):
r = client.get("/api/basemap-config")
assert r.status_code == 200
assert r.json() == {"carto": {"configured": False, "key": ""}}
def test_returns_trimmed_key_without_admin_auth(client, monkeypatch):
monkeypatch.setenv("CARTO_API_KEY", " carto-test-key ")
r = client.get("/api/basemap-config")
assert r.status_code == 200
assert r.json() == {"carto": {"configured": True, "key": "carto-test-key"}}
def test_reads_persisted_operator_key_file(client, tmp_path):
(tmp_path / "operator_api_keys.env").write_text("CARTO_API_KEY=persisted-key\n")
r = client.get("/api/basemap-config")
assert r.json()["carto"] == {"configured": True, "key": "persisted-key"}
def test_settings_model_exposes_carto_key(monkeypatch):
# env_check reads keys off Settings, so the field must exist there or the
# startup check would always report CARTO_API_KEY as unset.
from services.config import Settings
monkeypatch.setenv("CARTO_API_KEY", "from-env")
assert Settings().CARTO_API_KEY == "from-env"
def test_carto_key_is_in_registry_and_saveable():
assert "CARTO_API_KEY" in api_settings.ALLOWED_ENV_KEYS
entry = next(a for a in api_settings.API_REGISTRY if a["env_key"] == "CARTO_API_KEY")
assert entry["required"] is False