release: bump version to 0.49.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
linkong
2026-05-08 17:42:27 +08:00
parent bb9183b8a4
commit e1984c7a35
86 changed files with 9165 additions and 412 deletions

View File

@@ -2,10 +2,45 @@
import pytest
import asyncio
from typing import AsyncGenerator
from unittest.mock import AsyncMock, MagicMock, patch
import json
from unittest.mock import AsyncMock, MagicMock
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession
@pytest.fixture(autouse=True)
def bgp_collector_location_cache():
"""Mirror app startup seeding for tests that call sync BGP helpers."""
from app.services.bgp_collector_locations import (
SEED_PATH,
set_bgp_collector_location_cache,
)
payload = json.loads(SEED_PATH.read_text(encoding="utf-8"))
cache = {}
for entry in payload.get("locations", []):
collector_id = next(
alias for alias in entry.get("aliases", []) if str(alias).startswith("rrc")
)
cache[collector_id] = {
"city": entry.get("city"),
"country": entry.get("country"),
"latitude": entry.get("latitude"),
"longitude": entry.get("longitude"),
"precision": entry.get("precision") or "city",
"source": "legacy_seed",
"needs_confirmation": True,
"matched_location_name": entry.get("site") or collector_id,
"verified_at": None,
"confidence": entry.get("confidence"),
"operator": entry.get("operator"),
"site": entry.get("site"),
"verification_status": "unverified",
"source_note": entry.get("source_note"),
}
set_bgp_collector_location_cache(cache)
yield
set_bgp_collector_location_cache({})
@pytest.fixture(scope="session")

View File

