"""Tests for the HF/Shortwave WebSDR integration.""" from unittest.mock import patch import pytest from routes.websdr import _haversine, _parse_gps_coord from utils.kiwisdr import parse_host_port # ============================================ # Helper function tests # ============================================ def test_parse_gps_coord_float(): """Should parse a simple float string.""" assert _parse_gps_coord('51.5074') == pytest.approx(51.5074) def test_parse_gps_coord_negative(): """Should parse a negative coordinate.""" assert _parse_gps_coord('-33.87') == pytest.approx(-33.87) def test_parse_gps_coord_parentheses(): """Should handle parentheses in coordinate string.""" assert _parse_gps_coord('(-33.87)') == pytest.approx(-33.87) def test_parse_gps_coord_empty(): """Should return None for empty string.""" assert _parse_gps_coord('') is None assert _parse_gps_coord(None) is None def test_parse_gps_coord_invalid(): """Should return None for invalid string.""" assert _parse_gps_coord('abc') is None def test_haversine_same_point(): """Distance between same point should be 0.""" assert _haversine(51.5, -0.1, 51.5, -0.1) == pytest.approx(0.0, abs=0.01) def test_haversine_known_distance(): """Test with known city pair (London to Paris ~343 km).""" dist = _haversine(51.5074, -0.1278, 48.8566, 2.3522) assert 340 < dist < 350 # ============================================ # Endpoint tests # ============================================ @pytest.fixture def auth_client(client): """Client with logged-in session.""" with client.session_transaction() as sess: sess['logged_in'] = True return client def test_websdr_status(auth_client): """Status endpoint should return cache info.""" resp = auth_client.get('/websdr/status') assert resp.status_code == 200 data = resp.get_json() assert data['status'] == 'ok' assert 'cached_receivers' in data def test_websdr_receivers_empty_cache(auth_client): """Receivers endpoint should work even with empty cache.""" with patch('routes.websdr.get_receivers', return_value=[]): resp = auth_client.get('/websdr/receivers') assert resp.status_code == 200 data = resp.get_json() assert data['status'] == 'success' assert data['receivers'] == [] def test_websdr_receivers_with_data(auth_client): """Receivers endpoint should return filtered data.""" mock_receivers = [ {'name': 'Test RX', 'url': 'http://test.com', 'lat': 51.5, 'lon': -0.1, 'users': 1, 'users_max': 4, 'available': True, 'freq_lo': 0, 'freq_hi': 30000, 'antenna': 'Dipole', 'bands': 'HF'}, {'name': 'Full RX', 'url': 'http://full.com', 'lat': 48.8, 'lon': 2.3, 'users': 4, 'users_max': 4, 'available': False, 'freq_lo': 0, 'freq_hi': 30000, 'antenna': 'Loop', 'bands': 'HF'}, ] with patch('routes.websdr.get_receivers', return_value=mock_receivers): # Filter available only resp = auth_client.get('/websdr/receivers?available=true') assert resp.status_code == 200 data = resp.get_json() assert len(data['receivers']) == 1 assert data['receivers'][0]['name'] == 'Test RX' def test_websdr_nearest_missing_params(auth_client): """Nearest endpoint should require lat/lon.""" resp = auth_client.get('/websdr/receivers/nearest') assert resp.status_code == 400 def test_websdr_nearest_with_coords(auth_client): """Nearest endpoint should sort by distance.""" mock_receivers = [ {'name': 'Far RX', 'url': 'http://far.com', 'lat': -33.87, 'lon': 151.21, 'users': 0, 'users_max': 4, 'available': True, 'freq_lo': 0, 'freq_hi': 30000, 'antenna': 'Dipole', 'bands': 'HF'}, {'name': 'Near RX', 'url': 'http://near.com', 'lat': 51.0, 'lon': -0.5, 'users': 0, 'users_max': 4, 'available': True, 'freq_lo': 0, 'freq_hi': 30000, 'antenna': 'Loop', 'bands': 'HF'}, ] with patch('routes.websdr.get_receivers', return_value=mock_receivers): resp = auth_client.get('/websdr/receivers/nearest?lat=51.5&lon=-0.1') assert resp.status_code == 200 data = resp.get_json() assert data['status'] == 'success' assert len(data['receivers']) == 2 # Near should be first assert data['receivers'][0]['name'] == 'Near RX' def test_websdr_spy_station_receivers(auth_client): """Spy station cross-reference should find matching receivers.""" mock_receivers = [ {'name': 'HF RX', 'url': 'http://hf.com', 'lat': 51.5, 'lon': -0.1, 'users': 0, 'users_max': 4, 'available': True, 'freq_lo': 0, 'freq_hi': 30000, 'antenna': 'Dipole', 'bands': 'HF'}, ] with patch('routes.websdr.get_receivers', return_value=mock_receivers): # e06 is one of the spy stations resp = auth_client.get('/websdr/spy-station/e06/receivers') assert resp.status_code == 200 data = resp.get_json() assert data['status'] == 'success' assert 'station' in data def test_websdr_spy_station_not_found(auth_client): """Non-existent station should return 404.""" resp = auth_client.get('/websdr/spy-station/nonexistent/receivers') assert resp.status_code == 404 # ============================================ # parse_host_port tests (integration) # ============================================ def test_parse_host_port_http_url(): """Should parse standard KiwiSDR URL.""" host, port = parse_host_port('http://kiwi.example.com:8073') assert host == 'kiwi.example.com' assert port == 8073 def test_parse_host_port_no_protocol(): """Should handle bare hostname.""" host, port = parse_host_port('my-kiwi.local:8074') assert host == 'my-kiwi.local' assert port == 8074 def test_parse_host_port_with_trailing_slash(): """Should handle URL with trailing path.""" host, port = parse_host_port('http://kiwi.com:8073/') assert host == 'kiwi.com' assert port == 8073