Spaces:
Runtime error
Runtime error
Commit Β·
1fec3cb
1
Parent(s): 291da19
Test: export.build_kml coordinate invariants (regression for coord-less KML)
Browse filesFive new checks in test_pipeline.py guard against places_map.kml being
generated without <Point><coordinates> elements when geocoded rows exist:
build_kml writes coords, omits Point for ungeocoded rows, correct pinned
count in mixed batches, full exportβimport round-trip, and export.run()
end-to-end via a temp CSV.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tests/test_pipeline.py +99 -0
tests/test_pipeline.py
CHANGED
|
@@ -334,6 +334,100 @@ https://www.instagram.com/p/ABC123/]]></description>
|
|
| 334 |
check("KML import: flat Placemark name", flat_rows[0]["name"] == "Mystery Place")
|
| 335 |
|
| 336 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
# ββ FOOD_CATEGORIES sync invariant ββββββββββββββββββββββββββββββββββββββββββββ
|
| 338 |
|
| 339 |
def test_food_categories_sync():
|
|
@@ -674,6 +768,11 @@ test_parse_response()
|
|
| 674 |
test_parse_responses_dedup()
|
| 675 |
test_parse_import_csv()
|
| 676 |
test_parse_import_kml()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
test_food_categories_sync()
|
| 678 |
test_parse_batch_response_none_guard()
|
| 679 |
test_parse_batch_response_empty_string()
|
|
|
|
| 334 |
check("KML import: flat Placemark name", flat_rows[0]["name"] == "Mystery Place")
|
| 335 |
|
| 336 |
|
| 337 |
+
# ββ export.build_kml βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 338 |
+
|
| 339 |
+
def _row(**overrides) -> dict:
|
| 340 |
+
"""Minimal valid row dict for export tests."""
|
| 341 |
+
base = {
|
| 342 |
+
"name": "Ichiran", "city": "Tokyo", "state": "Tokyo", "country": "Japan",
|
| 343 |
+
"address": "UNKNOWN", "category": "Restaurant", "cuisine": "Ramen",
|
| 344 |
+
"price_range": "$$", "highlight": "Best ramen", "occasion": "UNKNOWN",
|
| 345 |
+
"lat": "35.6595000", "lng": "139.7005000", "creator": "ramenking",
|
| 346 |
+
"saved_at": "2024-03-15", "instagram_url": "https://instagram.com/p/abc",
|
| 347 |
+
"status": "unvisited",
|
| 348 |
+
}
|
| 349 |
+
base.update(overrides)
|
| 350 |
+
return base
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def test_build_kml_writes_coordinates():
|
| 354 |
+
from pipeline import export as export_mod
|
| 355 |
+
kml = export_mod.build_kml([_row()])
|
| 356 |
+
check("build_kml: <Point> present for geocoded row", "<Point>" in kml)
|
| 357 |
+
check("build_kml: <coordinates> tag present", "<coordinates>" in kml)
|
| 358 |
+
check("build_kml: lng,lat,alt order correct", "139.7005000,35.6595000,0" in kml)
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
def test_build_kml_omits_point_for_no_coords():
|
| 362 |
+
from pipeline import export as export_mod
|
| 363 |
+
kml = export_mod.build_kml([_row(lat="", lng="")])
|
| 364 |
+
check("build_kml: no <Point> for ungeocoded row", "<Point>" not in kml)
|
| 365 |
+
check("build_kml: no <coordinates> for ungeocoded row", "<coordinates>" not in kml)
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
def test_build_kml_mixed_pinned_count():
|
| 369 |
+
"""Only geocoded rows emit <Point>; ungeocoded rows are included but pin-free."""
|
| 370 |
+
from pipeline import export as export_mod
|
| 371 |
+
rows = [
|
| 372 |
+
_row(name="Geocoded"),
|
| 373 |
+
_row(name="Not Geocoded", lat="", lng=""),
|
| 374 |
+
_row(name="Also Geocoded", lat="34.6937", lng="135.5023"),
|
| 375 |
+
]
|
| 376 |
+
kml = export_mod.build_kml(rows)
|
| 377 |
+
import xml.etree.ElementTree as ET
|
| 378 |
+
root = ET.fromstring(kml)
|
| 379 |
+
ns = "http://www.opengis.net/kml/2.2"
|
| 380 |
+
coords = root.findall(f".//{{{ns}}}coordinates")
|
| 381 |
+
placemarks = root.findall(f".//{{{ns}}}Placemark")
|
| 382 |
+
check("build_kml: 3 placemarks total", len(placemarks) == 3)
|
| 383 |
+
check("build_kml: exactly 2 <coordinates> for 2 geocoded rows", len(coords) == 2)
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def test_build_kml_coordinates_roundtrip():
|
| 387 |
+
"""export.build_kml β _parse_import_kml preserves lat/lng exactly."""
|
| 388 |
+
from pipeline import export as export_mod
|
| 389 |
+
from web.app import _parse_import_kml
|
| 390 |
+
rows_in = [
|
| 391 |
+
_row(name="Ichiran"),
|
| 392 |
+
_row(name="No Coord Cafe", lat="", lng=""),
|
| 393 |
+
]
|
| 394 |
+
kml_bytes = export_mod.build_kml(rows_in).encode("utf-8")
|
| 395 |
+
rows_out = _parse_import_kml(kml_bytes)
|
| 396 |
+
geocoded = [r for r in rows_out if r.get("lat") and r.get("lng")]
|
| 397 |
+
check("roundtrip: 1 geocoded row survives", len(geocoded) == 1)
|
| 398 |
+
check("roundtrip: lat preserved", geocoded[0]["lat"] == "35.6595000")
|
| 399 |
+
check("roundtrip: lng preserved", geocoded[0]["lng"] == "139.7005000")
|
| 400 |
+
check("roundtrip: name preserved", geocoded[0]["name"] == "Ichiran")
|
| 401 |
+
check("roundtrip: total row count unchanged", len(rows_out) == 2)
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
def test_export_run_writes_coordinates():
|
| 405 |
+
"""export.run() reads a CSV with geocoded rows and writes <Point> elements."""
|
| 406 |
+
import csv as _csv, tempfile, xml.etree.ElementTree as ET
|
| 407 |
+
from pipeline import export as export_mod
|
| 408 |
+
from pipeline.extract import FIELDNAMES
|
| 409 |
+
|
| 410 |
+
rows = [_row(name="Ichiran"), _row(name="No Coord", lat="", lng="")]
|
| 411 |
+
|
| 412 |
+
with tempfile.TemporaryDirectory() as d:
|
| 413 |
+
csv_path = f"{d}/places_full.csv"
|
| 414 |
+
kml_path = f"{d}/places_map.kml"
|
| 415 |
+
with open(csv_path, "w", newline="", encoding="utf-8") as f:
|
| 416 |
+
w = _csv.DictWriter(f, fieldnames=FIELDNAMES)
|
| 417 |
+
w.writeheader()
|
| 418 |
+
w.writerows(rows)
|
| 419 |
+
|
| 420 |
+
export_mod.run(csv_path, kml_path)
|
| 421 |
+
|
| 422 |
+
kml_content = open(kml_path, encoding="utf-8").read()
|
| 423 |
+
root = ET.fromstring(kml_content)
|
| 424 |
+
ns = "http://www.opengis.net/kml/2.2"
|
| 425 |
+
coords = root.findall(f".//{{{ns}}}coordinates")
|
| 426 |
+
check("export.run: KML file written", len(kml_content) > 0)
|
| 427 |
+
check("export.run: exactly 1 <coordinates> for 1 geocoded row", len(coords) == 1)
|
| 428 |
+
check("export.run: coordinate value correct", "139.7005" in (coords[0].text or ""))
|
| 429 |
+
|
| 430 |
+
|
| 431 |
# ββ FOOD_CATEGORIES sync invariant ββββββββββββββββββββββββββββββββββββββββββββ
|
| 432 |
|
| 433 |
def test_food_categories_sync():
|
|
|
|
| 768 |
test_parse_responses_dedup()
|
| 769 |
test_parse_import_csv()
|
| 770 |
test_parse_import_kml()
|
| 771 |
+
test_build_kml_writes_coordinates()
|
| 772 |
+
test_build_kml_omits_point_for_no_coords()
|
| 773 |
+
test_build_kml_mixed_pinned_count()
|
| 774 |
+
test_build_kml_coordinates_roundtrip()
|
| 775 |
+
test_export_run_writes_coordinates()
|
| 776 |
test_food_categories_sync()
|
| 777 |
test_parse_batch_response_none_guard()
|
| 778 |
test_parse_batch_response_empty_string()
|