@@ -1,6 +1,7 @@
"""Tests for BGP observability helpers."""
from datetime import UTC, datetime, timedelta
from types import SimpleNamespace
import pytest
from httpx import ASGITransport, AsyncClient
@@ -54,11 +55,34 @@ class _FakeResult:
def scalars(self):
return _FakeScalarResult(self._rows)
def all(self):
if self._rows and all(isinstance(row, BGPObservation) for row in self._rows):
return [
(row.prefix, row.origin_asn, row.collector, row.collector_geo)
for row in self._rows
]
return self._rows
def scalar(self):
if not self._rows:
return 0
first = self._rows[0]
if isinstance(first, (int, float, str)):
return first
if isinstance(first, tuple) and len(first) == 1:
return first[0]
return len(self._rows)
def fetchall(self):
return self._rows
def fetchone(self):
return self._rows[0] if self._rows else None
if not self._rows:
return None
first = self._rows[0]
if isinstance(first, CollectedData):
return {"extra_data": first.extra_data}
return first
class _FakeAsyncSession:
@@ -988,7 +1012,7 @@ async def test_infer_related_infrastructure_links_nearby_cables():
data_type="cable",
extra_data={"cable_id": 20},
)
db = _FakeAsyncSession([[landing], [relation], [cable]])
db = _FakeAsyncSession([[landing, relation, cable]])
result = await infer_related_infrastructure(
db,
@@ -1012,27 +1036,37 @@ async def test_infer_related_infrastructure_links_nearby_cables():
@pytest.mark.asyncio
async def test_build_bgp_collector_coverage_summarizes_observations():
now = datetime.now(UTC)
obs_one = BGPObservation(
source="ris_live_bgp",
aggregate = SimpleNamespace(
collector="rrc00",
observation_count=2,
prefix_count=2,
origin_asn_count=2,
peer_asn_count=2,
recent_15m_observation_count=2,
recent_24h_observation_count=2,
recent_7d_observation_count=2,
recent_15m_prefix_count=2,
recent_24h_prefix_count=2,
recent_7d_prefix_count=2,
latest_observed_at=now + timedelta(minutes=5),
)
latest = SimpleNamespace(
collector="rrc00",
latest_event_type="withdrawal",
country="Netherlands",
city="Amsterdam",
)
top_event = SimpleNamespace(
collector="rrc00",
prefix="203.0.113.0/24",
origin_asn=64496,
peer_asn=3333,
event_type="announcement",
observed_at=now,
collector_geo={"city": "Amsterdam", "country": "Netherlands"},
count=1,
)
obs_two = BGPObservation(
source="ris_live_bgp",
scope = SimpleNamespace(
collector="rrc00",
prefix="198.51.100.0/24",
origin_asn=64497,
peer_asn=3334,
event_type="withdrawal",
observed_at=now + timedelta(minutes=5),
collector_geo={"city": "Amsterdam", "country": "Netherlands"},
country="Netherlands",
city="Amsterdam",
)
db = _FakeAsyncSession([[obs_one, obs_two]])
db = _FakeAsyncSession([[aggregate], [latest], [top_event], [scope]])
coverage = await build_bgp_collector_coverage(db, source_filter=BGP_SOURCES)
@@ -1363,18 +1397,39 @@ async def test_bgp_event_summary_api_returns_aggregates():
@pytest.mark.asyncio
async def test_bgp_collectors_api_returns_coverage():
now = datetime.now(UTC)
observation = BGPObservation(
id=1,
source="ris_live_bgp",
aggregate = SimpleNamespace(
collector="rrc00",
peer_asn=3333,
prefix="203.0.113.0/24",
event_type="announcement",
origin_asn=64496,
observed_at=now,
collector_geo={"city": "Amsterdam", "country": "Netherlands"},
observation_count=1,
prefix_count=1,
origin_asn_count=1,
peer_asn_count=1,
recent_15m_observation_count=1,
recent_24h_observation_count=1,
recent_7d_observation_count=1,
recent_15m_prefix_count=1,
recent_24h_prefix_count=1,
recent_7d_prefix_count=1,
latest_observed_at=now,
)
latest = SimpleNamespace(
collector="rrc00",
latest_event_type="announcement",
country="Netherlands",
city="Amsterdam",
)
top_event = SimpleNamespace(
collector="rrc00",
event_type="announcement",
count=1,
)
scope = SimpleNamespace(
collector="rrc00",
country="Netherlands",
city="Amsterdam",
)
db = _FakeAsyncSession(
[[aggregate], [latest], [top_event], [scope], [aggregate], [latest], [top_event], [scope]]
)
db = _FakeAsyncSession([[observation], [observation]])
client = await _bgp_test_client(db)
try:

View File

@@ -0,0 +1,149 @@
"""Tests for the BGP collector + event location services."""
from __future__ import annotations
import pytest
from app.services import bgp_collector_locations
from app.services.bgp_collector_locations import (
RIPE_RIS_COLLECTOR_COORDS,
collect_bgp_collector_location_candidates,
iter_known_collector_names,
resolve_bgp_collector_location,
)
from app.services.bgp_event_locations import (
resolve_bgp_event_geo_dict,
resolve_bgp_event_location,
)
def test_legacy_dict_view_preserves_backward_compatible_keys():
rrc00 = RIPE_RIS_COLLECTOR_COORDS["rrc00"]
assert rrc00["city"] == "Amsterdam"
assert rrc00["country"] == "Netherlands"
assert rrc00["latitude"] == pytest.approx(52.3676)
assert rrc00["longitude"] == pytest.approx(4.9041)
# New richer fields layered on top.
assert rrc00["precision"] == "city"
assert rrc00["source"] == "legacy_seed"
assert rrc00["needs_confirmation"] is True
def test_every_legacy_collector_present():
expected = {
"rrc00", "rrc01", "rrc03", "rrc04", "rrc05", "rrc06", "rrc07",
"rrc10", "rrc11", "rrc12", "rrc13", "rrc14", "rrc15", "rrc16",
"rrc18", "rrc19", "rrc20", "rrc21", "rrc22", "rrc23", "rrc24",
"rrc25", "rrc26",
}
assert set(iter_known_collector_names()) == expected
def test_resolve_bgp_collector_returns_stored_location():
result = resolve_bgp_collector_location("rrc12")
assert result.location is not None
assert result.location.city == "Frankfurt"
assert result.location.country == "Germany"
assert result.location.precision == "city"
assert result.location.source == "legacy_seed"
assert result.location.needs_confirmation is True
def test_resolve_unknown_bgp_collector_returns_diagnostic(monkeypatch):
monkeypatch.setattr(bgp_collector_locations, "_geocode_online", lambda q: None)
result = resolve_bgp_collector_location("rrc-doesnotexist")
assert result.location is None
assert result.diagnostic is not None
assert result.diagnostic.failure_reason
def test_collect_bgp_collector_candidates_uses_stored_context_without_registry(monkeypatch):
bgp_collector_locations._geocode_online.cache_clear()
def _fake_geocode(query):
assert "CIXP" in query or "Geneva" in query
return {
"lat": "46.2044",
"lon": "6.1432",
"display_name": "Geneva, Switzerland",
"address": {"city": "Geneva", "country": "Switzerland"},
}
monkeypatch.setattr(bgp_collector_locations, "_geocode_online", _fake_geocode)
candidates, attempted = collect_bgp_collector_location_candidates(
collector="rrc04",
)
assert attempted, "stored context should feed online query attempts"
assert candidates, "online geocoding should produce at least one candidate"
best = candidates[0]
assert best.source == "nominatim_online_geocode"
assert best.needs_confirmation is True
assert all(candidate.source != "local_registry" for candidate in candidates)
def test_collect_bgp_collector_candidates_uses_nominatim_when_registry_misses(monkeypatch):
bgp_collector_locations._geocode_online.cache_clear()
def _fake_geocode(query):
if "Lyon" not in query and "France-IX" not in query and "FR-IX" not in query:
return None
return {
"lat": "45.764",
"lon": "4.8357",
"display_name": "Lyon, Auvergne-Rhône-Alpes, France",
"address": {"city": "Lyon", "country": "France"},
}
monkeypatch.setattr(bgp_collector_locations, "_geocode_online", _fake_geocode)
candidates, attempted = collect_bgp_collector_location_candidates(
collector="rrc-mystery",
city="Lyon",
country="France",
)
assert attempted, "Nominatim plan should run"
online = [c for c in candidates if c.source == "nominatim_online_geocode"]
assert online, "online resolver must produce a candidate when registry misses"
assert online[0].needs_confirmation is True
# ── BGP event resolver ─────────────────────────────────────────────
def test_event_resolver_inherits_from_owning_collector():
geo = resolve_bgp_event_geo_dict("rrc25")
assert geo["city"] == "Amsterdam"
assert geo["country"] == "Netherlands"
assert geo["source"] == "inherited_from_collector"
assert geo["precision"] == "city"
def test_event_resolver_does_not_match_unrelated_collectors():
"""Regression: passing operator=RIPE NCC must NOT make every collector match."""
rrc12 = resolve_bgp_event_geo_dict("rrc12")
rrc25 = resolve_bgp_event_geo_dict("rrc25")
assert rrc12["city"] == "Frankfurt"
assert rrc25["city"] == "Amsterdam"
assert rrc12["latitude"] != rrc25["latitude"]
def test_event_resolver_uses_source_coordinates_when_present():
geo = resolve_bgp_event_geo_dict(
"rrc12",
source_latitude=12.34,
source_longitude=56.78,
)
assert geo["latitude"] == pytest.approx(12.34)
assert geo["longitude"] == pytest.approx(56.78)
assert geo["precision"] == "precise"
assert geo["source"] == "source_coordinates"
def test_event_resolver_returns_empty_for_unknown_collector_without_source_coords():
geo = resolve_bgp_event_geo_dict("rrc-doesnotexist")
assert geo == {}
def test_event_resolver_full_result_carries_diagnostic_on_miss():
result = resolve_bgp_event_location(collector="rrc-doesnotexist")
assert result.location is None
assert result.diagnostic is not None

View File

@@ -0,0 +1,115 @@
"""Docs Gatekeeper API tests."""
import pytest
from httpx import ASGITransport, AsyncClient
from app.api.v1 import docs as docs_api
from app.main import app
from app.models.user import User
def make_user(role: str = "viewer", groups: list[str] | None = None) -> User:
user = User(
id=1,
username="docs-user",
email="docs@example.com",
password_hash="x",
role=role,
is_active=True,
)
user.gatekeeper_groups = groups or []
return user
async def get_json(path: str, user: User | None = None):
if user is not None:
async def override_user():
return user
app.dependency_overrides[docs_api.get_optional_current_user] = override_user
transport = ASGITransport(app=app)
try:
async with AsyncClient(transport=transport, base_url="http://test") as client:
return await client.get(path)
finally:
app.dependency_overrides.clear()
@pytest.mark.asyncio
async def test_public_catalog_only_for_anonymous_user():
response = await get_json("/api/v1/docs/catalog")
assert response.status_code == 200
items = response.json()["items"]
assert {item["access"] for item in items} == {"public"}
assert {item["slug"] for item in items if item["lang"] == "zh"} == {
"overview",
"quickstart",
"manual",
"location-pipeline-user",
}
@pytest.mark.asyncio
async def test_anonymous_can_read_public_doc():
response = await get_json("/api/v1/docs/zh/quickstart")
assert response.status_code == 200
assert response.json()["access"] == "public"
assert "快速开始" in response.json()["markdown"]
@pytest.mark.asyncio
async def test_anonymous_protected_doc_requires_authentication():
response = await get_json("/api/v1/docs/zh/backend-collectors")
assert response.status_code == 401
@pytest.mark.asyncio
async def test_viewer_without_group_cannot_read_developer_doc():
response = await get_json(
"/api/v1/docs/zh/backend-collectors",
make_user(role="viewer"),
)
assert response.status_code == 403
@pytest.mark.asyncio
async def test_developer_group_can_read_developer_but_not_admin_doc():
user = make_user(role="viewer", groups=["docs_developer"])
developer_response = await get_json("/api/v1/docs/zh/backend-collectors", user)
admin_response = await get_json("/api/v1/docs/zh/backend-system-service-control", user)
assert developer_response.status_code == 200
assert developer_response.json()["access"] == "docs_developer"
assert admin_response.status_code == 403
@pytest.mark.asyncio
async def test_admin_and_super_admin_can_read_admin_docs():
admin_response = await get_json(
"/api/v1/docs/zh/backend-system-service-control",
make_user(role="admin"),
)
super_admin_response = await get_json(
"/api/v1/docs/zh/backend-system-service-control",
make_user(role="super_admin"),
)
assert admin_response.status_code == 200
assert super_admin_response.status_code == 200
@pytest.mark.asyncio
async def test_unknown_language_slug_and_path_traversal_do_not_read_files():
bad_lang = await get_json("/api/v1/docs/fr/quickstart")
bad_slug = await get_json("/api/v1/docs/zh/not-a-doc")
traversal = await get_json("/api/v1/docs/zh/..%2Fmanual")
assert bad_lang.status_code == 404
assert bad_slug.status_code == 404
assert traversal.status_code == 404

View File

@@ -0,0 +1,429 @@
"""Tests for the shared location resolution pipeline.
Validates the abstraction itself: the protocol contract, the orchestrator,
each built-in resolver, and the pluggability promise (a custom resolver can
be slotted in without touching consumers).
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from app.services.location import (
InheritFromAnotherEntityResolver,
LocationCandidate,
LocationPipeline,
LocationQuery,
NominatimResolver,
RegistryResolver,
ResolverOutput,
SourceCoordinatesResolver,
)
# ── Test fixtures ────────────────────────────────────────────────────
@pytest.fixture
def tmp_registry(tmp_path: Path) -> Path:
payload = {
"locations": [
{
"canonical_name": "Test Site Alpha",
"aliases": ["alpha", "alpha-one", "Acme HQ"],
"operator": "Acme Networks",
"site": "Acme HQ",
"city": "Lyon",
"country": "France",
"latitude": 45.764,
"longitude": 4.8357,
"precision": "site",
"confidence": 0.92,
"source_note": "Test fixture",
"verified_at": "2026-05-08",
},
{
"canonical_name": "Test Site Bravo",
"aliases": ["bravo"],
"operator": "Acme Networks",
"site": "Bravo POP",
"city": "Berlin",
"country": "Germany",
"latitude": 52.52,
"longitude": 13.405,
"precision": "city",
"confidence": 0.85,
},
],
"city_fallbacks": [
{
"city": "Bhutan-Capital",
"country": "Bhutan",
"latitude": 27.4728,
"longitude": 89.639,
"precision": "city",
"confidence": 0.5,
}
],
}
path = tmp_path / "registry.json"
path.write_text(json.dumps(payload), encoding="utf-8")
return path
# ── SourceCoordinatesResolver ────────────────────────────────────────
def test_source_coordinates_resolver_passes_through_valid_coordinates():
resolver = SourceCoordinatesResolver()
query = LocationQuery(
name="Acme HQ",
source_latitude=45.0,
source_longitude=4.0,
country="France",
)
output = resolver.resolve(query)
assert len(output.candidates) == 1
candidate = output.candidates[0]
assert candidate.latitude == 45.0
assert candidate.longitude == 4.0
assert candidate.precision == "precise"
assert candidate.source == "source_coordinates"
assert candidate.needs_confirmation is False
def test_source_coordinates_resolver_skips_zero_coordinates():
resolver = SourceCoordinatesResolver()
output = resolver.resolve(
LocationQuery(name="X", source_latitude=0.0, source_longitude=0.0)
)
assert output.candidates == ()
def test_source_coordinates_resolver_skips_when_missing():
resolver = SourceCoordinatesResolver()
output = resolver.resolve(LocationQuery(name="X"))
assert output.candidates == ()
# ── RegistryResolver ─────────────────────────────────────────────────
def test_registry_resolver_matches_alias(tmp_registry):
resolver = RegistryResolver(registry_path=tmp_registry)
resolver.reload()
output = resolver.resolve(
LocationQuery(name="alpha", country="France")
)
candidates = list(output.candidates)
assert candidates, "should match registry entry"
assert any(c.matched_location_name == "Test Site Alpha" for c in candidates)
alpha = next(c for c in candidates if c.matched_location_name == "Test Site Alpha")
assert alpha.precision == "site"
assert alpha.confidence == pytest.approx(0.92)
assert alpha.needs_confirmation is True
assert alpha.location_verified_at is None
def test_registry_resolver_filters_country_mismatch(tmp_registry):
resolver = RegistryResolver(registry_path=tmp_registry)
resolver.reload()
# alpha is in France; query says Spain → should reject
output = resolver.resolve(
LocationQuery(name="alpha", country="Spain")
)
assert all(
c.matched_location_name != "Test Site Alpha" for c in output.candidates
)
def test_registry_resolver_emits_city_fallback_candidate(tmp_registry):
resolver = RegistryResolver(registry_path=tmp_registry)
resolver.reload()
output = resolver.resolve(
LocationQuery(city="Bhutan-Capital", country="Bhutan")
)
candidates = list(output.candidates)
assert candidates, "city fallback should fire"
assert any(c.source == "local_registry_city" for c in candidates)
# ── NominatimResolver ───────────────────────────────────────────────
def test_nominatim_resolver_calls_geocoder_with_plan_queries():
calls = []
def fake_geocoder(query: str):
calls.append(query)
return {
"lat": "12.34",
"lon": "56.78",
"display_name": "Test City, Country",
"address": {"city": "Test City", "country": "Country"},
}
def plan(query: LocationQuery):
return [
("primary query", ("name",)),
("secondary query", ("city",)),
]
resolver = NominatimResolver(
query_plan_builder=plan,
geocoder=fake_geocoder,
)
output = resolver.resolve(LocationQuery(name="X", country="Country"))
assert calls == ["primary query", "secondary query"]
assert output.attempted_queries == ("primary query", "secondary query")
assert len(output.candidates) == 2
assert all(c.precision == "city" for c in output.candidates)
assert all(c.needs_confirmation for c in output.candidates)
def test_nominatim_resolver_skips_when_geocoder_returns_none():
resolver = NominatimResolver(
query_plan_builder=lambda q: [("only", ("name",))],
geocoder=lambda q: None,
)
output = resolver.resolve(LocationQuery(name="X"))
assert output.candidates == ()
assert output.attempted_queries == ("only",)
def test_nominatim_resolver_swallows_exceptions_per_query():
def boom(query):
raise RuntimeError("network down")
resolver = NominatimResolver(
query_plan_builder=lambda q: [("a", ()), ("b", ())],
geocoder=boom,
)
output = resolver.resolve(LocationQuery(name="X"))
assert output.candidates == ()
assert output.attempted_queries == ("a", "b")
# ── InheritFromAnotherEntityResolver ────────────────────────────────
def test_inherit_resolver_returns_provided_candidate():
sentinel = LocationCandidate(
latitude=10.0,
longitude=20.0,
display_name="Inherited",
precision="city",
confidence=0.7,
query="inherit::test",
source="inherited",
source_note=None,
matched_fields=("collector",),
needs_confirmation=False,
)
resolver = InheritFromAnotherEntityResolver(
source_lookup=lambda q: sentinel
)
output = resolver.resolve(LocationQuery(name="X"))
assert output.candidates == (sentinel,)
def test_inherit_resolver_skips_when_lookup_returns_none():
resolver = InheritFromAnotherEntityResolver(source_lookup=lambda q: None)
assert resolver.resolve(LocationQuery(name="X")).candidates == ()
# ── LocationPipeline orchestration ──────────────────────────────────
def test_pipeline_aggregates_candidates_across_resolvers(tmp_registry):
pipeline = LocationPipeline(
[
SourceCoordinatesResolver(),
RegistryResolver(registry_path=tmp_registry),
NominatimResolver(
query_plan_builder=lambda q: [("nominatim attempt", ("name",))],
geocoder=lambda q: {
"lat": "1.0",
"lon": "2.0",
"display_name": "Online City",
"address": {"city": "Online City", "country": "France"},
},
),
]
)
pipeline.resolvers[1].reload()
candidates, attempted = pipeline.collect_candidates(
LocationQuery(
name="alpha",
country="France",
source_latitude=44.0,
source_longitude=5.0,
)
)
sources = {c.source for c in candidates}
assert "source_coordinates" in sources
assert "local_registry" in sources
assert "nominatim_online_geocode" in sources
assert "nominatim attempt" in attempted
def test_pipeline_dedupes_by_source_and_coordinates():
same = LocationCandidate(
latitude=1.0,
longitude=2.0,
display_name="dup",
precision="city",
confidence=0.5,
query="x",
source="dup_source",
source_note=None,
matched_fields=(),
needs_confirmation=False,
)
class _DupResolver:
name = "dup_source"
def resolve(self, query):
return ResolverOutput(candidates=(same, same))
pipeline = LocationPipeline([_DupResolver()])
candidates, _ = pipeline.collect_candidates(LocationQuery(name="X"))
assert len(candidates) == 1
def test_registry_short_aliases_do_not_match_inside_larger_tokens(tmp_path: Path):
registry_path = tmp_path / "registry.json"
registry_path.write_text(
json.dumps(
{
"locations": [
{
"canonical_name": "Aurora",
"aliases": ["Aurora", "ANL"],
"site": "DOE/SC/Argonne National Laboratory",
"country": "United States",
"city": "Lemont",
"latitude": 41.713,
"longitude": -87.982,
"precision": "site",
},
{
"canonical_name": "Venado",
"aliases": ["Venado"],
"site": "DOE/NNSA/LANL",
"country": "United States",
"city": "Los Alamos",
"latitude": 35.8443,
"longitude": -106.2872,
"precision": "site",
},
],
"city_fallbacks": [],
}
),
encoding="utf-8",
)
resolver = RegistryResolver(registry_path=registry_path)
resolver.reload()
output = resolver.resolve(
LocationQuery(
name="Venado",
country="United States",
extra={"site": "DOE/NNSA/LANL"},
)
)
assert len(output.candidates) == 1
assert output.candidates[0].matched_location_name == "Venado"
def test_pipeline_resolve_best_returns_highest_priority():
online = LocationCandidate(
latitude=10.0,
longitude=20.0,
display_name="online",
precision="city",
confidence=0.9,
query="x",
source="nominatim_online_geocode",
source_note=None,
matched_fields=(),
needs_confirmation=True,
)
source = LocationCandidate(
latitude=11.0,
longitude=21.0,
display_name="src",
precision="precise",
confidence=1.0,
query="x",
source="source_coordinates",
source_note=None,
matched_fields=(),
needs_confirmation=False,
)
class _StubResolver:
def __init__(self, c, name):
self._c = c
self.name = name
def resolve(self, query):
return ResolverOutput(candidates=(self._c,))
pipeline = LocationPipeline(
[
_StubResolver(online, "online"),
_StubResolver(source, "src"),
]
)
result = pipeline.resolve_best(LocationQuery(name="X"))
assert result.location is source, "source_coordinates should beat nominatim"
def test_pipeline_returns_diagnostic_when_nothing_resolves():
pipeline = LocationPipeline([SourceCoordinatesResolver()])
result = pipeline.resolve_best(LocationQuery(name="X", country="Bhutan"))
assert result.location is None
assert result.diagnostic is not None
assert result.diagnostic.country == "Bhutan"
def test_pluggability_custom_resolver_works_without_changing_pipeline():
"""Validates the abstraction promise: a new algorithm = a new class."""
class _PeeringDBStubResolver:
name = "fake_peeringdb"
def resolve(self, query):
asn = (query.extra or {}).get("asn")
if asn != 174:
return ResolverOutput()
return ResolverOutput(
candidates=(
LocationCandidate(
latitude=1.0,
longitude=2.0,
display_name="Cogent HQ",
precision="site",
confidence=0.8,
query=f"peeringdb::{asn}",
source="peeringdb_stub",
source_note="Stub for testing",
matched_fields=("asn",),
needs_confirmation=False,
),
)
)
pipeline = LocationPipeline([_PeeringDBStubResolver()])
candidates, _ = pipeline.collect_candidates(
LocationQuery(name="X", extra={"asn": 174})
)
assert len(candidates) == 1
assert candidates[0].source == "peeringdb_stub"

View File

@@ -4,6 +4,7 @@ import pytest
from httpx import ASGITransport, AsyncClient
from app.api.v1.visualization import convert_compute_centers_to_geojson
import app.services.compute_center_locations as compute_center_locations
from app.db.session import get_db
from app.main import app
from app.models.collected_data import CollectedData
@@ -89,6 +90,8 @@ def test_convert_compute_centers_to_geojson_unifies_sources():
assert supercomputer_feature["properties"]["operator"] == "ORNL"
assert supercomputer_feature["properties"]["location_precision"] == "precise"
assert supercomputer_feature["properties"]["is_estimated"] is False
assert supercomputer_feature["properties"]["location_source"] == "source_coordinates"
assert supercomputer_feature["properties"]["location_confidence"] == 1.0
gpu_feature = payload["features"][1]
assert gpu_feature["properties"]["site_type"] == "gpu_cluster"
@@ -98,8 +101,110 @@ def test_convert_compute_centers_to_geojson_unifies_sources():
assert gpu_feature["properties"]["location_precision"] == "precise"
def test_convert_compute_centers_to_geojson_uses_coordinate_hints():
hinted_record = _build_record(
def test_convert_compute_centers_to_geojson_accepts_source_coordinate_aliases():
record = _build_record(
record_id=3,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Alias Coordinates",
country="United States",
city="New York",
latitude=0.0,
longitude=0.0,
metadata={
"latitude": "",
"longitude": "",
"location": {
"lat": 40.7128,
"lng": -74.0060,
},
"value": "1200",
"unit": "TFlop/s",
},
)
payload = convert_compute_centers_to_geojson([record])
assert len(payload["features"]) == 1
feature = payload["features"][0]
assert feature["geometry"]["coordinates"] == [-74.006, 40.7128]
assert feature["properties"]["location_source"] == "source_coordinates"
def test_compute_center_source_coordinates_win_over_stored_location():
compute_center_locations.set_compute_center_location_cache({
"top500:top500-31": {
"source": "top500",
"source_id": "top500-31",
"name": "Stored Wrong",
"latitude": 1.0,
"longitude": 2.0,
"precision": "city",
"confidence": 0.5,
"needs_confirmation": True,
}
})
record = _build_record(
record_id=31,
source="top500",
data_type="supercomputer",
name="Source Wins",
country="United States",
city="Oak Ridge",
latitude=35.93,
longitude=-84.31,
metadata={"organization": "ORNL"},
)
payload = convert_compute_centers_to_geojson([record])
assert payload["features"][0]["geometry"]["coordinates"] == [-84.31, 35.93]
assert payload["features"][0]["properties"]["location_source"] == "source_coordinates"
compute_center_locations.set_compute_center_location_cache({})
def test_compute_center_geojson_uses_stored_location_when_source_coords_missing():
compute_center_locations.set_compute_center_location_cache({
"epoch_ai_gpu:epoch_ai_gpu-32": {
"source": "epoch_ai_gpu",
"source_id": "epoch_ai_gpu-32",
"name": "Stored Cluster",
"city": "Memphis",
"country": "United States",
"latitude": 35.1495,
"longitude": -90.049,
"precision": "city",
"confidence": 0.72,
"location_source": "manual_selection",
"source_note": "Saved by user",
"needs_confirmation": False,
"verified_at": "2026-05-08T00:00:00Z",
}
})
record = _build_record(
record_id=32,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Stored Cluster",
country="United States",
city="",
latitude=0.0,
longitude=0.0,
metadata={"value": "1200", "unit": "TFlop/s"},
)
payload = convert_compute_centers_to_geojson([record])
assert len(payload["features"]) == 1
feature = payload["features"][0]
assert feature["geometry"]["coordinates"] == [-90.049, 35.1495]
assert feature["properties"]["location_source"] == "stored_compute_center_location"
assert feature["properties"]["needs_confirmation"] is False
compute_center_locations.set_compute_center_location_cache({})
def test_convert_compute_centers_to_geojson_does_not_use_registry_aliases():
registry_record = _build_record(
record_id=3,
source="top500",
data_type="supercomputer",
@@ -114,23 +219,51 @@ def test_convert_compute_centers_to_geojson_uses_coordinate_hints():
},
)
payload = convert_compute_centers_to_geojson([hinted_record])
payload = convert_compute_centers_to_geojson([registry_record])
assert len(payload["features"]) == 1
coords = payload["features"][0]["geometry"]["coordinates"]
assert coords[0] == pytest.approx(-84.3107)
assert coords[1] == pytest.approx(35.9319)
assert payload["features"][0]["properties"]["is_estimated"] is True
assert payload["features"][0]["properties"]["location_precision"] == "estimated_site"
assert payload["features"] == []
assert len(payload["unresolved"]) == 1
assert payload["unresolved"][0]["name"] == "Frontier"
assert "source coords" in payload["unresolved"][0]["failure_reason"]
def test_convert_compute_centers_to_geojson_falls_back_to_country_centroid():
centroid_record = _build_record(
def test_convert_compute_centers_to_geojson_does_not_use_city_fallback():
city_record = _build_record(
record_id=4,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Sample GPU Cluster",
country="United States",
city="San Francisco, CA",
latitude=0.0,
longitude=0.0,
metadata={
"organization": "Sample Operator",
"value": "10000",
"unit": "TFlop/s",
},
)
payload = convert_compute_centers_to_geojson([city_record])
assert payload["features"] == []
assert len(payload["unresolved"]) == 1
assert payload["unresolved"][0]["city"] == "San Francisco, CA"
def test_convert_compute_centers_to_geojson_does_not_online_geocode_on_startup(monkeypatch):
compute_center_locations._geocode_online.cache_clear()
def _explode(_query):
raise AssertionError("startup GeoJSON must not call online geocoding")
monkeypatch.setattr(compute_center_locations, "_geocode_online", _explode)
country_record = _build_record(
record_id=4,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Unknown Cluster",
country="United States",
country="France",
city="",
latitude=0.0,
longitude=0.0,
@@ -141,16 +274,228 @@ def test_convert_compute_centers_to_geojson_falls_back_to_country_centroid():
},
)
payload = convert_compute_centers_to_geojson([centroid_record])
payload = convert_compute_centers_to_geojson([country_record])
assert len(payload["features"]) == 1
props = payload["features"][0]["properties"]
coords = payload["features"][0]["geometry"]["coordinates"]
assert coords[0] == pytest.approx(-98.5795)
assert coords[1] == pytest.approx(39.8283)
assert props["is_estimated"] is True
assert props["location_precision"] == "estimated_country"
assert props["geography_mode"] == "country_centroid"
assert payload["features"] == []
assert len(payload["unresolved"]) == 1
assert payload["unresolved"][0]["operator"] == "Unknown Operator"
def test_convert_compute_centers_to_geojson_records_diagnostics_when_online_geocode_fails(monkeypatch):
compute_center_locations._geocode_online.cache_clear()
monkeypatch.setattr(compute_center_locations, "_geocode_online", lambda _query: None)
country_record = _build_record(
record_id=5,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Unknown French Cluster",
country="France",
city="",
latitude=0.0,
longitude=0.0,
metadata={
"organization": "Unknown Operator",
"value": "10000",
"unit": "TFlop/s",
},
)
payload = convert_compute_centers_to_geojson([country_record])
assert payload["features"] == []
assert len(payload["unresolved"]) == 1
diagnostic = payload["unresolved"][0]
assert diagnostic["record_id"] == 5
assert diagnostic["source_id"] == "epoch_ai_gpu-5"
assert diagnostic["country"] == "France"
assert diagnostic["operator"] == "Unknown Operator"
assert diagnostic["failure_reason"]
assert diagnostic["attempted_queries"] == []
def test_convert_compute_centers_to_geojson_records_diagnostics_when_no_country(monkeypatch):
compute_center_locations._geocode_online.cache_clear()
monkeypatch.setattr(compute_center_locations, "_geocode_online", lambda _query: None)
unknown_record = _build_record(
record_id=6,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Unknown Offshore Cluster",
country="",
city="",
latitude=0.0,
longitude=0.0,
metadata={
"organization": "Unknown Operator",
"value": "10000",
"unit": "TFlop/s",
},
)
payload = convert_compute_centers_to_geojson([unknown_record])
assert payload["features"] == []
assert len(payload["unresolved"]) == 1
assert payload["unresolved"][0]["failure_reason"]
def test_convert_compute_centers_to_geojson_never_emits_zero_coordinates(monkeypatch):
compute_center_locations._geocode_online.cache_clear()
def _zero_geocode(query):
return {
"lat": "0",
"lon": "0",
"display_name": "Null Island",
"address": {"city": "", "country": ""},
}
monkeypatch.setattr(compute_center_locations, "_geocode_online", _zero_geocode)
record = _build_record(
record_id=7,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Null Island Cluster",
country="",
city="",
latitude=0.0,
longitude=0.0,
metadata={"organization": "Null Inc"},
)
payload = convert_compute_centers_to_geojson([record])
for feature in payload["features"]:
coords = feature["geometry"]["coordinates"]
assert coords[0] not in (0, 0.0)
assert coords[1] not in (0, 0.0)
def test_convert_compute_centers_to_geojson_rejects_country_or_unknown_precision(monkeypatch):
compute_center_locations._geocode_online.cache_clear()
monkeypatch.setattr(compute_center_locations, "_geocode_online", lambda _query: None)
record = _build_record(
record_id=8,
source="top500",
data_type="supercomputer",
name="Phantom System",
country="Liechtenstein",
city="",
latitude=0.0,
longitude=0.0,
metadata={"organization": "Phantom Operator", "rmax": 100.0},
)
payload = convert_compute_centers_to_geojson([record])
for feature in payload["features"]:
assert feature["properties"]["location_precision"] in {"precise", "site", "city"}
assert payload["features"] == []
assert payload["unresolved"], "phantom record must surface as diagnostic"
def test_resolve_full_returns_diagnostic_for_unresolved(monkeypatch):
compute_center_locations._geocode_online.cache_clear()
monkeypatch.setattr(compute_center_locations, "_geocode_online", lambda _query: None)
record = _build_record(
record_id=11,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Phantom Cluster",
country="Bhutan",
city="",
latitude=0.0,
longitude=0.0,
metadata={"organization": "Mystery Operator"},
)
result = compute_center_locations.resolve_compute_center_location_full(record, record.extra_data)
assert result.location is None
assert result.diagnostic is not None
assert result.diagnostic.failure_reason
assert result.diagnostic.country == "Bhutan"
def test_collect_location_candidates_ignores_registry_and_uses_online(monkeypatch):
compute_center_locations._geocode_online.cache_clear()
def _fake_ror(query):
assert query == "Oak Ridge National Laboratory"
return {
"id": "https://ror.org/01qz5mb56",
"names": [
{"types": ["ror_display"], "value": "Oak Ridge National Laboratory"}
],
"locations": [
{
"geonames_id": 4646571,
"geonames_details": {
"name": "Oak Ridge",
"country_subdivision_name": "Tennessee",
"country_name": "United States",
"lat": 36.01036,
"lng": -84.26964,
},
}
],
}
monkeypatch.setattr(compute_center_locations, "_lookup_ror_organization", _fake_ror)
monkeypatch.setattr(compute_center_locations, "_geocode_online", lambda _query: None)
candidates, attempted = compute_center_locations.collect_location_candidates(
name="Frontier",
operator="Oak Ridge National Laboratory",
country="United States",
)
assert candidates, "online source-traced query must produce a candidate"
best = candidates[0]
assert best.source == "ror_organization_registry"
assert best.precision == "city"
assert best.needs_confirmation is True
assert attempted[0] == "ror:Oak Ridge National Laboratory"
def test_collect_location_candidates_returns_online_when_registry_misses(monkeypatch):
compute_center_locations._geocode_online.cache_clear()
def _fake_geocode(query):
if "Lyon" not in query and "Mystery Operator" not in query and "Lyon, France" not in query:
return None
return {
"lat": "45.7640",
"lon": "4.8357",
"display_name": "Lyon, Auvergne-Rhône-Alpes, France",
"address": {"city": "Lyon", "state": "Auvergne-Rhône-Alpes", "country": "France"},
}
monkeypatch.setattr(compute_center_locations, "_geocode_online", _fake_geocode)
monkeypatch.setattr(compute_center_locations, "_lookup_ror_organization", lambda _query: None)
candidates, attempted = compute_center_locations.collect_location_candidates(
name="Mystery System",
operator="Mystery Operator",
city="Lyon",
country="France",
)
assert candidates, "online geocoding must produce a candidate"
online_candidates = [c for c in candidates if c.source == "nominatim_online_geocode"]
assert online_candidates, "must include at least one online candidate"
online = online_candidates[0]
assert online.precision == "city"
assert online.needs_confirmation is True
assert online.suggested_registry_entry is not None
assert attempted, "must record attempted query strings"
def test_collect_location_candidates_failure_returns_attempted_queries(monkeypatch):
compute_center_locations._geocode_online.cache_clear()
monkeypatch.setattr(compute_center_locations, "_lookup_ror_organization", lambda _query: None)
monkeypatch.setattr(compute_center_locations, "_geocode_online", lambda _query: None)
candidates, attempted = compute_center_locations.collect_location_candidates(
name="Mystery Offshore Cluster",
operator="Mystery Operator",
country="Bhutan",
)
assert candidates == []
assert attempted, "even on failure we record attempted queries for diagnostics"
@pytest.mark.asyncio
@@ -353,3 +698,304 @@ async def test_visualization_geo_summary_returns_counts(monkeypatch):
assert stats["bgp_collector_count"] == 2
finally:
app.dependency_overrides.clear()
@pytest.mark.asyncio
async def test_collect_location_endpoint_returns_candidates_for_known_record(monkeypatch):
def _fake_ror(query):
assert query == "Oak Ridge National Laboratory"
return {
"id": "https://ror.org/01qz5mb56",
"names": [
{"types": ["ror_display"], "value": "Oak Ridge National Laboratory"}
],
"locations": [
{
"geonames_id": 4646571,
"geonames_details": {
"name": "Oak Ridge",
"country_subdivision_name": "Tennessee",
"country_name": "United States",
"lat": 36.01036,
"lng": -84.26964,
},
}
],
}
monkeypatch.setattr(compute_center_locations, "_lookup_ror_organization", _fake_ror)
monkeypatch.setattr(compute_center_locations, "_geocode_online", lambda _query: None)
target_record = _build_record(
record_id=42,
source="top500",
data_type="supercomputer",
name="Frontier",
country="United States",
city="",
latitude=0.0,
longitude=0.0,
metadata={"organization": "Oak Ridge National Laboratory", "rmax": 1102000.0},
)
class _ScalarResult:
def __init__(self, rows):
self._rows = rows
def scalars(self):
class _Scalars:
def __init__(self, rows):
self._rows = rows
def first(self):
return self._rows[0] if self._rows else None
def all(self):
return self._rows
return _Scalars(self._rows)
class _FakeSession:
async def execute(self, _query):
return _ScalarResult([target_record])
async def override_get_db():
yield _FakeSession()
app.dependency_overrides[get_db] = override_get_db
transport = ASGITransport(app=app)
try:
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/api/v1/visualization/compute-centers/top500-42/collect-location",
json={
"name": "Frontier",
"operator": "Oak Ridge National Laboratory",
"country": "United States",
},
)
assert response.status_code == 200
body = response.json()
assert body["success"] is True
assert body["candidates"], "must include candidates"
best = body["best_candidate"]
assert best["precision"] in {"precise", "site", "city"}
assert best["source"] == "ror_organization_registry"
assert best["needs_confirmation"] is True
assert best["matched_fields"], "matched_fields must be populated"
finally:
app.dependency_overrides.clear()
@pytest.mark.asyncio
async def test_collect_location_endpoint_returns_failure_reason(monkeypatch):
monkeypatch.setattr(compute_center_locations, "_lookup_ror_organization", lambda _query: None)
monkeypatch.setattr(compute_center_locations, "_geocode_online", lambda _query: None)
class _ScalarResult:
def __init__(self, rows):
self._rows = rows
def scalars(self):
class _Scalars:
def __init__(self, rows):
self._rows = rows
def first(self):
return self._rows[0] if self._rows else None
def all(self):
return self._rows
return _Scalars(self._rows)
class _FakeSession:
async def execute(self, _query):
return _ScalarResult([])
async def override_get_db():
yield _FakeSession()
app.dependency_overrides[get_db] = override_get_db
transport = ASGITransport(app=app)
try:
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/api/v1/visualization/compute-centers/epoch-mystery-99/collect-location",
json={
"name": "Mystery Cluster",
"operator": "Mystery Operator",
"country": "Bhutan",
},
)
assert response.status_code == 200
body = response.json()
assert body["success"] is False
assert body["failure_reason"]
assert body["candidates"] == []
assert body["attempted_queries"], "must include attempted queries"
finally:
app.dependency_overrides.clear()
@pytest.mark.asyncio
async def test_save_location_endpoint_upserts_and_geojson_can_render():
target_record = _build_record(
record_id=52,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Saved Cluster",
country="United States",
city="",
latitude=0.0,
longitude=0.0,
metadata={"value": "1200", "unit": "TFlop/s"},
)
class _ScalarResult:
def __init__(self, rows):
self._rows = rows
def scalars(self):
class _Scalars:
def __init__(self, rows):
self._rows = rows
def first(self):
return self._rows[0] if self._rows else None
def all(self):
return self._rows
return _Scalars(self._rows)
class _FakeSession:
def __init__(self):
self.saved = []
async def execute(self, _query):
if self.saved:
return _ScalarResult(self.saved)
return _ScalarResult([target_record])
async def scalar(self, _query):
return None
def add(self, record):
self.saved.append(record)
async def commit(self):
return None
async def refresh(self, _record):
return None
fake_session = _FakeSession()
async def override_get_db():
yield fake_session
app.dependency_overrides[get_db] = override_get_db
transport = ASGITransport(app=app)
try:
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post(
"/api/v1/visualization/compute-centers/epoch_ai_gpu-52/location",
json={
"source": "epoch_ai_gpu",
"name": "Saved Cluster",
"latitude": 35.1495,
"longitude": -90.049,
"precision": "city",
"confidence": 0.72,
"location_source": "ror_organization_registry",
"source_note": "Selected by user",
"raw_payload": {"source": "ror_organization_registry"},
},
)
assert response.status_code == 200
body = response.json()
assert body["success"] is True
assert fake_session.saved
payload = convert_compute_centers_to_geojson([target_record])
assert len(payload["features"]) == 1
feature = payload["features"][0]
assert feature["geometry"]["coordinates"] == [-90.049, 35.1495]
assert feature["properties"]["location_source"] == "stored_compute_center_location"
finally:
app.dependency_overrides.clear()
compute_center_locations.set_compute_center_location_cache({})
def test_resolution_chain_orders_source_coords_first(monkeypatch):
def _explode(_query):
raise AssertionError("source coords must short-circuit before online geocoding")
monkeypatch.setattr(compute_center_locations, "_geocode_online", _explode)
record = _build_record(
record_id=20,
source="top500",
data_type="supercomputer",
name="Frontier",
country="United States",
city="Oak Ridge",
latitude=35.93,
longitude=-84.31,
metadata={"organization": "ORNL"},
)
result = compute_center_locations.resolve_compute_center_location_full(record, record.extra_data)
assert result.is_resolved
assert result.location.location_precision == "precise"
assert result.location.location_source == "source_coordinates"
def test_no_country_centroid_or_major_compute_city_fallback(monkeypatch):
monkeypatch.setattr(compute_center_locations, "_geocode_online", lambda _query: None)
record = _build_record(
record_id=21,
source="top500",
data_type="supercomputer",
name="Phantom System",
country="France",
city="",
latitude=0.0,
longitude=0.0,
metadata={"organization": "Phantom Operator"},
)
result = compute_center_locations.resolve_compute_center_location_full(record, record.extra_data)
assert result.location is None, "must NOT fall back to country centroid or hashed major city"
assert result.diagnostic is not None
assert result.diagnostic.failure_reason
def test_repository_has_no_forbidden_precision_tokens():
"""Static guard: forbidden fallback strategies must not regress into the codebase.
Each forbidden token may appear at most once per target file, and only inside
the FORBIDDEN_PRECISIONS guard list (so we still reject them at runtime).
"""
from pathlib import Path
backend_root = Path(__file__).resolve().parents[1]
forbidden_tokens = (
"country_centroid",
"country_major_compute_city",
"estimated_country",
)
targets = [
backend_root / "app" / "services" / "compute_center_locations.py",
backend_root / "app" / "api" / "v1" / "visualization.py",
]
for target in targets:
text = target.read_text(encoding="utf-8")
for token in forbidden_tokens:
occurrences = text.count(token)
assert occurrences <= 1, (
f"{token} appears {occurrences} times in {target}; "
"should only appear in FORBIDDEN_PRECISIONS guard list."
)
if occurrences == 1:
assert "FORBIDDEN_PRECISIONS" in text, (
f"{token} appears in {target} outside the FORBIDDEN_PRECISIONS guard"
)