Skip to content

Commit bea0acb

Browse files
committed
Harden ISO lookup handling
1 parent f0fa8ee commit bea0acb

5 files changed

Lines changed: 90 additions & 18 deletions

File tree

__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@
3232
ISO: Convert alpha2 country codes to country name.
3333
"""
3434

35-
import sys
36-
import supybot
3735
from supybot import world
3836

3937
# Use this for the version of this plugin.
@@ -60,7 +58,7 @@
6058
# reloaded when this plugin is reloaded. Don't forget to import them as well!
6159

6260
if world.testing:
63-
from . import test
61+
from . import test as test
6462

6563
Class = plugin.Class
6664
configure = config.configure

config.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,24 @@
2828

2929
###
3030

31-
from supybot import conf, registry
31+
from supybot import conf
3232

3333
try:
3434
from supybot.i18n import PluginInternationalization
3535

3636
_ = PluginInternationalization("ISO")
37-
except:
37+
except ImportError:
3838
# Placeholder that allows to run the plugin on a bot
3939
# without the i18n module
40-
_ = lambda x: x
40+
def _(text):
41+
return text
4142

4243

4344
def configure(advanced):
4445
# This will be called by supybot to configure this module. advanced is
4546
# a bool that specifies whether the user identified themself as an advanced
4647
# user or not. You should effect your configuration by manipulating the
4748
# registry as appropriate.
48-
from supybot.questions import expect, anything, something, yn
49-
5049
conf.registerPlugin("ISO", True)
5150

5251

plugin.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
###
3030

3131
from supybot import callbacks
32-
from supybot.commands import *
32+
from supybot.commands import wrap
3333

3434
try:
3535
from supybot.i18n import PluginInternationalization
@@ -38,14 +38,41 @@
3838
except ImportError:
3939
# Placeholder that allows to run the plugin on a bot
4040
# without the i18n module
41-
_ = lambda x: x
41+
def _(text):
42+
return text
43+
4244

4345
try:
4446
from iso3166 import countries
4547
except ImportError as ie:
4648
raise ImportError(f"Cannot import module: {ie}")
4749

4850

51+
MAX_LOOKUP_LENGTH = 64
52+
53+
54+
def normalise_lookup(value):
55+
"""Normalise a country lookup value before querying iso3166."""
56+
lookup = str(value or "").strip().lower()
57+
if not lookup:
58+
raise ValueError("Country code or name is required.")
59+
if len(lookup) > MAX_LOOKUP_LENGTH:
60+
raise ValueError("Country code or name is too long.")
61+
return lookup
62+
63+
64+
def lookup_country(value):
65+
"""Return alpha-2 code and country name for a safe lookup value."""
66+
lookup = normalise_lookup(value)
67+
try:
68+
country = countries.get(lookup)
69+
except KeyError:
70+
raise ValueError("Unknown country code or name.")
71+
name = country[0]
72+
alpha2 = country[1]
73+
return alpha2, name
74+
75+
4976
class ISO(callbacks.Plugin):
5077
"""Convert alpha2 country codes to country name."""
5178

@@ -58,13 +85,10 @@ def country(self, irc, msg, args, code):
5885
Convert country name to alpha2 country codes.
5986
"""
6087

61-
code = code.lower()
6288
try:
63-
country = countries.get(code)
64-
name = country[0]
65-
alpha2 = country[1]
66-
except KeyError as error:
67-
raise callbacks.Error(f"{error} unknown country code.")
89+
alpha2, name = lookup_country(code)
90+
except ValueError as error:
91+
raise callbacks.Error(str(error))
6892
irc.reply(f"{alpha2} {name}", prefixNick=False)
6993

7094

pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ extend-exclude = '''
3535
)/
3636
'''
3737

38+
[tool.mypy]
39+
python_version = "3.11"
40+
ignore_missing_imports = true
41+
explicit_package_bases = true
42+
exclude = "(__init__|test)\\.py"
43+
44+
[tool.pytest.ini_options]
45+
python_files = ["test.py"]
46+
3847
[tool.setuptools.package-dir]
3948
"limnoria_iso" = "."
4049
"limnoria_iso.local" = "local/"

test.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,53 @@
2828

2929
###
3030

31-
from supybot.test import *
31+
import unittest
3232

33+
import supybot.test as supybot_test
3334

34-
class ISOTestCase(PluginTestCase):
35+
supybot_test.PluginTestCase.__test__ = False
36+
37+
try:
38+
from . import plugin as iso_plugin
39+
except ImportError: # pragma: no cover - allows direct unittest execution.
40+
import plugin as iso_plugin
41+
42+
43+
class ISOTestCase(supybot_test.PluginTestCase):
44+
__test__ = False
3545
plugins = ("ISO",)
3646

3747

48+
class ISOUnitTestCase(unittest.TestCase):
49+
def test_lookup_country_accepts_alpha2_code(self):
50+
self.assertEqual(iso_plugin.lookup_country("tr"), ("TR", "Türkiye"))
51+
52+
def test_lookup_country_accepts_country_name(self):
53+
self.assertEqual(
54+
iso_plugin.lookup_country("myanmar"), ("MM", "Myanmar")
55+
)
56+
57+
def test_normalise_lookup_rejects_empty_input(self):
58+
with self.assertRaisesRegex(
59+
ValueError, "Country code or name is required."
60+
):
61+
iso_plugin.normalise_lookup(" \n\t ")
62+
63+
def test_normalise_lookup_rejects_overlong_input(self):
64+
with self.assertRaisesRegex(
65+
ValueError, "Country code or name is too long."
66+
):
67+
iso_plugin.normalise_lookup(
68+
"x" * (iso_plugin.MAX_LOOKUP_LENGTH + 1)
69+
)
70+
71+
def test_lookup_country_uses_generic_unknown_error(self):
72+
with self.assertRaisesRegex(
73+
ValueError, "Unknown country code or name."
74+
) as context:
75+
iso_plugin.lookup_country("zz\nvery noisy input")
76+
77+
self.assertNotIn("zz", str(context.exception))
78+
79+
3880
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

0 commit comments

Comments
 (0)