"""Geocode, festivals, events, advisories tools with mocked HTTP."""
import requests as requests_lib
import pytest
from app.tools import advisories, events, festivals, geocode
# ---- geocode ----------------------------------------------------------------
def test_geocode_returns_best_match(monkeypatch, fake_response, fake_keys):
captured = {"urls": [], "params": []}
def fake_get(url, params=None, timeout=None):
captured["urls"].append(url)
captured["params"].append(params)
return fake_response(
{
"results": [
{
"position": {"lat": 26.8512, "lon": 80.9462},
"address": {"freeformAddress": "Hazratganj, Lucknow"},
"poi": {"name": "Hazratganj Market"},
}
]
}
)
monkeypatch.setattr(geocode.requests, "get", fake_get)
result = geocode.geocode_place("Hazratganj")
assert result["lat"] == 26.8512
assert result["name"] == "Hazratganj Market"
assert any("Lucknow" in url for url in captured["urls"]) # query is city-biased
assert captured["params"][0]["countrySet"] == "IN"
def test_geocode_no_results_raises(monkeypatch, fake_response, fake_keys):
monkeypatch.setattr(geocode.requests, "get", lambda *a, **k: fake_response({"results": []}))
with pytest.raises(RuntimeError, match="No location found"):
geocode.geocode_place("Nonexistent Place XYZ")
def test_geocode_prefers_poi_over_city_geography(monkeypatch, fake_response, fake_keys):
"""'iiit lucknow' must resolve to the campus POI, not Lucknow city center."""
payload = {
"results": [
{ # the city itself — this used to win and wreck all ETAs
"type": "Geography",
"position": {"lat": 26.8467, "lon": 80.9462},
"address": {"freeformAddress": "Lucknow, Uttar Pradesh"},
},
{
"type": "POI",
"position": {"lat": 26.7991, "lon": 81.0220},
"address": {"freeformAddress": "IIIT Lucknow, Ahmamau"},
"poi": {"name": "Indian Institute of Information Technology Lucknow"},
},
]
}
monkeypatch.setattr(geocode.requests, "get", lambda *a, **k: fake_response(payload))
result = geocode.geocode_place("iiit lucknow")
assert result["lat"] == 26.7991
assert "Information Technology" in result["name"]
def test_geocode_falls_back_to_geography_when_nothing_else(monkeypatch, fake_response, fake_keys):
payload = {
"results": [
{
"type": "Geography",
"position": {"lat": 26.8467, "lon": 80.9462},
"address": {"freeformAddress": "Lucknow, Uttar Pradesh"},
}
]
}
monkeypatch.setattr(geocode.requests, "get", lambda *a, **k: fake_response(payload))
result = geocode.geocode_place("Lucknow")
assert result["lat"] == 26.8467
def test_geocode_rejects_results_outside_lucknow(monkeypatch, fake_response, fake_keys):
payload = {
"results": [
{ # a match in Delhi — must not be accepted
"type": "POI",
"position": {"lat": 28.6139, "lon": 77.2090},
"address": {"freeformAddress": "Connaught Place, Delhi"},
"poi": {"name": "Some Place Delhi"},
}
]
}
monkeypatch.setattr(geocode.requests, "get", lambda *a, **k: fake_response(payload))
with pytest.raises(RuntimeError, match="No location found"):
geocode.geocode_place("Some Place")
def test_geocode_no_double_city_suffix(monkeypatch, fake_response, fake_keys):
captured = {"urls": []}
def fake_get(url, params=None, timeout=None):
captured["urls"].append(url)
return fake_response(
{
"results": [
{
"type": "POI",
"position": {"lat": 26.80, "lon": 81.02},
"address": {"freeformAddress": "IIIT Lucknow"},
"poi": {"name": "IIIT Lucknow"},
}
]
}
)
monkeypatch.setattr(geocode.requests, "get", fake_get)
geocode.geocode_place("iiit lucknow")
# Input already contains 'lucknow' -> the city suffix must NOT be appended
assert "Uttar%20Pradesh" not in captured["urls"][0]
# ---- festivals ----------------------------------------------------------------
def test_festivals_merges_calendarific_and_curated(monkeypatch, fake_response, fake_keys):
calendarific_payload = {
"meta": {"code": 200},
"response": {
"holidays": [
{
"name": "Some National Holiday",
"description": "A big holiday",
"date": {"iso": "2026-06-09"},
"type": ["National holiday"],
}
]
},
}
monkeypatch.setattr(
festivals.requests, "get", lambda *a, **k: fake_response(calendarific_payload)
)
# 2026-06-09 is a Bada Mangal Tuesday -> curated event must appear too
result = festivals.get_festivals("2026-06-09")
names = [festival["name"] for festival in result["festivals"]]
assert "Some National Holiday" in names
assert "Bada Mangal" in names
assert result["impact"] == 2 # very_high from Bada Mangal
def test_festivals_survives_api_failure(monkeypatch, fake_keys):
def boom(*args, **kwargs):
raise requests_lib.ConnectionError("network down")
monkeypatch.setattr(festivals.requests, "get", boom)
result = festivals.get_festivals("2026-03-02") # no curated events that day
assert result["festivals"] == []
assert result["impact"] == 0
# ---- events ----------------------------------------------------------------
def test_events_classifies_stadium_as_high_impact(monkeypatch, fake_response, fake_keys):
payload = {
"_embedded": {
"events": [
{
"name": "IPL Match",
"dates": {"start": {"localTime": "19:30:00"}},
"_embedded": {"venues": [{"name": "Ekana Cricket Stadium"}]},
}
]
}
}
monkeypatch.setattr(events.requests, "get", lambda *a, **k: fake_response(payload))
result = events.get_events("2026-06-12")
assert result["impact"] == 2
assert result["events"][0]["traffic_impact"] == "high"
def test_events_empty_when_api_fails(monkeypatch, fake_keys):
def boom(*args, **kwargs):
raise requests_lib.Timeout("slow")
monkeypatch.setattr(events.requests, "get", boom)
result = events.get_events("2026-06-12")
assert result == {"events": [], "impact": 0}
def test_events_empty_without_key(monkeypatch):
from app import config
monkeypatch.setattr(config, "ticketmaster_key", lambda: None)
result = events.get_events("2026-06-12")
assert result == {"events": [], "impact": 0}
# ---- advisories ----------------------------------------------------------------
def test_advisories_parses_relevant_results(monkeypatch, fake_response):
html = """
Lucknow traffic police announces route diversion for procession
Roads near Chowk will remain closed on Friday evening...
Weather in Mumbai today
Sunny skies expected across the city
"""
monkeypatch.setattr(
advisories.requests, "post", lambda *a, **k: fake_response(text=html)
)
result = advisories.get_police_advisories()
assert result["count"] == 1
assert "diversion" in result["advisories"][0]["title"].lower()
assert result["advisories"][0]["title"].startswith("News:")
def test_advisories_searches_past_week_and_unescapes(monkeypatch, fake_response):
captured = {}
def fake_post(url, data=None, headers=None, timeout=None):
captured["data"] = data
html = (
'Lucknow "Bada Mangal" route diversion advisory'
'Roads closed near Aliganj & Hazratganj'
)
return fake_response(text=html)
monkeypatch.setattr(advisories.requests, "post", fake_post)
result = advisories.get_police_advisories()
assert captured["data"]["df"] == "w" # past-week filter is on
assert '"' in result["advisories"][0]["title"] # " unescaped
assert """ not in result["advisories"][0]["title"]
assert "&" not in result["advisories"][0]["detail"]
def test_advisories_empty_on_network_failure(monkeypatch):
def boom(*args, **kwargs):
raise requests_lib.ConnectionError("blocked")
monkeypatch.setattr(advisories.requests, "post", boom)
result = advisories.get_police_advisories()
assert result == {"advisories": [], "count": 0}