Cache provisional weekday timetables
This commit is contained in:
parent
378d2484d0
commit
bc7cb9cffa
6 changed files with 686 additions and 58 deletions
|
|
@ -79,6 +79,12 @@ def test_search_redirects_return_with_return_date():
|
|||
)
|
||||
|
||||
|
||||
def test_nr_weekday_cache_key_includes_timetable_period():
|
||||
key = app_module._nr_weekday_cache_key("to_paddington", "BRI", "2026-06-22")
|
||||
|
||||
assert key == "weekday_rtt_to_paddington_BRI_2026-05-17_2026-12-12_mon"
|
||||
|
||||
|
||||
def test_results_shows_same_day_destination_switcher(monkeypatch):
|
||||
_stub_data(monkeypatch)
|
||||
client = _client()
|
||||
|
|
@ -94,6 +100,140 @@ def test_results_shows_same_day_destination_switcher(monkeypatch):
|
|||
assert 'ES 9014' in html
|
||||
|
||||
|
||||
def test_results_can_render_from_weekday_timetable_cache(monkeypatch):
|
||||
travel_date = "2026-06-22"
|
||||
cache = {
|
||||
app_module._nr_weekday_cache_key("to_paddington", "BRI", travel_date): [
|
||||
{'depart_bristol': '07:00', 'arrive_paddington': '08:45', 'headcode': '1A23'},
|
||||
],
|
||||
app_module._eurostar_weekday_cache_key("outbound", travel_date, "Paris Gare du Nord"): [
|
||||
{'depart_st_pancras': '10:01', 'arrive_destination': '13:34',
|
||||
'destination': 'Paris Gare du Nord', 'train_number': 'ES 9014'},
|
||||
],
|
||||
}
|
||||
monkeypatch.setattr(app_module, 'get_cached', lambda key, ttl=None: cache.get(key))
|
||||
monkeypatch.setattr(app_module, 'set_cached', lambda key, data: None)
|
||||
monkeypatch.setattr(
|
||||
app_module.rtt_scraper,
|
||||
'fetch',
|
||||
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("should use weekday NR cache")),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.eurostar_scraper,
|
||||
'fetch',
|
||||
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("should use weekday Eurostar cache")),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.gwr_fares_scraper,
|
||||
'fetch',
|
||||
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("should stream prices later")),
|
||||
)
|
||||
client = _client()
|
||||
|
||||
resp = client.get('/results/BRI/paris/2026-06-22?min_connection=60&max_connection=120')
|
||||
html = resp.get_data(as_text=True)
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert '07:00 → 08:45' in html
|
||||
assert '10:01 → 13:34' in html
|
||||
assert 'ES 9014' in html
|
||||
assert 'checking exact timetable' in html
|
||||
assert '/api/results_refresh/BRI/paris/2026-06-22' in html
|
||||
|
||||
|
||||
def test_results_refresh_reloads_when_exact_timetable_differs(monkeypatch):
|
||||
travel_date = "2026-06-22"
|
||||
cache = {
|
||||
app_module._nr_weekday_cache_key("to_paddington", "BRI", travel_date): [
|
||||
{'depart_bristol': '07:00', 'arrive_paddington': '08:45', 'headcode': '1A23'},
|
||||
],
|
||||
app_module._eurostar_weekday_cache_key("outbound", travel_date, "Paris Gare du Nord"): [
|
||||
{'depart_st_pancras': '10:01', 'arrive_destination': '13:34',
|
||||
'destination': 'Paris Gare du Nord', 'train_number': 'ES 9014'},
|
||||
],
|
||||
}
|
||||
monkeypatch.setattr(app_module, 'get_cached', lambda key, ttl=None: cache.get(key))
|
||||
monkeypatch.setattr(app_module, 'set_cached', lambda key, data: cache.__setitem__(key, data))
|
||||
monkeypatch.setattr(
|
||||
app_module.rtt_scraper,
|
||||
'fetch',
|
||||
lambda travel_date, user_agent, station_crs='BRI': [
|
||||
{'depart_bristol': '07:05', 'arrive_paddington': '08:50', 'headcode': '1A24'},
|
||||
],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.eurostar_scraper,
|
||||
'fetch',
|
||||
lambda destination, travel_date: [
|
||||
{'depart_st_pancras': '10:01', 'arrive_destination': '13:34',
|
||||
'destination': destination, 'train_number': 'ES 9014',
|
||||
'price': 59, 'seats': 42, 'plus_price': 89, 'plus_seats': 5},
|
||||
],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.gwr_fares_scraper,
|
||||
'fetch',
|
||||
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("reload should stop before fare fetch")),
|
||||
)
|
||||
client = _client()
|
||||
|
||||
resp = client.get('/api/results_refresh/BRI/paris/2026-06-22')
|
||||
body = resp.get_data(as_text=True)
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert '"type": "reload"' in body
|
||||
assert cache[app_module._nr_exact_cache_key("to_paddington", "BRI", travel_date)][0]['depart_bristol'] == '07:05'
|
||||
assert cache[app_module._nr_weekday_cache_key("to_paddington", "BRI", travel_date)][0]['depart_bristol'] == '07:05'
|
||||
|
||||
|
||||
def test_results_refresh_streams_prices_when_timetable_matches(monkeypatch):
|
||||
travel_date = "2026-06-22"
|
||||
nr_timetable = [
|
||||
{'depart_bristol': '07:00', 'arrive_paddington': '08:45', 'headcode': '1A23'},
|
||||
]
|
||||
es_timetable = [
|
||||
{'depart_st_pancras': '10:01', 'arrive_destination': '13:34',
|
||||
'destination': 'Paris Gare du Nord', 'train_number': 'ES 9014'},
|
||||
]
|
||||
cache = {
|
||||
app_module._nr_weekday_cache_key("to_paddington", "BRI", travel_date): nr_timetable,
|
||||
app_module._eurostar_weekday_cache_key("outbound", travel_date, "Paris Gare du Nord"): es_timetable,
|
||||
}
|
||||
monkeypatch.setattr(app_module, 'get_cached', lambda key, ttl=None: cache.get(key))
|
||||
monkeypatch.setattr(app_module, 'set_cached', lambda key, data: cache.__setitem__(key, data))
|
||||
monkeypatch.setattr(
|
||||
app_module.rtt_scraper,
|
||||
'fetch',
|
||||
lambda travel_date, user_agent, station_crs='BRI': nr_timetable,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.eurostar_scraper,
|
||||
'fetch',
|
||||
lambda destination, travel_date: [
|
||||
{**es_timetable[0], 'price': 59, 'seats': 42, 'plus_price': 89, 'plus_seats': 5},
|
||||
],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.gwr_fares_scraper,
|
||||
'fetch',
|
||||
lambda station_crs, travel_date: {
|
||||
'07:00': {'ticket': 'Anytime Day Single', 'price': 138.70, 'code': 'SDS'},
|
||||
},
|
||||
)
|
||||
client = _client()
|
||||
|
||||
resp = client.get('/api/results_refresh/BRI/paris/2026-06-22')
|
||||
body = resp.get_data(as_text=True)
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert '"type": "reload"' not in body
|
||||
assert '"type": "eurostar_prices"' in body
|
||||
assert '"main:10:01"' in body
|
||||
assert '"price": 59' in body
|
||||
assert '"type": "walkon_fares"' in body
|
||||
assert '"price": 138.7' in body
|
||||
|
||||
|
||||
def test_results_progressive_shell_loads_without_scraping(monkeypatch):
|
||||
def fail_fetch(*args, **kwargs):
|
||||
raise AssertionError("progressive shell should not fetch data")
|
||||
|
|
@ -111,6 +251,23 @@ def test_results_progressive_shell_loads_without_scraping(monkeypatch):
|
|||
assert 'render=full' in html
|
||||
|
||||
|
||||
def test_return_progressive_shell_formats_return_date(monkeypatch):
|
||||
def fail_fetch(*args, **kwargs):
|
||||
raise AssertionError("progressive shell should not fetch data")
|
||||
|
||||
monkeypatch.setattr(app_module.rtt_scraper, 'fetch', fail_fetch)
|
||||
monkeypatch.setattr(app_module.eurostar_scraper, 'fetch_return', fail_fetch)
|
||||
monkeypatch.setattr(app_module.gwr_fares_scraper, 'fetch', fail_fetch)
|
||||
client = _client()
|
||||
|
||||
resp = client.get('/results/BRI/paris/2026-04-10/return/2026-04-17?progressive=1')
|
||||
html = resp.get_data(as_text=True)
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert 'Friday 10 April 2026 to Friday 17 April 2026' in html
|
||||
assert 'to 2026-04-17' not in html
|
||||
|
||||
|
||||
def test_results_title_and_social_meta_include_destination(monkeypatch):
|
||||
_stub_data(monkeypatch)
|
||||
client = _client()
|
||||
|
|
@ -221,6 +378,42 @@ def test_results_shows_eurostar_price_and_total(monkeypatch):
|
|||
assert 'data-es-std="59"' in html
|
||||
|
||||
|
||||
def test_results_uses_unique_row_keys_for_same_eurostar(monkeypatch):
|
||||
monkeypatch.setattr(app_module, 'get_cached', lambda key, ttl=None: None)
|
||||
monkeypatch.setattr(app_module, 'set_cached', lambda key, data: None)
|
||||
monkeypatch.setattr(app_module.gwr_fares_scraper, 'fetch', lambda s, d: {
|
||||
'07:00': {'ticket': 'Anytime Day Single', 'price': 138.70, 'code': 'SDS'},
|
||||
'07:30': {'ticket': 'Anytime Day Single', 'price': 138.70, 'code': 'SDS'},
|
||||
})
|
||||
monkeypatch.setattr(
|
||||
app_module.rtt_scraper,
|
||||
'fetch',
|
||||
lambda travel_date, user_agent, station_crs='BRI': [
|
||||
{'depart_bristol': '07:00', 'arrive_paddington': '08:30', 'headcode': '1A01'},
|
||||
{'depart_bristol': '07:30', 'arrive_paddington': '09:00', 'headcode': '1A02'},
|
||||
],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
app_module.eurostar_scraper,
|
||||
'fetch',
|
||||
lambda destination, travel_date: [
|
||||
{'depart_st_pancras': '10:01', 'arrive_destination': '13:34',
|
||||
'destination': destination, 'train_number': 'ES 9014',
|
||||
'price': 59, 'seats': 42, 'plus_price': 89, 'plus_seats': 5},
|
||||
],
|
||||
)
|
||||
client = _client()
|
||||
|
||||
resp = client.get('/results/BRI/paris/2026-04-10?min_connection=60&max_connection=120')
|
||||
html = resp.get_data(as_text=True)
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert 'data-row-key="main:07:00:10:01"' in html
|
||||
assert 'data-row-key="main:07:30:10:01"' in html
|
||||
assert '"main:07:00:10:01"' in html
|
||||
assert '"main:07:30:10:01"' in html
|
||||
|
||||
|
||||
def test_results_shows_unreachable_service_when_no_trips(monkeypatch):
|
||||
# Only one Eurostar at 09:30; GWR arrives 08:45 with min=60 → unreachable.
|
||||
# No trips at all, so the unreachable service is shown as "Too early".
|
||||
|
|
@ -423,8 +616,9 @@ def test_results_return_renders_outbound_and_inbound_tables(monkeypatch):
|
|||
assert resp.status_code == 200
|
||||
assert 'Outbound: Bristol Temple Meads → Paris Gare du Nord' in html
|
||||
assert 'Return: Paris Gare du Nord → Bristol Temple Meads' in html
|
||||
assert '/results/BRI/paris/2026-04-09/return/2026-04-17' in html
|
||||
assert '/results/BRI/paris/2026-04-11/return/2026-04-17' in html
|
||||
assert 'Friday 10 April 2026 to Friday 17 April 2026' in html
|
||||
assert '/results/BRI/paris/2026-04-09/return/2026-04-16' in html
|
||||
assert '/results/BRI/paris/2026-04-11/return/2026-04-18' in html
|
||||
assert "/results/BRI/paris/2026-04-10/return/2026-04-17" in html
|
||||
assert 'journey_type=return' not in html
|
||||
assert 'return_date=2026-04-17' not in html
|
||||
|
|
|
|||
|
|
@ -40,3 +40,10 @@ def test_get_cached_expired_returns_none(tmp_cache):
|
|||
old = time.time() - 25 * 3600 # 25 hours ago
|
||||
os.utime(path, (old, old))
|
||||
assert get_cached('k', ttl=24 * 3600) is None
|
||||
|
||||
|
||||
def test_get_cached_invalid_json_returns_none(tmp_cache):
|
||||
path = tmp_cache / 'broken.json'
|
||||
path.write_text('{"not": "finished"')
|
||||
|
||||
assert get_cached('broken') is None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue