mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-03-31 04:25:39 +00:00
- Updated the `resolve_path` function in `utils.py` to clarify behavior regarding absolute paths. - Changed type hints in `discord_bridge_service.py` for better clarity and consistency. - Removed unused imports and unnecessary comments in various test files to improve code cleanliness and readability.
402 lines
15 KiB
Python
402 lines
15 KiB
Python
"""Tests for modules.commands.aurora_command — pure logic functions."""
|
|
|
|
import configparser
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
|
|
from modules.commands.aurora_command import AuroraCommand
|
|
from tests.conftest import mock_message
|
|
|
|
|
|
def _make_bot(with_location=False):
|
|
bot = MagicMock()
|
|
bot.logger = Mock()
|
|
config = configparser.ConfigParser()
|
|
config.add_section("Bot")
|
|
config.set("Bot", "bot_name", "TestBot")
|
|
if with_location:
|
|
config.set("Bot", "bot_latitude", "47.6")
|
|
config.set("Bot", "bot_longitude", "-122.3")
|
|
config.add_section("Channels")
|
|
config.set("Channels", "monitor_channels", "general")
|
|
config.set("Channels", "respond_to_dms", "true")
|
|
config.add_section("Keywords")
|
|
config.add_section("Weather")
|
|
config.set("Weather", "default_state", "WA")
|
|
config.set("Weather", "default_country", "US")
|
|
config.add_section("Aurora_Command")
|
|
config.set("Aurora_Command", "enabled", "true")
|
|
bot.config = config
|
|
bot.translator = MagicMock()
|
|
bot.translator.translate = Mock(side_effect=lambda key, **kw: key)
|
|
bot.command_manager = MagicMock()
|
|
bot.command_manager.monitor_channels = ["general"]
|
|
return bot
|
|
|
|
|
|
class TestProbIndicator:
|
|
"""Tests for _prob_indicator."""
|
|
|
|
def setup_method(self):
|
|
self.cmd = AuroraCommand(_make_bot())
|
|
|
|
def test_zero_returns_lowest_bar(self):
|
|
result = self.cmd._prob_indicator(0)
|
|
assert result == "▁"
|
|
|
|
def test_100_returns_highest_bar(self):
|
|
result = self.cmd._prob_indicator(100)
|
|
assert result == "█"
|
|
|
|
def test_50_returns_mid_bar(self):
|
|
result = self.cmd._prob_indicator(50)
|
|
assert result in "▁▂▃▄▅▆▇█"
|
|
|
|
def test_all_values_return_valid_bar(self):
|
|
for pct in range(0, 101, 10):
|
|
result = self.cmd._prob_indicator(pct)
|
|
assert result in "▁▂▃▄▅▆▇█"
|
|
|
|
|
|
class TestFormatKpTime:
|
|
"""Tests for _format_kp_time."""
|
|
|
|
def setup_method(self):
|
|
self.cmd = AuroraCommand(_make_bot())
|
|
|
|
def test_empty_string_returns_dash(self):
|
|
assert self.cmd._format_kp_time("") == "—"
|
|
|
|
def test_none_like_returns_dash(self):
|
|
assert self.cmd._format_kp_time(" ") == "—"
|
|
|
|
def test_space_separated_format(self):
|
|
result = self.cmd._format_kp_time("2026-01-21 05:13:00")
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_iso_format(self):
|
|
result = self.cmd._format_kp_time("2026-01-21T05:13:00")
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_invalid_returns_dash(self):
|
|
result = self.cmd._format_kp_time("not-a-date")
|
|
assert result == "—"
|
|
|
|
|
|
class TestGetBotLocation:
|
|
"""Tests for _get_bot_location."""
|
|
|
|
def test_returns_location_when_configured(self):
|
|
bot = _make_bot(with_location=True)
|
|
cmd = AuroraCommand(bot)
|
|
result = cmd._get_bot_location()
|
|
assert result is not None
|
|
lat, lon = result
|
|
assert abs(lat - 47.6) < 0.01
|
|
assert abs(lon - (-122.3)) < 0.01
|
|
|
|
def test_returns_none_when_not_configured(self):
|
|
bot = _make_bot(with_location=False)
|
|
cmd = AuroraCommand(bot)
|
|
result = cmd._get_bot_location()
|
|
assert result is None
|
|
|
|
|
|
class TestResolveLocation:
|
|
"""Tests for _resolve_location (pure location string parsing)."""
|
|
|
|
def setup_method(self):
|
|
self.cmd = AuroraCommand(_make_bot())
|
|
|
|
def test_coord_string_parsed(self):
|
|
msg = mock_message(content="aurora 47.6,-122.3")
|
|
lat, lon, label, err = self.cmd._resolve_location(msg, "47.6,-122.3")
|
|
assert lat == pytest.approx(47.6)
|
|
assert lon == pytest.approx(-122.3)
|
|
assert err is None
|
|
|
|
def test_invalid_lat_returns_error(self):
|
|
msg = mock_message(content="aurora 200,0")
|
|
lat, lon, label, err = self.cmd._resolve_location(msg, "200,0")
|
|
assert err is not None
|
|
|
|
def test_invalid_lon_returns_error(self):
|
|
msg = mock_message(content="aurora 0,200")
|
|
lat, lon, label, err = self.cmd._resolve_location(msg, "0,200")
|
|
assert err is not None
|
|
|
|
def test_no_location_uses_bot_location(self):
|
|
bot = _make_bot(with_location=True)
|
|
cmd = AuroraCommand(bot)
|
|
msg = mock_message(content="aurora")
|
|
# No companion location: db_manager returns nothing
|
|
bot.db_manager.execute_query.return_value = []
|
|
lat, lon, label, err = cmd._resolve_location(msg, None)
|
|
assert lat is not None
|
|
assert err is None
|
|
|
|
def test_no_location_no_bot_returns_error(self):
|
|
bot = _make_bot(with_location=False)
|
|
cmd = AuroraCommand(bot)
|
|
msg = mock_message(content="aurora")
|
|
# No companion, no bot location
|
|
bot.db_manager.execute_query.return_value = []
|
|
lat, lon, label, err = cmd._resolve_location(msg, None)
|
|
assert err is not None
|
|
|
|
|
|
class TestAuroraCanExecute:
|
|
"""Tests for can_execute."""
|
|
|
|
def test_enabled(self):
|
|
bot = _make_bot()
|
|
cmd = AuroraCommand(bot)
|
|
msg = mock_message(content="aurora", channel="general")
|
|
assert cmd.can_execute(msg) is True
|
|
|
|
def test_disabled(self):
|
|
bot = _make_bot()
|
|
bot.config.set("Aurora_Command", "enabled", "false")
|
|
cmd = AuroraCommand(bot)
|
|
msg = mock_message(content="aurora", channel="general")
|
|
assert cmd.can_execute(msg) is False
|
|
|
|
|
|
class TestResolveLocationExtended:
|
|
"""Extended tests for _resolve_location."""
|
|
|
|
def test_companion_location_used(self):
|
|
bot = _make_bot()
|
|
cmd = AuroraCommand(bot)
|
|
bot.db_manager.execute_query.return_value = [{"latitude": 47.5, "longitude": -122.1}]
|
|
msg = mock_message(content="aurora", sender_pubkey="a" * 64)
|
|
lat, lon, label, err = cmd._resolve_location(msg, None)
|
|
assert lat == pytest.approx(47.5)
|
|
assert lon == pytest.approx(-122.1)
|
|
assert err is None
|
|
|
|
def test_default_lat_lon_from_config(self):
|
|
bot = _make_bot()
|
|
bot.config.set("Aurora_Command", "default_lat", "48.0")
|
|
bot.config.set("Aurora_Command", "default_lon", "-122.5")
|
|
cmd = AuroraCommand(bot)
|
|
bot.db_manager.execute_query.return_value = []
|
|
msg = mock_message(content="aurora")
|
|
lat, lon, label, err = cmd._resolve_location(msg, None)
|
|
assert lat == pytest.approx(48.0)
|
|
assert err is None
|
|
|
|
def test_coords_value_error_returns_error(self):
|
|
bot = _make_bot()
|
|
cmd = AuroraCommand(bot)
|
|
msg = mock_message(content="aurora 47.6,-not-a-number")
|
|
lat, lon, label, err = cmd._resolve_location(msg, "47.6,-not-a-number")
|
|
# Non-matching regex so falls through to city path or ValueError
|
|
assert isinstance(err, (str, type(None)))
|
|
|
|
|
|
class TestAuroraExecute:
|
|
"""Tests for execute()."""
|
|
|
|
def test_no_location_no_bot_returns_error(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
bot = _make_bot(with_location=False)
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
bot.db_manager.execute_query.return_value = []
|
|
msg = mock_message(content="aurora", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
cmd.send_response.assert_called_once()
|
|
|
|
def test_execute_with_bot_location_success(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
bot = _make_bot(with_location=True)
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
bot.db_manager.execute_query.return_value = []
|
|
|
|
# Mock the aurora client data
|
|
mock_data = MagicMock()
|
|
mock_data.kp_index = 2.5
|
|
mock_data.kp_timestamp = "2026-01-21 05:13:00"
|
|
mock_data.aurora_probability = 15.0
|
|
|
|
with patch("modules.commands.aurora_command.NOAAAuroraClient") as MockClient:
|
|
MockClient.return_value.get_aurora_data.return_value = mock_data
|
|
msg = mock_message(content="aurora", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
cmd.send_response.assert_called_once()
|
|
|
|
def test_execute_kp_g3_severe(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
bot = _make_bot(with_location=True)
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
bot.db_manager.execute_query.return_value = []
|
|
|
|
mock_data = MagicMock()
|
|
mock_data.kp_index = 8.0 # >= 7
|
|
mock_data.kp_timestamp = ""
|
|
mock_data.aurora_probability = 95.0
|
|
|
|
with patch("modules.commands.aurora_command.NOAAAuroraClient") as MockClient:
|
|
MockClient.return_value.get_aurora_data.return_value = mock_data
|
|
msg = mock_message(content="aurora", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
|
|
def test_execute_kp_g1_g2(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
bot = _make_bot(with_location=True)
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
bot.db_manager.execute_query.return_value = []
|
|
|
|
mock_data = MagicMock()
|
|
mock_data.kp_index = 5.5 # >= 5 but < 7
|
|
mock_data.kp_timestamp = ""
|
|
mock_data.aurora_probability = 60.0
|
|
|
|
with patch("modules.commands.aurora_command.NOAAAuroraClient") as MockClient:
|
|
MockClient.return_value.get_aurora_data.return_value = mock_data
|
|
msg = mock_message(content="aurora", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
|
|
def test_execute_kp_unsettled(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
bot = _make_bot(with_location=True)
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
bot.db_manager.execute_query.return_value = []
|
|
|
|
mock_data = MagicMock()
|
|
mock_data.kp_index = 4.2 # >= 4 but < 5
|
|
mock_data.kp_timestamp = "2026-01-21T05:13:00"
|
|
mock_data.aurora_probability = 40.0
|
|
|
|
with patch("modules.commands.aurora_command.NOAAAuroraClient") as MockClient:
|
|
MockClient.return_value.get_aurora_data.return_value = mock_data
|
|
msg = mock_message(content="aurora", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
|
|
def test_execute_aurora_fetch_exception(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
bot = _make_bot(with_location=True)
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
bot.db_manager.execute_query.return_value = []
|
|
|
|
with patch("modules.commands.aurora_command.NOAAAuroraClient") as MockClient:
|
|
MockClient.return_value.get_aurora_data.side_effect = Exception("Network error")
|
|
msg = mock_message(content="aurora", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
cmd.send_response.assert_called_once()
|
|
|
|
def test_execute_with_coords_arg(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
bot = _make_bot()
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
|
|
mock_data = MagicMock()
|
|
mock_data.kp_index = 1.0
|
|
mock_data.kp_timestamp = ""
|
|
mock_data.aurora_probability = 5.0
|
|
|
|
with patch("modules.commands.aurora_command.NOAAAuroraClient") as MockClient:
|
|
MockClient.return_value.get_aurora_data.return_value = mock_data
|
|
msg = mock_message(content="aurora 47.6,-122.3", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
|
|
def test_execute_no_location_error_key(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
bot = _make_bot(with_location=False)
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
bot.db_manager.execute_query.return_value = []
|
|
|
|
# Simulate an error from _resolve_location
|
|
with patch.object(cmd, "_resolve_location", return_value=(None, None, None, "commands.aurora.no_location")):
|
|
msg = mock_message(content="aurora", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
|
|
def test_execute_error_key_zipcode(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
bot = _make_bot()
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
|
|
with patch.object(cmd, "_resolve_location", return_value=(None, None, None, "commands.aurora.no_location_zipcode")):
|
|
msg = mock_message(content="aurora 99999", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
|
|
def test_execute_error_key_city(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
bot = _make_bot()
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
|
|
with patch.object(cmd, "_resolve_location", return_value=(None, None, None, "commands.aurora.no_location_city")):
|
|
msg = mock_message(content="aurora Seattle", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
|
|
def test_execute_error_key_other(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
bot = _make_bot()
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
|
|
with patch.object(cmd, "_resolve_location", return_value=(None, None, None, "commands.aurora.error")):
|
|
msg = mock_message(content="aurora bad", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
|
|
def test_execute_response_truncated_at_max_length(self):
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
bot = _make_bot(with_location=True)
|
|
cmd = AuroraCommand(bot)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
bot.db_manager.execute_query.return_value = []
|
|
|
|
mock_data = MagicMock()
|
|
mock_data.kp_index = 0.5
|
|
mock_data.kp_timestamp = ""
|
|
mock_data.aurora_probability = 1.0
|
|
|
|
# Make translate return a very long string
|
|
cmd.translate = Mock(side_effect=lambda key, **kw: "x" * 300 if key == "commands.aurora.response" else key)
|
|
cmd.get_max_message_length = Mock(return_value=100)
|
|
|
|
with patch("modules.commands.aurora_command.NOAAAuroraClient") as MockClient:
|
|
MockClient.return_value.get_aurora_data.return_value = mock_data
|
|
msg = mock_message(content="aurora", channel="general")
|
|
result = asyncio.run(cmd.execute(msg))
|
|
assert result is True
|
|
# Response should be truncated
|
|
call_text = cmd.send_response.call_args[0][1]
|
|
assert len(call_text) <= 103 # 100 + "..."
|