Files
meshcore-bot/tests/commands/test_airplanes_command.py

163 lines
5.9 KiB
Python

"""Tests for airplanes/ADS-B command API URL handling and fetch errors."""
from unittest.mock import MagicMock, patch
import pytest
import requests
from modules.commands.airplanes_command import (
DEFAULT_API_URL,
USER_AGENT,
AirplanesCommand,
_response_body_snippet,
is_deprecated_public_airplanes_live_url,
normalize_api_url,
)
class TestNormalizeApiUrl:
def test_adds_trailing_slash(self):
assert normalize_api_url("https://api.adsb.lol/v2") == "https://api.adsb.lol/v2/"
def test_keeps_trailing_slash(self):
assert normalize_api_url("https://api.adsb.lol/v2/") == "https://api.adsb.lol/v2/"
def test_empty_stays_empty(self):
assert normalize_api_url(" ") == ""
class TestDeprecatedAirplanesLiveUrl:
@pytest.mark.parametrize(
"url",
[
"http://api.airplanes.live/v2/",
"https://api.airplanes.live/v2/",
"http://api.airplanes.live/v2",
"https://API.AIRPLANES.LIVE/v2/",
],
)
def test_detects_public_host(self, url):
assert is_deprecated_public_airplanes_live_url(url) is True
@pytest.mark.parametrize(
"url",
[
DEFAULT_API_URL,
"http://localhost:8080/data/",
"https://rest.api.airplanes.live/",
"https://api.airplanes.live.example.com/v2/",
],
)
def test_ignores_other_hosts(self, url):
assert is_deprecated_public_airplanes_live_url(url) is False
class TestResolveApiUrl:
def test_default_is_adsb_lol(self, command_mock_bot):
cmd = AirplanesCommand(command_mock_bot)
assert cmd.api_url == DEFAULT_API_URL
@pytest.mark.parametrize(
"configured",
[
"http://api.airplanes.live/v2/",
"https://api.airplanes.live/v2",
],
)
def test_remaps_legacy_airplanes_live(self, command_mock_bot, configured):
command_mock_bot.config.add_section("Airplanes_Command")
command_mock_bot.config.set("Airplanes_Command", "api_url", configured)
cmd = AirplanesCommand(command_mock_bot)
assert cmd.api_url == DEFAULT_API_URL
command_mock_bot.logger.warning.assert_called()
warning = " ".join(str(arg) for arg in command_mock_bot.logger.warning.call_args[0])
assert "api.airplanes.live" in warning
assert DEFAULT_API_URL in warning
def test_preserves_local_readsb_url(self, command_mock_bot):
command_mock_bot.config.add_section("Airplanes_Command")
command_mock_bot.config.set("Airplanes_Command", "api_url", "http://localhost:8080/data")
cmd = AirplanesCommand(command_mock_bot)
assert cmd.api_url == "http://localhost:8080/data/"
command_mock_bot.logger.warning.assert_not_called()
def test_preserves_airplanes_live_pro_host(self, command_mock_bot):
command_mock_bot.config.add_section("Airplanes_Command")
command_mock_bot.config.set(
"Airplanes_Command", "api_url", "https://rest.api.airplanes.live/"
)
cmd = AirplanesCommand(command_mock_bot)
assert cmd.api_url == "https://rest.api.airplanes.live/"
def test_empty_api_url_uses_default(self, command_mock_bot):
command_mock_bot.config.add_section("Airplanes_Command")
command_mock_bot.config.set("Airplanes_Command", "api_url", " ")
cmd = AirplanesCommand(command_mock_bot)
assert cmd.api_url == DEFAULT_API_URL
class TestFetchAircraftData:
def test_sends_user_agent_and_builds_point_url(self, command_mock_bot):
mock_response = MagicMock()
mock_response.ok = True
mock_response.json.return_value = {"ac": []}
cmd = AirplanesCommand(command_mock_bot)
with patch(
"modules.commands.airplanes_command.requests.get",
return_value=mock_response,
) as mock_get:
data = cmd._fetch_aircraft_data(47.6, -122.3, 25)
assert data == {"ac": []}
mock_get.assert_called_once()
args, kwargs = mock_get.call_args
assert args[0] == f"{DEFAULT_API_URL}point/47.6/-122.3/25"
assert kwargs["headers"]["User-Agent"] == USER_AGENT
assert kwargs["headers"]["Accept"] == "application/json"
assert kwargs["timeout"] == cmd.url_timeout
def test_logs_http_error_status_and_sanitized_body(self, command_mock_bot):
mock_response = MagicMock()
mock_response.ok = False
mock_response.status_code = 403
mock_response.reason = "Forbidden"
mock_response.text = (
'{"error": "Please contact us at contact@airplanes.live."}\nsecret'
)
cmd = AirplanesCommand(command_mock_bot)
with patch(
"modules.commands.airplanes_command.requests.get",
return_value=mock_response,
):
assert cmd._fetch_aircraft_data(47.6, -122.3, 25) is None
command_mock_bot.logger.warning.assert_called()
args = command_mock_bot.logger.warning.call_args[0]
assert args[1] == 403
assert "api.adsb.lol" in args[2]
snippet = args[3]
assert "Please contact us" in snippet
assert "\n" not in snippet
def test_timeout_returns_none(self, command_mock_bot):
cmd = AirplanesCommand(command_mock_bot)
with patch(
"modules.commands.airplanes_command.requests.get",
side_effect=requests.exceptions.Timeout(),
):
assert cmd._fetch_aircraft_data(47.6, -122.3, 25) is None
command_mock_bot.logger.warning.assert_called()
class TestResponseBodySnippet:
def test_strips_newlines_and_truncates(self):
response = MagicMock()
response.text = "line1\nline2" + ("x" * 300)
response.reason = "Forbidden"
snippet = _response_body_snippet(response, max_length=20)
assert "\n" not in snippet
assert len(snippet) <= 20
assert snippet.startswith("line1")