# test_gpx.py # # author: deng # date: 20251129 import io import pytest class TestGPXProcessor: """Test cases for GPXProcessor class.""" @pytest.fixture def simple_gpx_data(self): """Create a simple GPX file data for testing.""" gpx_xml = """ 100 Test Waypoint Test Track 100 150 120 """ return io.BytesIO(gpx_xml.encode('utf-8')) @pytest.fixture def gpx_processor(self, simple_gpx_data): """Create a GPXProcessor instance with simple data.""" from hiking_assistant.gpx import GPXProcessor processor = GPXProcessor(simple_gpx_data) processor.validate_and_parse() return processor def test_init(self): """Test GPXProcessor initialization.""" from hiking_assistant.gpx import GPXProcessor gpx_file = io.BytesIO(b'test data') processor = GPXProcessor(gpx_file) assert processor.gpx_file == gpx_file assert processor.gpx is None assert processor.points == [] assert processor.elevations == [] assert processor.waypoints == [] def test_validate_and_parse_success(self, simple_gpx_data): """Test successful GPX parsing.""" from hiking_assistant.gpx import GPXProcessor processor = GPXProcessor(simple_gpx_data) result = processor.validate_and_parse() assert result is True assert len(processor.points) == 3 assert len(processor.elevations) == 3 assert len(processor.waypoints) == 1 def test_validate_and_parse_invalid_gpx_raises_exception(self): """Test that invalid GPX data raises exception.""" from hiking_assistant.gpx import GPXProcessor invalid_data = io.BytesIO(b'not a valid gpx file') processor = GPXProcessor(invalid_data) with pytest.raises(Exception, match='GPX 檔案解析失敗'): processor.validate_and_parse() def test_waypoint_extraction(self, gpx_processor): """Test waypoint extraction from GPX.""" assert len(gpx_processor.waypoints) == 1 waypoint = gpx_processor.waypoints[0] assert waypoint['lat'] == 25.0 assert waypoint['lon'] == 121.0 assert waypoint['name'] == 'Test Waypoint' assert waypoint['elevation'] == 100.0 def test_calculate_distance(self, gpx_processor): """Test distance calculation.""" distance = gpx_processor.calculate_distance() assert distance > 0 assert isinstance(distance, float) assert len(gpx_processor.distances) == 3 assert gpx_processor.distances[0] == 0.0 def test_calculate_distance_empty_points_returns_zero(self): """Test distance calculation with no points.""" from hiking_assistant.gpx import GPXProcessor processor = GPXProcessor(io.BytesIO(b'')) distance = processor.calculate_distance() assert distance == 0.0 def test_calculate_elevation_gain_loss(self, gpx_processor): """Test elevation gain/loss calculation.""" gain, loss = gpx_processor.calculate_elevation_gain_loss() assert isinstance(gain, float) assert isinstance(loss, float) assert gain >= 0 assert loss >= 0 def test_get_min_max_elevation(self, gpx_processor): """Test min/max elevation retrieval.""" min_elev, max_elev = gpx_processor.get_min_max_elevation() assert min_elev == 100.0 assert max_elev == 150.0 def test_get_min_max_elevation_empty_returns_zero(self): """Test min/max elevation with empty data.""" from hiking_assistant.gpx import GPXProcessor processor = GPXProcessor(io.BytesIO(b'')) min_elev, max_elev = processor.get_min_max_elevation() assert min_elev == 0.0 assert max_elev == 0.0 def test_get_start_end_points(self, gpx_processor): """Test start/end points retrieval.""" start, end = gpx_processor.get_start_end_points() assert start == (25.0, 121.0, 100.0) assert end == (25.002, 121.002, 120.0) def test_get_start_end_points_insufficient_data_returns_none(self): """Test start/end points with insufficient data.""" from hiking_assistant.gpx import GPXProcessor processor = GPXProcessor(io.BytesIO(b'')) start, end = processor.get_start_end_points() assert start is None assert end is None def test_get_all_points(self, gpx_processor): """Test getting all route points.""" points = gpx_processor.get_all_points() assert len(points) == 3 assert all(isinstance(p, tuple) and len(p) == 2 for p in points) def test_get_elevation_profile_data(self, gpx_processor): """Test elevation profile data retrieval.""" # Need to calculate distances first gpx_processor.calculate_distance() distances, elevations = gpx_processor.get_elevation_profile_data() assert len(distances) == len(elevations) assert len(distances) == 3 def test_get_gradients(self, gpx_processor): """Test gradient calculation.""" gradients = gpx_processor.get_gradients() assert len(gradients) == 2 # n-1 gradients for n points assert all(isinstance(g, (int, float)) for g in gradients) def test_get_gradients_empty_returns_empty_list(self): """Test gradient calculation with no points.""" from hiking_assistant.gpx import GPXProcessor processor = GPXProcessor(io.BytesIO(b'')) gradients = processor.get_gradients() assert gradients == [] def test_calculate_naismith_time(self, gpx_processor): """Test Naismith's Rule time calculation.""" time_minutes = gpx_processor.calculate_naismith_time() assert isinstance(time_minutes, int) assert time_minutes >= 0 def test_calculate_naismith_time_with_custom_speeds(self, gpx_processor): """Test Naismith calculation with custom speeds.""" time_default = gpx_processor.calculate_naismith_time() time_fast = gpx_processor.calculate_naismith_time(horizontal_speed=10, vertical_speed=1200) # Faster speeds should result in less time assert time_fast < time_default def test_calculate_naismith_time_no_points_returns_zero(self): """Test Naismith calculation with no points.""" from hiking_assistant.gpx import GPXProcessor processor = GPXProcessor(io.BytesIO(b'')) time_minutes = processor.calculate_naismith_time() assert time_minutes == 0 class TestGPXProcessorEdgeCases: """Test edge cases for GPXProcessor.""" def test_gpx_with_bytes_data(self): """Test GPX parsing with bytes input.""" from hiking_assistant.gpx import GPXProcessor gpx_xml = b""" 100 """ processor = GPXProcessor(io.BytesIO(gpx_xml)) result = processor.validate_and_parse() assert result is True def test_gpx_with_string_data(self): """Test GPX parsing with string input.""" from hiking_assistant.gpx import GPXProcessor gpx_xml = """ 100 """ processor = GPXProcessor(io.StringIO(gpx_xml)) result = processor.validate_and_parse() assert result is True def test_gpx_with_no_elevation_data(self): """Test GPX parsing when elevation data is missing.""" from hiking_assistant.gpx import GPXProcessor gpx_xml = """ """ processor = GPXProcessor(io.BytesIO(gpx_xml.encode('utf-8'))) processor.validate_and_parse() # Should default to 0 elevation assert all(e == 0 for e in processor.elevations)