test: add geocoding lang parameter unit tests
8 standalone tests covering Accept-Language header in Nominatim requests and languageCode field in Google Places payload, for both explicit lang values (en, de, fr) and the default fallback (en).
This commit is contained in:
186
backend/server/test_geocoding_lang.py
Normal file
186
backend/server/test_geocoding_lang.py
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Standalone unit tests for geocoding lang parameter support.
|
||||||
|
No Django project setup needed — all HTTP calls are mocked.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
cd /home/alex/projects/voyage/backend/server
|
||||||
|
python3 test_geocoding_lang.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
import types
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Minimal stubs so geocoding.py can be imported without a full Django project
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
django_module = types.ModuleType("django")
|
||||||
|
django_conf_module = types.ModuleType("django.conf")
|
||||||
|
|
||||||
|
|
||||||
|
class _Settings:
|
||||||
|
GOOGLE_MAPS_API_KEY = None
|
||||||
|
|
||||||
|
|
||||||
|
settings = _Settings()
|
||||||
|
django_conf_module.settings = settings
|
||||||
|
django_module.conf = django_conf_module
|
||||||
|
sys.modules.setdefault("django", django_module)
|
||||||
|
sys.modules.setdefault("django.conf", django_conf_module)
|
||||||
|
|
||||||
|
# Stub worldtravel models (not used by the functions under test)
|
||||||
|
for mod_name in ("worldtravel", "worldtravel.models"):
|
||||||
|
m = types.ModuleType(mod_name)
|
||||||
|
for attr in ("Region", "City", "VisitedRegion", "VisitedCity"):
|
||||||
|
setattr(m, attr, None)
|
||||||
|
sys.modules.setdefault(mod_name, m)
|
||||||
|
|
||||||
|
sys.path.insert(0, "/home/alex/projects/voyage/backend/server")
|
||||||
|
from adventures.geocoding import search_osm, search_google # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def _mock_response(json_data, status_code=200):
|
||||||
|
r = MagicMock()
|
||||||
|
r.json.return_value = json_data
|
||||||
|
r.raise_for_status.return_value = None
|
||||||
|
r.status_code = status_code
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
_OSM_RESULT = [
|
||||||
|
{
|
||||||
|
"lat": "50.0755",
|
||||||
|
"lon": "14.4378",
|
||||||
|
"name": "Prague",
|
||||||
|
"display_name": "Prague, Czech Republic",
|
||||||
|
"type": "city",
|
||||||
|
"category": "place",
|
||||||
|
"importance": 0.9,
|
||||||
|
"addresstype": "city",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
_GOOGLE_RESULT = {
|
||||||
|
"places": [
|
||||||
|
{
|
||||||
|
"displayName": {"text": "Prague"},
|
||||||
|
"formattedAddress": "Prague, Czech Republic",
|
||||||
|
"location": {"latitude": 50.0755, "longitude": 14.4378},
|
||||||
|
"types": ["locality"],
|
||||||
|
"rating": 4.5,
|
||||||
|
"userRatingCount": 1000,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Tests: search_osm (Nominatim)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
class TestSearchOsmLang(unittest.TestCase):
|
||||||
|
def test_explicit_lang_en(self):
|
||||||
|
"""search_osm('Prague', lang='en') sends Accept-Language: en"""
|
||||||
|
with patch(
|
||||||
|
"requests.get", return_value=_mock_response(_OSM_RESULT)
|
||||||
|
) as mock_get:
|
||||||
|
search_osm("Prague", lang="en")
|
||||||
|
headers = mock_get.call_args[1].get("headers", {})
|
||||||
|
self.assertEqual(headers.get("Accept-Language"), "en")
|
||||||
|
|
||||||
|
def test_explicit_lang_de(self):
|
||||||
|
"""search_osm('Prague', lang='de') sends Accept-Language: de"""
|
||||||
|
with patch("requests.get", return_value=_mock_response([])) as mock_get:
|
||||||
|
search_osm("Prague", lang="de")
|
||||||
|
headers = mock_get.call_args[1].get("headers", {})
|
||||||
|
self.assertEqual(headers.get("Accept-Language"), "de")
|
||||||
|
|
||||||
|
def test_default_lang_is_en(self):
|
||||||
|
"""search_osm('Prague') without lang defaults to Accept-Language: en"""
|
||||||
|
with patch("requests.get", return_value=_mock_response([])) as mock_get:
|
||||||
|
search_osm("Prague")
|
||||||
|
headers = mock_get.call_args[1].get("headers", {})
|
||||||
|
self.assertEqual(
|
||||||
|
headers.get("Accept-Language"),
|
||||||
|
"en",
|
||||||
|
f"Expected 'en', got {headers.get('Accept-Language')!r}",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_returns_list(self):
|
||||||
|
"""search_osm returns a list"""
|
||||||
|
with patch("requests.get", return_value=_mock_response(_OSM_RESULT)):
|
||||||
|
result = search_osm("Prague", lang="en")
|
||||||
|
self.assertIsInstance(result, list)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Tests: search_google (Google Places)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
class TestSearchGoogleLang(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
settings.GOOGLE_MAPS_API_KEY = "test-api-key"
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
settings.GOOGLE_MAPS_API_KEY = None
|
||||||
|
|
||||||
|
def test_explicit_lang_en(self):
|
||||||
|
"""search_google('Prague', lang='en') posts languageCode: 'en'"""
|
||||||
|
with patch(
|
||||||
|
"requests.post", return_value=_mock_response(_GOOGLE_RESULT)
|
||||||
|
) as mock_post:
|
||||||
|
search_google("Prague", lang="en")
|
||||||
|
payload = mock_post.call_args[1].get("json", {})
|
||||||
|
self.assertIn("languageCode", payload)
|
||||||
|
self.assertEqual(payload["languageCode"], "en")
|
||||||
|
|
||||||
|
def test_explicit_lang_fr(self):
|
||||||
|
"""search_google('Prague', lang='fr') posts languageCode: 'fr'"""
|
||||||
|
with patch(
|
||||||
|
"requests.post", return_value=_mock_response(_GOOGLE_RESULT)
|
||||||
|
) as mock_post:
|
||||||
|
search_google("Prague", lang="fr")
|
||||||
|
payload = mock_post.call_args[1].get("json", {})
|
||||||
|
self.assertEqual(payload.get("languageCode"), "fr")
|
||||||
|
|
||||||
|
def test_default_lang_is_en(self):
|
||||||
|
"""search_google('Prague') without lang defaults to languageCode: 'en'"""
|
||||||
|
with patch(
|
||||||
|
"requests.post", return_value=_mock_response(_GOOGLE_RESULT)
|
||||||
|
) as mock_post:
|
||||||
|
search_google("Prague")
|
||||||
|
payload = mock_post.call_args[1].get("json", {})
|
||||||
|
self.assertEqual(
|
||||||
|
payload.get("languageCode"),
|
||||||
|
"en",
|
||||||
|
f"Expected 'en', got {payload.get('languageCode')!r}",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_returns_list(self):
|
||||||
|
"""search_google returns a list"""
|
||||||
|
with patch("requests.post", return_value=_mock_response(_GOOGLE_RESULT)):
|
||||||
|
result = search_google("Prague", lang="en")
|
||||||
|
self.assertIsInstance(result, list)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 60)
|
||||||
|
print("Geocoding lang parameter — unit tests")
|
||||||
|
print("=" * 60)
|
||||||
|
runner = unittest.TextTestRunner(verbosity=2)
|
||||||
|
suite = unittest.TestLoader().loadTestsFromNames(
|
||||||
|
["__main__.TestSearchOsmLang", "__main__.TestSearchGoogleLang"]
|
||||||
|
)
|
||||||
|
result = runner.run(suite)
|
||||||
|
print("=" * 60)
|
||||||
|
if result.wasSuccessful():
|
||||||
|
print(f"ALL {result.testsRun} TESTS PASSED ✓")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print(f"FAILURES: {len(result.failures)}, ERRORS: {len(result.errors)}")
|
||||||
|
sys.exit(1)
|
||||||
Reference in New Issue
Block a user