diff --git a/dynsupdate/client.py b/dynsupdate/client.py index 468e581..e89e97f 100644 --- a/dynsupdate/client.py +++ b/dynsupdate/client.py @@ -72,7 +72,7 @@ def simple_ip_fetch(url, extract_fun=lambda x: x.strip(), timeout=5): # Limit response size data = resp.read(MAX_RESPONSE_DATA) except (URLError, socket.error) as e: - logger.warn('couldn\'t fetch url "{0}" with timeout {1:d}' + logger.warn('couldn\'t fetch url "{0}" with timeout {1:.4g}' .format(url, timeout)) return None else: @@ -468,6 +468,30 @@ def parse(input_str): return parse +def integer_range(min=None, max=None): + + def validate_int(value): + try: + res = int(value) + except ValueError: + raise argparse.ArgumentTypeError('Invalid integer "%s"' % value) + else: + if min is not None and res < min: + raise argparse.ArgumentTypeError( + 'Value "{val}" is smaller than "{min}"' + .format(val=res, min=min) + ) + elif max is not None and res > max: + raise argparse.ArgumentTypeError( + 'Value "{val}" is bigger than "{max}"' + .format(val=res, max=min) + ) + + return res + + return validate_int + + class Program(object): COMMANDS = { @@ -558,14 +582,16 @@ def ip_fun(cls, tries=5, timeout=5, types=None, services=None): @classmethod def ip_arguments(cls, parser): + tries_type = integer_range(min=1) types_type = comma_separated_list(SimpleIpGetter.SERVICE_TYPES) services_type = comma_separated_list(SimpleIpGetter.SERVICE_NAMES) - parser.add_argument('-n', '--tries', dest="tries", type=int, default=5) + parser.add_argument('-n', '--tries', dest="tries", type=tries_type, + default=5) parser.add_argument('-t', '--types', dest="types", type=types_type, default=None) parser.add_argument('--services', dest="services", type=services_type, default=None) - parser.add_argument('--timeout', dest="timeout", type=int, default=5) + parser.add_argument('--timeout', dest="timeout", type=float, default=5) @classmethod def build_parser(cls): diff --git a/tests/test_cli_iterface.py b/tests/test_cli_iterface.py index c18798d..8369b22 100644 --- a/tests/test_cli_iterface.py +++ b/tests/test_cli_iterface.py @@ -15,6 +15,10 @@ """ +class ExitException(Exception): + pass + + class CliParseTest(TestCase): def test_comma_separated_list(self): @@ -27,6 +31,13 @@ def test_comma_separated_list(self): self.assertRaises(argparse.ArgumentTypeError, my_types, "http,bad") # TODO: Maybe tests arguments in uppercase to + def test_validate_int(self): + positive_int = client.integer_range(min=0) + self.assertRaises(argparse.ArgumentTypeError, positive_int, "bad") + self.assertRaises(argparse.ArgumentTypeError, positive_int, "-2") + self.assertRaises(argparse.ArgumentTypeError, positive_int, "-1") + self.assertEqual(positive_int("0"), 0) + class CliIterfaceTest(TestCase): @@ -50,6 +61,17 @@ def patch_urlopen(self): self.addCleanup(urlopen_patch.stop) def patch_argparse(self): + self.arg_exit_mock = mock.Mock(spec=[]) + self.arg_error_mock = mock.Mock(spec=[]) + self.arg_exit_mock.side_effect = ExitException + self.arg_error_mock.side_effect = ExitException + argparse_patch = mock.patch.multiple( + 'argparse.ArgumentParser', + exit=self.arg_exit_mock, + error=self.arg_error_mock + ) + argparse_patch.start() + self.addCleanup(argparse_patch.stop) filetype_patch = mock.patch('argparse.FileType') self.filetype_mock = filetype_patch.start() @@ -122,6 +144,16 @@ def test_interface_checkip(self): ret_value = self.stdout_mock.getvalue() self.assertIn('127.0.0.1', ret_value) + def test_interface_checkip_bad_num(self): + self.urlopen_mock.side_effect = mock.mock_open(read_data="127.0.0.1\n") + self.assertEqual(argparse.ArgumentParser.exit, self.arg_exit_mock) + prog = client.Program() + with self.assertRaises(ExitException): + prog.run("checkip -n -2".split(), log=False) + + with self.assertRaises(ExitException): + prog.run("checkip -n 0".split(), log=False) + def test_interface_update(self): response_mock = mock.mock_open(read_data="127.0.0.6\n") self.urlopen_mock.side_effect = response_mock