bristol-eurostar/tests/test_app.py
Edward Betts 05eec29b7d Show Eurostar seat availability and no-prices notice
fetch_prices now returns {'price': ..., 'seats': ...} per departure.
Seat count (labelled "N at this price") is shown below the fare — it
reflects price-band depth rather than total remaining seats. A yellow
notice is shown when the API returns journeys but all prices are null
(tickets not yet on sale).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:12:54 +01:00

271 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import app as app_module
def _client():
app_module.app.config['TESTING'] = True
return app_module.app.test_client()
def _stub_data(monkeypatch, prices=None):
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.rtt_scraper,
'fetch',
lambda travel_date, user_agent: [
{'depart_bristol': '07:00', 'arrive_paddington': '08:45', 'headcode': '1A23'},
],
)
monkeypatch.setattr(
app_module.eurostar_scraper,
'fetch',
lambda destination, travel_date, user_agent: [
{
'depart_st_pancras': '10:01',
'arrive_destination': '13:34',
'destination': destination,
'train_number': 'ES 9014',
},
],
)
monkeypatch.setattr(
app_module.eurostar_scraper,
'timetable_url',
lambda destination: f'https://example.test/{destination.lower().replace(" ", "-")}',
)
_prices = prices if prices is not None else {}
monkeypatch.setattr(app_module, 'fetch_eurostar_prices', lambda dest, date: _prices)
def test_index_shows_fixed_departure_and_destination_radios():
client = _client()
resp = client.get('/')
html = resp.get_data(as_text=True)
assert resp.status_code == 200
assert 'Departure point' in html
assert 'Bristol Temple Meads' in html
assert html.count('type="radio"') == len(app_module.DESTINATIONS)
assert 'destination-rotterdam' in html
def test_search_redirects_to_results_with_selected_params():
client = _client()
resp = client.get('/search?destination=rotterdam&travel_date=2026-04-10&min_connection=60&max_connection=120')
assert resp.status_code == 302
assert resp.headers['Location'].endswith(
'/results/rotterdam/2026-04-10?min_connection=60&max_connection=120'
)
def test_results_shows_same_day_destination_switcher(monkeypatch):
_stub_data(monkeypatch)
client = _client()
resp = client.get('/results/paris/2026-04-10?min_connection=60&max_connection=120')
html = resp.get_data(as_text=True)
assert resp.status_code == 200
assert 'Switch destination for Friday 10 April 2026' in html
assert '<span class="chip-current">Paris Gare du Nord</span>' in html
assert '/results/brussels/2026-04-10?min_connection=60&amp;max_connection=120' in html
assert '/results/rotterdam/2026-04-10?min_connection=60&amp;max_connection=120' in html
assert 'ES 9014' in html
def test_results_title_and_social_meta_include_destination(monkeypatch):
_stub_data(monkeypatch)
client = _client()
resp = client.get('/results/lille/2026-04-10?min_connection=60&max_connection=120')
html = resp.get_data(as_text=True)
assert resp.status_code == 200
assert '<title>Bristol to Lille Europe via Eurostar</title>' in html
assert '<meta property="og:title" content="Bristol to Lille Europe via Eurostar">' in html
assert (
'<meta property="og:description" content="Train options from Bristol Temple Meads '
'to Lille Europe on Friday 10 April 2026 via Paddington, St Pancras, and Eurostar.">'
) in html
assert '<meta property="og:url" content="http://localhost/results/lille/2026-04-10?min_connection=60&amp;max_connection=120">' in html
def test_results_marks_trips_within_five_minutes_of_fastest_and_slowest(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, 'fetch_eurostar_prices', lambda dest, date: {})
monkeypatch.setattr(
app_module.rtt_scraper,
'fetch',
lambda travel_date, user_agent: [
{'depart_bristol': '07:00', 'arrive_paddington': '08:30', 'headcode': '1A01'},
{'depart_bristol': '07:05', 'arrive_paddington': '08:36', 'headcode': '1A02'},
{'depart_bristol': '07:10', 'arrive_paddington': '08:46', 'headcode': '1A03'},
{'depart_bristol': '07:15', 'arrive_paddington': '08:56', 'headcode': '1A04'},
{'depart_bristol': '07:20', 'arrive_paddington': '09:06', 'headcode': '1A05'},
],
)
monkeypatch.setattr(
app_module.eurostar_scraper,
'fetch',
lambda destination, travel_date, user_agent: [
{
'depart_st_pancras': '09:30',
'arrive_destination': '11:50',
'destination': destination,
'train_number': 'ES 1001',
},
{
'depart_st_pancras': '09:40',
'arrive_destination': '12:00',
'destination': destination,
'train_number': 'ES 1002',
},
{
'depart_st_pancras': '09:50',
'arrive_destination': '12:20',
'destination': destination,
'train_number': 'ES 1003',
},
{
'depart_st_pancras': '10:00',
'arrive_destination': '12:35',
'destination': destination,
'train_number': 'ES 1004',
},
{
'depart_st_pancras': '10:10',
'arrive_destination': '12:45',
'destination': destination,
'train_number': 'ES 1005',
},
],
)
monkeypatch.setattr(
app_module.eurostar_scraper,
'timetable_url',
lambda destination: f'https://example.test/{destination.lower().replace(" ", "-")}',
)
client = _client()
resp = client.get('/results/paris/2026-04-10?min_connection=60&max_connection=120')
html = resp.get_data(as_text=True)
assert resp.status_code == 200
assert html.count('title="Fastest journey"') == 2
assert html.count('title="Slowest journey"') == 2
assert '4h 50m ⚡' in html
assert '4h 55m ⚡' in html
assert '5h 20m 🐢' in html
assert '5h 25m 🐢' in html
assert '5h 10m ⚡' not in html
assert '5h 10m 🐢' not in html
def test_results_shows_unreachable_morning_eurostar_services(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, 'fetch_eurostar_prices', lambda dest, date: {})
monkeypatch.setattr(
app_module.rtt_scraper,
'fetch',
lambda travel_date, user_agent: [
{'depart_bristol': '07:00', 'arrive_paddington': '08:45', 'headcode': '1A23'},
],
)
monkeypatch.setattr(
app_module.eurostar_scraper,
'fetch',
lambda destination, travel_date, user_agent: [
{
'depart_st_pancras': '09:30',
'arrive_destination': '12:00',
'destination': destination,
'train_number': 'ES 9001',
},
{
'depart_st_pancras': '10:15',
'arrive_destination': '13:40',
'destination': destination,
'train_number': 'ES 9002',
},
{
'depart_st_pancras': '12:30',
'arrive_destination': '15:55',
'destination': destination,
'train_number': 'ES 9003',
},
],
)
monkeypatch.setattr(
app_module.eurostar_scraper,
'timetable_url',
lambda destination: f'https://example.test/{destination.lower().replace(" ", "-")}',
)
client = _client()
resp = client.get('/results/paris/2026-04-10?min_connection=60&max_connection=120')
html = resp.get_data(as_text=True)
assert resp.status_code == 200
assert '2 Eurostar services unavailable from Bristol' in html
assert '09:30' in html
assert 'ES 9001' in html
assert 'Too early' in html
assert html.index('09:30') < html.index('10:15')
def test_results_shows_eurostar_price_and_total(monkeypatch):
# 07:00 on Friday 2026-04-10 → Anytime £138.70 (weekday, 05:0508:25 window)
_stub_data(monkeypatch, prices={'10:01': {'price': 59, 'seats': 42}})
client = _client()
resp = client.get('/results/paris/2026-04-10?min_connection=60&max_connection=120')
html = resp.get_data(as_text=True)
assert resp.status_code == 200
assert '£59' in html # Eurostar Standard price
assert '£197.70' in html # Anytime £138.70 + ES £59
def test_results_can_show_only_unreachable_morning_services(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, 'fetch_eurostar_prices', lambda dest, date: {})
monkeypatch.setattr(
app_module.rtt_scraper,
'fetch',
lambda travel_date, user_agent: [
{'depart_bristol': '07:00', 'arrive_paddington': '08:45', 'headcode': '1A23'},
],
)
monkeypatch.setattr(
app_module.eurostar_scraper,
'fetch',
lambda destination, travel_date, user_agent: [
{
'depart_st_pancras': '09:30',
'arrive_destination': '12:00',
'destination': destination,
'train_number': 'ES 9001',
},
],
)
monkeypatch.setattr(
app_module.eurostar_scraper,
'timetable_url',
lambda destination: f'https://example.test/{destination.lower().replace(" ", "-")}',
)
client = _client()
resp = client.get('/results/paris/2026-04-10?min_connection=60&max_connection=120')
html = resp.get_data(as_text=True)
assert resp.status_code == 200
assert 'No valid journeys found.' not in html
assert '1 Eurostar service unavailable from Bristol' in html
assert '09:30' in html
assert 'Too early' in html