131 lines
5.2 KiB
Python
131 lines
5.2 KiB
Python
import pytest
|
|
from trip_planner import combine_trips, _fmt_duration
|
|
|
|
DATE = '2026-03-30'
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _fmt_duration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_fmt_duration_hours_and_minutes():
|
|
assert _fmt_duration(95) == '1h 35m'
|
|
|
|
def test_fmt_duration_exact_hours():
|
|
assert _fmt_duration(120) == '2h'
|
|
|
|
def test_fmt_duration_minutes_only():
|
|
assert _fmt_duration(45) == '45m'
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# combine_trips — basic pairing
|
|
# ---------------------------------------------------------------------------
|
|
|
|
GWR_FAST = {'depart_bristol': '07:00', 'arrive_paddington': '08:45'} # 1h 45m
|
|
GWR_SLOW = {'depart_bristol': '07:00', 'arrive_paddington': '09:26'} # 2h 26m — over limit
|
|
|
|
ES_PARIS = {'depart_st_pancras': '10:01', 'arrive_destination': '13:34', 'destination': 'Paris Gare du Nord'}
|
|
ES_EARLY = {'depart_st_pancras': '09:00', 'arrive_destination': '12:00', 'destination': 'Paris Gare du Nord'}
|
|
|
|
|
|
def test_valid_trip_is_returned():
|
|
trips = combine_trips([GWR_FAST], [ES_PARIS], DATE)
|
|
assert len(trips) == 1
|
|
t = trips[0]
|
|
assert t['depart_bristol'] == '07:00'
|
|
assert t['arrive_paddington'] == '08:45'
|
|
assert t['depart_st_pancras'] == '10:01'
|
|
assert t['arrive_destination'] == '13:34'
|
|
assert t['destination'] == 'Paris Gare du Nord'
|
|
|
|
|
|
def test_gwr_too_slow_excluded():
|
|
# 2h 26m GWR journey exceeds MAX_GWR_MINUTES (110)
|
|
trips = combine_trips([GWR_SLOW], [ES_PARIS], DATE)
|
|
assert trips == []
|
|
|
|
|
|
def test_eurostar_too_early_excluded():
|
|
# Eurostar departs before min connection time has elapsed
|
|
trips = combine_trips([GWR_FAST], [ES_EARLY], DATE)
|
|
assert trips == []
|
|
|
|
|
|
def test_no_trains_returns_empty():
|
|
assert combine_trips([], [], DATE) == []
|
|
|
|
def test_no_gwr_returns_empty():
|
|
assert combine_trips([], [ES_PARIS], DATE) == []
|
|
|
|
def test_no_eurostar_returns_empty():
|
|
assert combine_trips([GWR_FAST], [], DATE) == []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Connection window constraints
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_min_connection_enforced():
|
|
# Arrive Paddington 08:45, need 75 min → earliest St Pancras 10:00
|
|
# ES at 09:59 should be excluded, 10:00 should be included
|
|
es_too_close = {'depart_st_pancras': '09:59', 'arrive_destination': '13:00', 'destination': 'Paris Gare du Nord'}
|
|
es_ok = {'depart_st_pancras': '10:00', 'arrive_destination': '13:00', 'destination': 'Paris Gare du Nord'}
|
|
assert combine_trips([GWR_FAST], [es_too_close], DATE, min_connection_minutes=75) == []
|
|
trips = combine_trips([GWR_FAST], [es_ok], DATE, min_connection_minutes=75)
|
|
assert len(trips) == 1
|
|
|
|
|
|
def test_max_connection_enforced():
|
|
# Arrive Paddington 08:45, max 140 min → latest St Pancras 11:05
|
|
es_ok = {'depart_st_pancras': '11:05', 'arrive_destination': '14:00', 'destination': 'Paris Gare du Nord'}
|
|
es_too_late = {'depart_st_pancras': '11:06', 'arrive_destination': '14:00', 'destination': 'Paris Gare du Nord'}
|
|
trips = combine_trips([GWR_FAST], [es_ok], DATE, max_connection_minutes=140)
|
|
assert len(trips) == 1
|
|
assert combine_trips([GWR_FAST], [es_too_late], DATE, max_connection_minutes=140) == []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Only earliest valid Eurostar per GWR departure
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_only_earliest_eurostar_per_gwr():
|
|
es1 = {'depart_st_pancras': '10:01', 'arrive_destination': '13:34', 'destination': 'Paris Gare du Nord'}
|
|
es2 = {'depart_st_pancras': '11:01', 'arrive_destination': '14:34', 'destination': 'Paris Gare du Nord'}
|
|
trips = combine_trips([GWR_FAST], [es1, es2], DATE)
|
|
assert len(trips) == 1
|
|
assert trips[0]['depart_st_pancras'] == '10:01'
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Multiple GWR trains → multiple trips
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_multiple_gwr_trains():
|
|
gwr2 = {'depart_bristol': '08:00', 'arrive_paddington': '09:45'}
|
|
es = {'depart_st_pancras': '11:01', 'arrive_destination': '14:34', 'destination': 'Paris Gare du Nord'}
|
|
trips = combine_trips([GWR_FAST, gwr2], [es], DATE, max_connection_minutes=140)
|
|
assert len(trips) == 2
|
|
assert trips[0]['depart_bristol'] == '07:00'
|
|
assert trips[1]['depart_bristol'] == '08:00'
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Duration fields
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_gwr_duration_in_trip():
|
|
trips = combine_trips([GWR_FAST], [ES_PARIS], DATE)
|
|
assert trips[0]['gwr_duration'] == '1h 45m'
|
|
|
|
|
|
def test_total_duration_in_trip():
|
|
# depart 07:00, arrive 13:34 → 6h 34m
|
|
trips = combine_trips([GWR_FAST], [ES_PARIS], DATE)
|
|
assert trips[0]['total_duration'] == '6h 34m'
|
|
|
|
|
|
def test_connection_duration_in_trip():
|
|
# arrive Paddington 08:45, depart St Pancras 10:01 → 1h 16m
|
|
trips = combine_trips([GWR_FAST], [ES_PARIS], DATE)
|
|
assert trips[0]['connection_duration'] == '1h 16m'
|