230 lines
8.5 KiB
Python
230 lines
8.5 KiB
Python
from django.test import TestCase
|
|
from common.models import Municipality, AdrStreet, AdrPoint
|
|
from django.contrib.gis.geos import Point
|
|
from .internal_geocoding import (
|
|
normalize_address,
|
|
search_address_points,
|
|
search_streets,
|
|
geocode_internal,
|
|
DEFAULT_SCORE_THRESHOLD
|
|
)
|
|
|
|
|
|
class NormalizeAddressTestCase(TestCase):
|
|
"""Test address normalization"""
|
|
|
|
def test_normalize_empty_address(self):
|
|
self.assertEqual(normalize_address(""), "")
|
|
self.assertEqual(normalize_address(None), "")
|
|
|
|
def test_normalize_removes_extra_spaces(self):
|
|
self.assertEqual(normalize_address(" rue de la loi "), "rue de la loi")
|
|
|
|
def test_normalize_lowercase(self):
|
|
self.assertEqual(normalize_address("Rue DE La LOI"), "rue de la loi")
|
|
|
|
|
|
class InternalGeocodingTestCase(TestCase):
|
|
"""Test internal geocoding with fuzzy matching"""
|
|
|
|
def setUp(self):
|
|
"""Create test data"""
|
|
# Create a municipality
|
|
self.municipality = Municipality.objects.create(
|
|
fid="1000",
|
|
name_fr="Bruxelles",
|
|
name_nl="Brussel",
|
|
niscode="21004"
|
|
)
|
|
|
|
# Create a street
|
|
self.street = AdrStreet.objects.create(
|
|
fid="street_001",
|
|
name_fr="Rue de la Loi",
|
|
name_nl="Wetstraat",
|
|
full_street_fr="Rue de la Loi",
|
|
full_street_nl="Wetstraat",
|
|
municipality=self.municipality,
|
|
lon=4.3676,
|
|
lat=50.8467,
|
|
centroid=Point(4.3676, 50.8467, srid=3812)
|
|
)
|
|
|
|
# Create address points
|
|
self.point1 = AdrPoint.objects.create(
|
|
fid="point_001",
|
|
number="16",
|
|
street_name=self.street,
|
|
municipality=self.municipality,
|
|
zipcode="1000",
|
|
full_address_fr="16 Rue de la Loi Bruxelles",
|
|
full_address_nl="16 Wetstraat Brussel",
|
|
lon=4.3676,
|
|
lat=50.8467,
|
|
geom=Point(4.3676, 50.8467, srid=3812)
|
|
)
|
|
|
|
self.point2 = AdrPoint.objects.create(
|
|
fid="point_002",
|
|
number="100",
|
|
street_name=self.street,
|
|
municipality=self.municipality,
|
|
zipcode="1000",
|
|
full_address_fr="100 Rue de la Loi Bruxelles",
|
|
full_address_nl="100 Wetstraat Brussel",
|
|
lon=4.3680,
|
|
lat=50.8470,
|
|
geom=Point(4.3680, 50.8470, srid=3812)
|
|
)
|
|
|
|
def test_search_address_points_exact_match(self):
|
|
"""Test searching for an exact address match"""
|
|
results = search_address_points("16 Rue de la Loi Bruxelles", limit=5)
|
|
self.assertGreater(len(results), 0)
|
|
point, score = results[0]
|
|
self.assertEqual(point.fid, "point_001")
|
|
self.assertGreater(score, 90) # Should be a very high score
|
|
|
|
def test_search_address_points_french(self):
|
|
"""Test searching in French"""
|
|
results = search_address_points("16 rue de la loi", limit=5)
|
|
self.assertGreater(len(results), 0)
|
|
point, score = results[0]
|
|
self.assertEqual(point.number, "16")
|
|
self.assertGreater(score, 60)
|
|
|
|
def test_search_address_points_dutch(self):
|
|
"""Test searching in Dutch"""
|
|
results = search_address_points("16 wetstraat", limit=5)
|
|
self.assertGreater(len(results), 0)
|
|
point, score = results[0]
|
|
self.assertEqual(point.number, "16")
|
|
self.assertGreater(score, 60)
|
|
|
|
def test_search_streets_french(self):
|
|
"""Test searching for streets in French"""
|
|
results = search_streets("Rue de la Loi", limit=5)
|
|
self.assertGreater(len(results), 0)
|
|
street, score = results[0]
|
|
self.assertEqual(street.fid, "street_001")
|
|
self.assertGreater(score, 90)
|
|
|
|
def test_search_streets_dutch(self):
|
|
"""Test searching for streets in Dutch"""
|
|
results = search_streets("Wetstraat", limit=5)
|
|
self.assertGreater(len(results), 0)
|
|
street, score = results[0]
|
|
self.assertEqual(street.fid, "street_001")
|
|
self.assertGreater(score, 90)
|
|
|
|
def test_geocode_internal_success(self):
|
|
"""Test successful internal geocoding"""
|
|
lon, lat, found_addr, score = geocode_internal("16 Rue de la Loi", threshold=60)
|
|
self.assertIsNotNone(lon)
|
|
self.assertIsNotNone(lat)
|
|
self.assertIn("16", found_addr)
|
|
self.assertIn("Rue de la Loi", found_addr)
|
|
self.assertGreater(score, 60)
|
|
|
|
def test_geocode_internal_street_only(self):
|
|
"""Test geocoding with street name only (no house number)"""
|
|
lon, lat, found_addr, score = geocode_internal("Rue de la Loi Bruxelles", threshold=60)
|
|
self.assertIsNotNone(lon)
|
|
self.assertIsNotNone(lat)
|
|
self.assertIn("Rue de la Loi", found_addr)
|
|
self.assertGreater(score, 60)
|
|
|
|
def test_geocode_internal_below_threshold(self):
|
|
"""Test that low-scoring matches are rejected"""
|
|
# Use a very high threshold that won't be met
|
|
lon, lat, found_addr, score = geocode_internal("16 Rue de la Loi", threshold=99)
|
|
# Should return None if score is below threshold
|
|
# (assuming the score is less than 99)
|
|
if score < 99:
|
|
self.assertIsNone(lon)
|
|
self.assertIsNone(lat)
|
|
|
|
def test_geocode_internal_no_match(self):
|
|
"""Test geocoding with no matching address"""
|
|
lon, lat, found_addr, score = geocode_internal("Nonexistent Street 999", threshold=80)
|
|
self.assertIsNone(lon)
|
|
self.assertIsNone(lat)
|
|
self.assertEqual(score, 0)
|
|
|
|
def test_geocode_internal_empty_address(self):
|
|
"""Test geocoding with empty address"""
|
|
lon, lat, found_addr, score = geocode_internal("", threshold=80)
|
|
self.assertIsNone(lon)
|
|
self.assertIsNone(lat)
|
|
self.assertEqual(score, 0)
|
|
|
|
def test_geocode_internal_fuzzy_match(self):
|
|
"""Test fuzzy matching with typos"""
|
|
# Test with a typo: "Rua" instead of "Rue"
|
|
lon, lat, found_addr, score = geocode_internal("16 Rua de la Loi", threshold=70)
|
|
# Should still find a match with a reasonable score
|
|
if score >= 70:
|
|
self.assertIsNotNone(lon)
|
|
self.assertIsNotNone(lat)
|
|
|
|
def test_geocode_internal_partial_match(self):
|
|
"""Test partial address matching"""
|
|
lon, lat, found_addr, score = geocode_internal("loi 16", threshold=70)
|
|
# Should find a match even with different word order
|
|
if score >= 70:
|
|
self.assertIsNotNone(lon)
|
|
self.assertIsNotNone(lat)
|
|
|
|
|
|
class SearchLimitTestCase(TestCase):
|
|
"""Test search limit functionality"""
|
|
|
|
def setUp(self):
|
|
"""Create multiple test addresses"""
|
|
self.municipality = Municipality.objects.create(
|
|
fid="1000",
|
|
name_fr="Bruxelles",
|
|
name_nl="Brussel",
|
|
niscode="21004"
|
|
)
|
|
|
|
self.street = AdrStreet.objects.create(
|
|
fid="street_001",
|
|
name_fr="Rue de la Loi",
|
|
name_nl="Wetstraat",
|
|
full_street_fr="Rue de la Loi",
|
|
full_street_nl="Wetstraat",
|
|
municipality=self.municipality,
|
|
lon=4.3676,
|
|
lat=50.8467,
|
|
centroid=Point(4.3676, 50.8467, srid=3812)
|
|
)
|
|
|
|
# Create 5 address points
|
|
for i in range(1, 6):
|
|
number = str(i * 10)
|
|
AdrPoint.objects.create(
|
|
fid=f"point_00{i}",
|
|
number=number,
|
|
street_name=self.street,
|
|
municipality=self.municipality,
|
|
zipcode="1000",
|
|
full_address_fr=f"{number} Rue de la Loi Bruxelles",
|
|
full_address_nl=f"{number} Wetstraat Brussel",
|
|
lon=4.3676 + i * 0.001,
|
|
lat=50.8467 + i * 0.001,
|
|
geom=Point(4.3676 + i * 0.001, 50.8467 + i * 0.001, srid=3812)
|
|
)
|
|
|
|
def test_search_respects_limit(self):
|
|
"""Test that search limit is respected"""
|
|
results = search_address_points("Rue de la Loi", limit=3)
|
|
self.assertLessEqual(len(results), 3)
|
|
|
|
def test_search_returns_best_matches(self):
|
|
"""Test that search returns best matches first"""
|
|
results = search_address_points("Rue de la Loi 10", limit=5)
|
|
if len(results) > 1:
|
|
# Scores should be in descending order
|
|
scores = [score for _, score in results]
|
|
self.assertEqual(scores, sorted(scores, reverse=True))
|