File size: 9,109 Bytes
8b96826
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
"""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 = """
    <a class="result__a" href="#">Lucknow traffic police announces route diversion for procession</a>
    <a class="result__snippet" href="#">Roads near Chowk will remain closed on Friday evening...</a>
    <a class="result__a" href="#">Weather in Mumbai today</a>
    <a class="result__snippet" href="#">Sunny skies expected across the city</a>
    """
    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 = (
            '<a class="result__a" href="#">Lucknow &quot;Bada Mangal&quot; route diversion advisory</a>'
            '<a class="result__snippet" href="#">Roads closed near Aliganj &amp; Hazratganj</a>'
        )
        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"]  # &quot; unescaped
    assert "&quot;" not in result["advisories"][0]["title"]
    assert "&amp;" 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}