Cache provisional weekday timetables

This commit is contained in:
Edward Betts 2026-05-21 11:31:17 +01:00
parent 378d2484d0
commit bc7cb9cffa
6 changed files with 686 additions and 58 deletions

View file

@ -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