feat: add project-scoped operations
Generic Container CI/CD / test-build-publish (push) Failing after 6s
Server CI/CD v2 / build-test-publish-and-deploy (push) Failing after 6s

This commit is contained in:
2026-10-22 18:40:20 +08:00
parent d320972d57
commit 1d061f6099
73 changed files with 4384 additions and 228 deletions
+110
View File
@@ -0,0 +1,110 @@
import asyncio
import json
import logging
import httpx
import pytest
from pydantic import ValidationError
from app.core.config import settings
from app.domain.schemas.place_search import ReverseGeocodingRequest
from app.domain.schemas.project_map_config import PlaceSearchConfig
from app.services.place_search import PlaceSearchError
from app.services.reverse_geocoding import reverse_geocode_tianditu
CONFIG = PlaceSearchConfig(enabled=True, admin_code='341126', region='凤阳县')
POINT = ReverseGeocodingRequest(longitude=117.570309, latitude=32.868029)
def provider_result(**component):
return {'status': '0', 'result': {
'formatted_address': '安徽省滁州市凤阳县府城镇朝阳路76号',
'location': {'lon': 117.57, 'lat': 32.86},
'addressComponent': {'poi': '府城镇', 'road': '朝阳路', 'county': '凤阳县',
'county_code': '156341126', 'province_code': '156340000', **component}}}
@pytest.fixture(autouse=True)
def token(monkeypatch):
monkeypatch.setattr(settings, 'TIANDITU_TOKEN', 'test-only-token')
def lookup(data=None, *, config=CONFIG, status=200, failure=None):
calls = []
def transport(request):
calls.append(request)
if failure:
raise failure
return httpx.Response(status, json=provider_result() if data is None else data)
async def run():
async with httpx.AsyncClient(transport=httpx.MockTransport(transport)) as client:
return await reverse_geocode_tianditu(POINT, config=config, client=client)
return asyncio.run(run()), calls
def test_reverse_lookup_keeps_clicked_coordinate_and_normalizes_the_address():
result, calls = lookup()
assert result.name == '府城镇' and result.address == '安徽省滁州市凤阳县府城镇朝阳路76号'
assert (result.longitude, result.latitude) == (POINT.longitude, POINT.latitude)
assert result.district == '凤阳县' and result.category == '附近地名'
params = calls[0].url.params
assert params['type'] == 'geocode' and params['tk'] == 'test-only-token'
assert json.loads(params['postStr']) == {'lon': POINT.longitude, 'lat': POINT.latitude, 'ver': 1}
def test_reverse_lookup_supports_city_scope_and_falls_back_to_real_road_name():
result, _ = lookup(provider_result(poi='', county='青浦区', county_code='156310118', province_code='156310000'),
config=PlaceSearchConfig(enabled=True, admin_code='310000', region='上海市'))
assert result.name == '朝阳路' and result.category == '地址' and result.district == '青浦区'
@pytest.mark.parametrize('data,status', [
({'status': '0', 'result': None}, 404),
({'status': '0', 'result': {'formatted_address': ''}}, 404),
({'status': '1'}, 502),
({'status': '0', 'result': 'bad'}, 502),
(provider_result(county='奉贤区', county_code='156310120', province_code='156310000'), 422),
(provider_result(county='定远县', county_code='156341125'), 422),
])
def test_reverse_lookup_distinguishes_no_data_bad_response_and_out_of_scope(data, status):
with pytest.raises(PlaceSearchError) as exc:
lookup(data)
assert exc.value.status_code == status
@pytest.mark.parametrize('failure,status', [(httpx.ReadTimeout('private-url'), 504), (httpx.ConnectError('private-url'), 502)])
def test_reverse_lookup_sanitizes_transport_failures(failure, status):
with pytest.raises(PlaceSearchError) as exc:
lookup(failure=failure)
assert exc.value.status_code == status and 'private-url' not in exc.value.detail
def test_reverse_lookup_redacts_provider_key_from_http_logs(caplog):
caplog.set_level(logging.INFO, logger='httpx')
lookup()
messages = [record.getMessage() for record in caplog.records if record.name == 'httpx']
assert messages and any('tk=[redacted]' in message for message in messages)
assert all('test-only-token' not in message for message in messages)
@pytest.mark.parametrize('body', [
{'longitude': 181, 'latitude': 32}, {'longitude': 117, 'latitude': -91},
{'longitude': float('nan'), 'latitude': 32}, {'longitude': 117, 'latitude': float('inf')},
{'longitude': 117, 'latitude': 32, 'region': '全国'},
])
def test_reverse_lookup_rejects_invalid_coordinates_and_scope_overrides(body):
with pytest.raises(ValidationError):
ReverseGeocodingRequest(**body)
def test_reverse_lookup_requires_configuration_and_shared_key(monkeypatch):
with pytest.raises(PlaceSearchError) as exc:
lookup(config=CONFIG.model_copy(update={'enabled': False}))
assert exc.value.status_code == 503
monkeypatch.setattr(settings, 'TIANDITU_TOKEN', '')
with pytest.raises(PlaceSearchError) as exc:
lookup()
assert exc.value.status_code == 503