mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-10-31 18:12:45 +00:00
Eight parsers checked str.isdigit() and then called int(). isdigit() also accepts characters like ², ① and ⁴⁵, which int() rejects, so wx and gwx raised before their error handling for "wx seattle ²" and sent no reply, dice and roll raised from their parsers, and repeater's batch size and purge days and the feed regex: and regex_cond: group numbers failed the same way. They now check str.isdecimal(), which accepts exactly the digits int() does (ASCII and other scripts' decimal digits), so the rest are read as text.
75 lines
3.1 KiB
Python
75 lines
3.1 KiB
Python
"""Superscript and other non-decimal digits pass str.isdigit() but not int().
|
|
|
|
Each of these parsers checked isdigit() and then called int(), so an argument
|
|
like "²" raised ValueError instead of being treated as text.
|
|
"""
|
|
|
|
import asyncio
|
|
import configparser
|
|
import inspect
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
import pytest
|
|
|
|
from modules.commands.alternatives.wx_international import GlobalWxCommand
|
|
from modules.commands.dice_command import DiceCommand
|
|
from modules.commands.roll_command import RollCommand
|
|
from modules.commands.wx_command import WxCommand
|
|
from modules.feed_format import apply_feed_field_function
|
|
|
|
NON_DECIMAL_DIGITS = ["²", "¹²", "①", "⁴⁵"]
|
|
|
|
|
|
def _bot():
|
|
config = configparser.ConfigParser()
|
|
config.read_dict({"Weather": {"weather_provider": "noaa"}, "Wx_Command": {}, "Bot": {}})
|
|
bot = Mock()
|
|
bot.config = config
|
|
bot.translator.translate = Mock(side_effect=lambda key, **kwargs: key)
|
|
return bot
|
|
|
|
|
|
def _run_weather(cls, content):
|
|
cmd = cls(_bot())
|
|
cmd._get_custom_mqtt_weather_topic = Mock(return_value=None)
|
|
cmd._get_custom_wxsim_source = Mock(return_value=None)
|
|
cmd.send_response = AsyncMock(return_value=True)
|
|
cmd.record_execution = Mock()
|
|
cmd.get_weather_for_location = AsyncMock(return_value="ok")
|
|
message = SimpleNamespace(content=content, sender_id="u", sender_pubkey="pk", channel="general", is_dm=False)
|
|
assert asyncio.run(cmd.execute(message)) is True
|
|
# wx and gwx order these parameters differently; read them by name.
|
|
bound = inspect.signature(cls.get_weather_for_location).bind(
|
|
cmd, *cmd.get_weather_for_location.await_args.args, **cmd.get_weather_for_location.await_args.kwargs
|
|
)
|
|
bound.apply_defaults()
|
|
return bound.arguments["location"], bound.arguments["forecast_type"], bound.arguments["num_days"]
|
|
|
|
|
|
@pytest.mark.parametrize("cls", [WxCommand, GlobalWxCommand])
|
|
@pytest.mark.parametrize("digits", NON_DECIMAL_DIGITS)
|
|
def test_weather_keeps_a_non_decimal_digit_as_part_of_the_location(cls, digits):
|
|
args = _run_weather(cls, f"wx seattle {digits}")
|
|
assert args[:2] == (f"seattle {digits}", "default")
|
|
|
|
|
|
@pytest.mark.parametrize("cls", [WxCommand, GlobalWxCommand])
|
|
def test_weather_still_reads_ascii_and_other_decimal_day_counts(cls):
|
|
assert _run_weather(cls, "wx seattle 5") == ("seattle", "multiday", 5)
|
|
assert _run_weather(cls, "wx seattle ٣") == ("seattle", "multiday", 3) # Arabic-Indic three
|
|
|
|
|
|
@pytest.mark.parametrize("digits", NON_DECIMAL_DIGITS)
|
|
def test_dice_and_roll_reject_non_decimal_digits(digits):
|
|
assert DiceCommand(_bot()).parse_dice_notation(digits) == (None, None, False)
|
|
assert RollCommand(_bot()).parse_roll_notation(digits) is None
|
|
|
|
|
|
def test_feed_regex_treats_a_non_decimal_suffix_as_part_of_the_pattern():
|
|
# Before, ":²" was taken for a group number and int("²") aborted the function.
|
|
assert apply_feed_field_function("abc:²x", "regex:(b)c:²") == "b"
|
|
assert apply_feed_field_function("abc", "regex_cond:(b):x:y:²") == "b"
|
|
# A real group number still works.
|
|
assert apply_feed_field_function("abc", "regex:(a)(b):2") == "b"
|