Coverage for tests/test_buses_graph.py: 100%
118 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
1"""Tests for bus graph module."""
3import pytest
5from data_api.domain.buses import graph, models
8class TestStopsData:
9 """Tests for stop definitions."""
11 async def test_stops_data_exists(self):
12 """Test that stops data is defined."""
13 assert len(graph.STOPS_DATA) > 0
15 async def test_stop_has_required_fields(self):
16 """Test that stops have required fields."""
17 for stop_id, stop in graph.STOPS_DATA.items():
18 assert isinstance(stop, models.Stop)
19 assert stop.id == stop_id
20 assert stop.name
21 assert stop.name_en
22 assert stop.latitude
23 assert stop.longitude
26class TestCreateRoute:
27 """Tests for route creation."""
29 async def test_create_route(self):
30 """Test creating a route."""
31 route = graph.create_route("test_route", ["M1", "M2", "M3"], [0, 1, 2])
32 assert route.id == "test_route"
33 assert len(route.stops) == 3
34 assert len(route.time_offsets) == 3
35 assert route.time_offsets == [0, 1, 3]
37 async def test_create_route_offsets_cumulative(self):
38 """Test that time offsets are cumulative."""
39 route = graph.create_route("test_route", ["M1", "M2", "M3", "M4"], [0, 2, 3, 1])
40 assert route.time_offsets == [0, 2, 5, 6]
43class TestRouteDefinitions:
44 """Tests for route definitions."""
46 async def test_red_routes_exist(self):
47 """Test red routes are defined."""
48 assert graph.red_M1_M5 is not None
49 assert graph.red_M2_M5 is not None
50 assert graph.red_M5_M1 is not None
51 assert graph.red_M5_M2 is not None
53 async def test_green_routes_exist(self):
54 """Test green routes are defined."""
55 assert graph.green_M1_M5 is not None
56 assert graph.green_M2_M5 is not None
57 assert graph.green_M5_M1 is not None
58 assert graph.green_M5_M2 is not None
60 async def test_nanda_routes_exist(self):
61 """Test Nanda routes are defined."""
62 assert graph.nanda_M1_S1_r1 is not None
63 assert graph.nanda_M1_S1_r2 is not None
64 assert graph.nanda_S1_M1_r1 is not None
65 assert graph.nanda_S1_M1_r2 is not None
68class TestRouteResolver:
69 """Tests for RouteResolver."""
71 async def test_resolve_main_campus_red_uphill(self):
72 """Test resolving red line uphill route."""
73 route = graph.RouteResolver.resolve_main_campus_route("red", "校門")
74 assert route == graph.red_M1_M5
76 async def test_resolve_main_campus_red_downhill(self):
77 """Test resolving red line downhill route."""
78 route = graph.RouteResolver.resolve_main_campus_route("red", "台積館")
79 assert route == graph.red_M5_M1
81 async def test_resolve_main_campus_red_downhill_from_gen2(self):
82 """Test resolving red line downhill route ending at Gen2."""
83 route = graph.RouteResolver.resolve_main_campus_route("red", "台積館", is_from_gen2=True)
84 assert route == graph.red_M5_M2
86 async def test_resolve_main_campus_red_from_gen2(self):
87 """Test resolving red line from Gen2."""
88 route = graph.RouteResolver.resolve_main_campus_route("red", "綜二")
89 assert route == graph.red_M2_M5
91 async def test_resolve_main_campus_green_uphill(self):
92 """Test resolving green line uphill route."""
93 route = graph.RouteResolver.resolve_main_campus_route("green", "北校門")
94 assert route == graph.green_M1_M5
96 async def test_resolve_main_campus_green_downhill(self):
97 """Test resolving green line downhill route."""
98 route = graph.RouteResolver.resolve_main_campus_route("green", "台積")
99 assert route == graph.green_M5_M1
101 async def test_resolve_main_campus_green_from_gen2(self):
102 """Test resolving green line from Gen2."""
103 route = graph.RouteResolver.resolve_main_campus_route("green", "綜二")
104 assert route == graph.green_M2_M5
106 async def test_resolve_main_campus_unknown_stop(self):
107 """Test resolving unknown stop returns None."""
108 route = graph.RouteResolver.resolve_main_campus_route("red", "未知站")
109 assert route is None
111 async def test_resolve_nanda_route_up_route1(self):
112 """Test resolving Nanda uphill route 1."""
113 route = graph.RouteResolver.resolve_nanda_route("up", "一般路線")
114 assert route == graph.nanda_M1_S1_r1
116 async def test_resolve_nanda_route_up_route2(self):
117 """Test resolving Nanda uphill route 2."""
118 route = graph.RouteResolver.resolve_nanda_route("up", "路線二經過教育學院")
119 assert route == graph.nanda_M1_S1_r2
121 async def test_resolve_nanda_route_down_route1(self):
122 """Test resolving Nanda downhill route 1."""
123 route = graph.RouteResolver.resolve_nanda_route("down", "")
124 assert route == graph.nanda_S1_M1_r1
126 async def test_resolve_nanda_route_down_route2(self):
127 """Test resolving Nanda downhill route 2."""
128 route = graph.RouteResolver.resolve_nanda_route("down", "教育學院")
129 assert route == graph.nanda_S1_M1_r2
131 async def test_get_nanda_line_route1(self):
132 """Test get_nanda_line returns route_1 for regular description."""
133 line = graph.RouteResolver.get_nanda_line("一般路線")
134 assert line == "route_1"
136 async def test_get_nanda_line_route2(self):
137 """Test get_nanda_line returns route_2 for route 2 description."""
138 line = graph.RouteResolver.get_nanda_line("路線二")
139 assert line == "route_2"
141 async def test_get_nanda_line_education_building(self):
142 """Test get_nanda_line returns route_2 for education building."""
143 line = graph.RouteResolver.get_nanda_line("經過教育學院")
144 assert line == "route_2"
146 async def test_get_nanda_line_none(self):
147 """Test get_nanda_line handles None description."""
148 line = graph.RouteResolver.get_nanda_line(None)
149 assert line == "route_1"
152@pytest.mark.parametrize(
153 "line,description,expected",
154 [
155 ("route1", "路線二", "route_1"),
156 ("route2", "", "route_2"),
157 ("route_1", "教育學院", "route_1"),
158 ("route_2", "", "route_2"),
159 ("nanda_route_1", "路線二", "route_1"),
160 ("nanda_route_2", "", "route_2"),
161 ("", "路線二", "route_2"),
162 ("", "", "route_1"),
163 ],
164)
165@pytest.mark.parametrize("direction", ["up", "down"])
166def test_published_nanda_line_selects_matching_graph(line, description, expected, direction):
167 assert graph.RouteResolver.get_nanda_line(description, line) == expected
168 route = graph.RouteResolver.resolve_nanda_route(direction, description, line)
169 assert ("M7" in [stop.id for stop in route.stops]) == (expected == "route_2")
170 assert route.stops[0].id == ("M1" if direction == "up" else "S1")
173def test_unknown_published_nanda_line_is_rejected():
174 with pytest.raises(ValueError, match="Invalid Nanda bus line"):
175 graph.RouteResolver.get_nanda_line("", "route3")
178class TestModels:
179 """Tests for bus models."""
181 async def test_stop_model(self):
182 """Test Stop model."""
183 stop = models.Stop("T1", "測試站", "Test Stop", "24.0", "120.0")
184 assert stop.id == "T1"
185 assert stop.name == "測試站"
186 assert stop.name_en == "Test Stop"
188 async def test_route_model(self):
189 """Test Route model."""
190 stop = models.Stop("T1", "測試站", "Test Stop", "24.0", "120.0")
191 route = models.Route("test_route", [stop], [0])
192 assert route.id == "test_route"
193 assert len(route.stops) == 1
194 assert route.time_offsets == [0]
196 async def test_bus_schedule_model(self):
197 """Test BusSchedule model."""
198 schedule = models.BusSchedule(
199 time="08:00",
200 description="往台積",
201 bus_type="main",
202 line="red",
203 dep_stop="校門",
204 )
205 assert schedule.time == "08:00"
206 assert schedule.bus_type == "main"
208 async def test_bus_detailed_schedule_model(self):
209 """Test BusDetailedSchedule model."""
210 schedule = models.BusDetailedSchedule(
211 dep_info={"time": "08:00", "description": "往台積"},
212 arr_info=[{"stop": "綜二", "arrive_time": "08:01"}],
213 bus_type="main",
214 )
215 assert schedule.dep_info["time"] == "08:00"
216 assert len(schedule.arr_info) == 1