diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..3cd9d09 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,13 @@ +# .coveragerc to control coverage.py +[run] +branch = True + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: diff --git a/.gitignore b/.gitignore index 5c49a76..2ce8916 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,20 @@ -*.pyc +*.py[co] +# packages build dist -*egg-info +.eggs +*.egg-info +*.egg +# tests/coverage +.tox +.coverage + +# IDE/SO files *~ .DS_Store .idea .project .pydevproject .settings - diff --git a/.travis.yml b/.travis.yml index 90b1c04..1497645 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,24 +7,25 @@ before_install: language: python -python: - - 2.7 - - 3.3 - - 3.4 - - pypy - - pypy3 - -matrix: - allow_failures: - - python: pypy - - python: pypy3 - fast_finish: true +env: + - TOXENV=py26 + - TOXENV=py27 + - TOXENV=py32 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=pypy + - TOXENV=pypy3 + - TOXENV=pep8 install: - - pip install coveralls + - pip install tox coveralls -script: - - coverage run --source=noipy setup.py test +script: + - tox after_success: - coveralls + +notifications: + email: + on_success: never diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 010d6a7..875668a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,12 @@ Changelog ========= +1.4.0 (2015-04-25) +------------------ + +- Using the awesome `Requests HTTP library `_ +- Using `tox `_ in order to ease test against multiple Python versions + 1.3.1 (2014-12-19) ------------------ diff --git a/README.rst b/README.rst index 8088430..9993431 100644 --- a/README.rst +++ b/README.rst @@ -38,6 +38,8 @@ To install **noipy**, simply: $ pip install noipy +**Note**: **noipy** will also install the `Requests HTTP library `_ if you haven't yet. + Usage ----- @@ -124,15 +126,42 @@ If you have any enhancement suggestions or find a bug, please: #. Open an `issue `_ #. `Fork `_ the project -#. Do your magic (+ `PEP8 `_ + test) +#. Do your magic +#. Please, `PEP8 `_ and test your code #. Is everything working? Send a `pull request `_ Running tests ~~~~~~~~~~~~~ +First, install tests dependencies (`tox `_ +and `flake8 `_): + +.. code-block:: bash + + $ pip install -r dev-requirements.txt + + +To test against all supported Python versions (if you have them installed): + +.. code-block:: bash + + $ tox + + +Or you can to test against a specific version: + +.. code-block:: bash + + $ tox -e {version} + +Where ``{version}`` can be ``py26``, ``py27``, ``py33``, ``py34``, ``pypy`` and ``pypy3``. + + +Don't forget to run ``pep8``: + .. code-block:: bash - $ python setup.py test + $ tox -e pep8 Copyright & License diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..15490d9 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,3 @@ +flake8>=2.3 +tox>=1.8.1 +requests>=2.0 \ No newline at end of file diff --git a/noipy/__init__.py b/noipy/__init__.py index 7e7902a..b85627f 100644 --- a/noipy/__init__.py +++ b/noipy/__init__.py @@ -9,7 +9,7 @@ """ __title__ = "noipy" -__version_info__ = ('1', '3', '1') +__version_info__ = ('1', '4', '0') __version__ = ".".join(__version_info__) __author__ = "Pablo O Vieira" __email__ = "email@povieira.com" diff --git a/noipy/authinfo.py b/noipy/authinfo.py index ca9814b..d684ca4 100644 --- a/noipy/authinfo.py +++ b/noipy/authinfo.py @@ -11,7 +11,7 @@ import base64 NOIPY_CONFIG = ".noipy" -DEFAULT_CONFIG_LOCATION = os.path.expanduser("~") +DEFAULT_CONFIG_DIR = os.path.expanduser("~") class ApiAuth(object): @@ -50,7 +50,7 @@ def __eq__(self, other): return str(self) == str(other) -def store(auth, provider, config_location=DEFAULT_CONFIG_LOCATION): +def store(auth, provider, config_location=DEFAULT_CONFIG_DIR): """Store auth info in file for specified provider """ auth_file = None @@ -85,7 +85,7 @@ def store(auth, provider, config_location=DEFAULT_CONFIG_LOCATION): raise e -def load(provider, config_location=DEFAULT_CONFIG_LOCATION): +def load(provider, config_location=DEFAULT_CONFIG_DIR): """Load provider specific auth info from file """ auth = None @@ -105,7 +105,7 @@ def load(provider, config_location=DEFAULT_CONFIG_LOCATION): return auth -def exists(provider, config_location=DEFAULT_CONFIG_LOCATION): +def exists(provider, config_location=DEFAULT_CONFIG_DIR): """Check whether provider info is already stored """ config_dir = os.path.join(config_location, NOIPY_CONFIG) diff --git a/noipy/dnsupdater.py b/noipy/dnsupdater.py index 7af2217..c77c2d0 100755 --- a/noipy/dnsupdater.py +++ b/noipy/dnsupdater.py @@ -7,13 +7,10 @@ from __future__ import print_function -try: - import urllib.request as urllib2 -except ImportError: - import urllib2 - import re +import requests + AVAILABLE_PLUGINS = { 'noip': 'NoipDnsUpdater', 'dyn': 'DynDnsUpdater', @@ -24,6 +21,27 @@ DEFAULT_PLUGIN = 'generic' +error_messages = { + 'badauth': "ERROR: Invalid username or password (badauth).", + 'nochg': "ERROR: Invalid username or password (nochg).", + '401': "ERROR: Invalid username or password (401).", + '403': "ERROR: Invalid username or password (403).", + '!donator': "ERROR: Update request include a feature that is not " + "available to informed user.", + 'notfqdn': "ERROR: The hostname specified is not a fully-qualified domain" + " name (not in the form hostname.dyndns.org or domain.com).", + 'nohost': "ERROR: Hostname specified does not exist in this user account.", + 'numhost': "ERROR: Too many hosts (more than 20) specified in an update. " + "Also returned if trying to update a round robin (which is " + "not allowed).", + 'abuse': "ERROR: Username/hostname is blocked due to update abuse.", + 'badagent': "ERROR: User agent not sent or HTTP method not permitted.", + 'dnserr': "ERROR: DNS error encountered.", + '911': "ERROR: Problem on server side. Retry update in a few minutes.", + 'KO': "ERROR: Hostname and/or token incorrect.", +} + + class DnsUpdaterPlugin(object): """ Base class for any DDNS updater """ @@ -37,7 +55,7 @@ def __init__(self, auth, hostname, options=None): self._auth = auth self._hostname = hostname self._options = {} if options is None else options - self.last_status_code = '' + self.last_ddns_response = "" @property def auth(self): @@ -61,71 +79,45 @@ def update_dns(self, new_ip): """Call No-IP API based on dict login_info and return the status code. """ + headers = None if self.auth_type == 'T': api_call_url = self._get_base_url().format(hostname=self.hostname, token=self.auth.token, ip=new_ip) - request = urllib2.Request(api_call_url) else: api_call_url = self._get_base_url().format(hostname=self.hostname, ip=new_ip) - request = urllib2.Request(api_call_url) - request.add_header('Authorization', 'Basic %s' % - self.auth.base64key.decode('utf-8')) + headers = { + 'Authorization': 'Basic %s' % + self.auth.base64key.decode('utf-8'), + } + + r = requests.get(api_call_url, headers=headers) + self.last_ddns_response = str(r.text) - try: - response = urllib2.urlopen(request) - self.last_status_code = response.read().decode('utf-8') - except urllib2.HTTPError as e: - self.last_status_code = str(e.code) + return r.status_code, r.text @property def status_message(self): """Return friendly response from API based on response code. """ msg = None - if self.last_status_code in ['badauth', 'nochg', '401', '403']: - msg = "ERROR: Invalid username or password (%s)." % \ - self.last_status_code - elif 'good' in self.last_status_code \ - or 'nochg' in self.last_status_code: - ip = re.search(r'(\d{1,3}\.?){4}', self.last_status_code).group() - if 'good' in self.last_status_code: + if self.last_ddns_response in error_messages.keys(): + msg = error_messages.get(self.last_ddns_response) + elif self.last_ddns_response == 'OK': + msg = "SUCCESS: DNS hostname successfully updated." + elif 'good' in self.last_ddns_response \ + or 'nochg' in self.last_ddns_response: + ip = re.search(r'(\d{1,3}\.?){4}', self.last_ddns_response).group() + if 'good' in self.last_ddns_response: msg = "SUCCESS: DNS hostname IP (%s) successfully updated." % \ ip else: msg = "SUCCESS: IP address (%s) is up to date, nothing was " \ "changed. Additional 'nochg' updates may be considered" \ " abusive." % ip - elif self.last_status_code == '!donator': - msg = "ERROR: Update request include a feature that is not " \ - "available to informed user." - elif self.last_status_code == 'notfqdn': - msg = "ERROR: The hostname specified is not a fully-qualified " \ - "domain name (not in the form hostname.dyndns.org or " \ - "domain.com)." - elif self.last_status_code == 'nohost': - msg = "ERROR: Hostname specified does not exist in this user " \ - "account." - elif self.last_status_code == 'numhost': - msg = "ERROR: Too many hosts (more than 20) specified in an " \ - "update. Also returned if trying to update a round robin " \ - "(which is not allowed)." - elif self.last_status_code == 'abuse': - msg = "ERROR: Username/hostname is blocked due to update abuse." - elif self.last_status_code == 'badagent': - msg = "ERROR: User agent not sent or HTTP method not permitted." - elif self.last_status_code == 'dnserr': - msg = "ERROR: DNS error encountered." - elif self.last_status_code == '911': - msg = "ERROR: Problem on server side. Retry update in a few " \ - "minutes." - elif self.last_status_code == 'OK': - msg = "SUCCESS: DNS hostname successfully updated." - elif self.last_status_code == 'KO': - msg = "ERROR: Hostname and/or token incorrect." else: - msg = "WARNING: Ooops! Something went wrong !!!" + msg = "ERROR: Ooops! Something went wrong !!!" return msg diff --git a/noipy/main.py b/noipy/main.py index 067b0fe..75796dc 100644 --- a/noipy/main.py +++ b/noipy/main.py @@ -7,16 +7,13 @@ from __future__ import print_function -try: - import urllib.request as urllib -except ImportError: - import urllib - import argparse -import sys -import re import getpass -import socket +import re +import sys + +from noipy import utils + try: from . import dnsupdater @@ -27,12 +24,6 @@ import authinfo __version__ = "0.TEST" -try: - # Python 3 capability - input = raw_input -except NameError: - pass - EXECUTION_RESULT_OK = 0 EXECUTION_RESULT_NOK = 1 @@ -48,29 +39,6 @@ r'(?:/?|[/?]\S+)$', re.IGNORECASE) -def get_ip(): - """Return the machine external IP. - """ - - page = urllib.urlopen('http://checkip.dyndns.org') - content = page.read().decode('utf-8') - - return re.search(r'(\d{1,3}\.?){4}', content).group() - - -def get_dns_ip(dnsname): - """Return the machine's current IP address in DNS. - """ - try: - return socket.gethostbyname(dnsname) - except socket.error: - return "" - - -def print_version(): - print("== noipy DDNS updater tool v%s ==" % __version__) - - def execute_update(args): """Execute the update based on command line args and returns a tuple with Exit Code and the processing Status Massage @@ -83,7 +51,6 @@ def execute_update(args): exec_result = EXECUTION_RESULT_NOK update_ddns = False - auth = None if args.store: # --store argument if args.usertoken: if args.password: @@ -92,10 +59,10 @@ def execute_update(args): auth = authinfo.ApiAuth(args.usertoken) else: if provider_class.auth_type == 'T': - token = input("Paste your auth token: ") + token = utils.get_input("Paste your auth token: ") auth = authinfo.ApiAuth(usertoken=token) else: - username = input("Type your username: ") + username = utils.get_input("Type your username: ") password = getpass.getpass("Type your password: ") auth = authinfo.ApiAuth(usertoken=username, password=password) @@ -136,7 +103,7 @@ def execute_update(args): if update_ddns and args.provider == 'generic': if args.url: if not URL_RE.match(args.url): - process_message = "Malformed url" + process_message = "Malformed URL." exec_result = EXECUTION_RESULT_NOK update_ddns = False else: @@ -148,8 +115,11 @@ def execute_update(args): update_ddns = False if update_ddns: - ip_address = args.ip if args.ip else get_ip() - if ip_address == get_dns_ip(args.hostname): + ip_address = args.ip if args.ip else utils.get_ip() + if not ip_address: + process_message = "Unable to get IP address. Check connection." + exec_result = False + elif ip_address == utils.get_dns_ip(args.hostname): process_message = "No update required." else: updater = provider_class(auth, args.hostname, updater_options) @@ -180,9 +150,9 @@ def create_parser(): "update the hostname if it is provided", action='store_true') parser.add_argument('-c', '--config', - help="path to noipy config location (default: %s)" % - authinfo.DEFAULT_CONFIG_LOCATION, - default=authinfo.DEFAULT_CONFIG_LOCATION) + help="noipy config directory (default: %s)" % + authinfo.DEFAULT_CONFIG_DIR, + default=authinfo.DEFAULT_CONFIG_DIR) parser.add_argument('ip', metavar='IP_ADDRESS', nargs='?', help="New host IP address. If not provided, current " "external IP address will be used.") @@ -191,7 +161,7 @@ def create_parser(): def main(): - print_version() + print("== noipy DDNS updater tool v%s ==" % __version__) parser = create_parser() args = parser.parse_args() diff --git a/noipy/utils.py b/noipy/utils.py new file mode 100644 index 0000000..c4b2934 --- /dev/null +++ b/noipy/utils.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# noipy.utils +# Copyright (c) 2013 Pablo O Vieira (povieira) +# See README.rst and LICENSE for details. + +import socket +import sys + +import requests + + +def get_input(message): + if sys.version_info[:2] < (3, 0): + return input(message) + else: + return raw_input(message) + + +def get_ip(): + """Return machine's origin IP address. + """ + try: + r = requests.get("http://httpbin.org/ip") + return r.json()['origin'] if r.status_code == 200 else None + except requests.exceptions.ConnectionError: + return None + + +def get_dns_ip(dnsname): + """Return machine's current IP address in DNS. + """ + try: + return socket.gethostbyname(dnsname) + except socket.error: + return None diff --git a/setup.cfg b/setup.cfg index 0a8df87..6b68236 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,7 @@ [wheel] -universal = 1 \ No newline at end of file +universal = 1 + +[check-manifest] +ignore = + .travis.yml + tox.ini diff --git a/setup.py b/setup.py index 1c689ac..f447dde 100644 --- a/setup.py +++ b/setup.py @@ -5,47 +5,61 @@ # Copyright (c) 2013 Pablo O Vieira (povieira) # See README.rst and LICENSE for details. -from setuptools import setup, find_packages +from setuptools import setup +import sys + from noipy import __version__, __author__, __email__, __license__ -with open('README.rst') as f: + +install_requires = [ + "requests>=2.0", +] + +if sys.version_info[:2] < (2, 7): + install_requires.append("argparse") + + +with open("README.rst") as f: readme = f.read() -with open('CHANGELOG.rst') as f: +with open("CHANGELOG.rst") as f: changelog = f.read() setup( - name='noipy', + name="noipy", version=__version__, - description='Command line update for No-IP and Dyn DDNS Update API', - long_description=readme + '\n\n' + changelog, + description="Command line tool for DDNS IP address updating.", + long_description=readme + "\n\n" + changelog, license=__license__, author=__author__, author_email=__email__, - url='https://github.com/povieira/noipy', - packages=find_packages(), - keywords=['no-ip', 'dyndns', 'duckdns', 'ddns', 'api'], + url="https://github.com/povieira/noipy", + packages=["noipy"], + install_requires=install_requires, + keywords=["no-ip", "dyndns", "duckdns", "ddns", "api"], classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: End Users/Desktop', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Internet :: Name Service (DNS)', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Internet :: Name Service (DNS)", ], entry_points={ - 'console_scripts': [ - 'noipy = noipy.main:main', + "console_scripts": [ + "noipy = noipy.main:main", ], }, zip_safe=True, - test_suite='test.test_noipy' + test_suite="test.test_noipy" ) diff --git a/test/test_noipy.py b/test/test_noipy.py index 22f73f5..82efb57 100644 --- a/test/test_noipy.py +++ b/test/test_noipy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# tests +# test.test_noipy # Copyright (c) 2013 Pablo O Vieira (povieira) # See README.rst and LICENSE for details. @@ -13,6 +13,8 @@ from noipy import authinfo from noipy import dnsupdater from noipy import main +from noipy import utils + VALID_IP_REGEX = r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25' \ r'[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4]' \ @@ -29,14 +31,15 @@ def tearDown(self): if os.path.exists(self.test_dir): shutil.rmtree(self.test_dir) - def test_sanity(self): - """Tests the sanity of the unit testing framework and if we can - import all we need to work + def test_get_ip(self): + ip = utils.get_ip() + + self.assertTrue(re.match(VALID_IP_REGEX, ip), 'get_ip() failed.') - * From https://github.com/rbanffy/testable_appengine (thanks, @rbanffy) - """ - self.assertTrue(True, "Oops! Sanity test failed! Did we take the" - " blue pill?") + def test_get_dns_ip(self): + ip = utils.get_dns_ip('localhost') + + self.assertTrue(ip == '127.0.0.1', 'get_dns_ip() failed.') class PluginsTest(unittest.TestCase): @@ -113,6 +116,21 @@ def test_generic_plugin(self): self.assertTrue(status_message.startswith("ERROR:"), "Status message should be an 'ERROR'") + def test_generic_plugin_malformed_url(self): + cmd_args = ['-u', 'username', '-p', 'password', + '--url', 'abced', + '--provider', 'generic', + '-n', 'noipy.no-ip.org', self.test_ip] + + args = self.parser.parse_args(cmd_args) + result, status_message = main.execute_update(args) + + self.assertTrue(result == main.EXECUTION_RESULT_NOK, + "An error should be flagged when --provider is " + "'generic' and URL is malformed.") + self.assertTrue(status_message == "Malformed URL.", + "Status message should be an 'Malformed URL.'") + class AuthInfoTest(unittest.TestCase): @@ -141,7 +159,7 @@ def test_auth_get_instance_token(self): self.assertEqual(auth1.token, auth2.token, 'ApiAuth.token fail.') def test_store_and_load_auth_info(self): - cmd_args = ['--store', '-u', 'username', '-p', 'password', + cmd_args = ['--store', '-u', "username", '-p', "password", '--provider', 'noip', '-c', self.test_dir, self.test_ip] # store @@ -153,6 +171,7 @@ def test_store_and_load_auth_info(self): self.assertTrue(status_message == "Auth info stored.", "Status message should be an 'Auth info stored.'") + # load cmd_args = ['--provider', 'noip', '-n', 'noipy.no-ip.org', '-c', self.test_dir, self.test_ip] @@ -186,16 +205,6 @@ def test_cmd_line_no_args(self): "Status message should start with 'Warning: The hostname to be " "updated must be provided.'") - def test_get_ip(self): - ip = main.get_ip() - - self.assertTrue(re.match(VALID_IP_REGEX, ip), 'get_ip() failed.') - - def test_get_dns_ip(self): - ip = main.get_dns_ip('localhost') - - self.assertTrue(ip == '127.0.0.1', 'get_dns_ip() failed.') - def test_unchanged_ip(self): cmd_args = ['-u', 'username', '-p', 'password', '--url', 'https://dynupdate.no-ip.com/nic/update', @@ -232,21 +241,21 @@ def test_dns_plugin_status_message(self): plugin = dnsupdater.DnsUpdaterPlugin(auth, hostname) # badauth code - plugin.last_status_code = 'badauth' + plugin.last_ddns_response = 'badauth' expected_message = "ERROR: Invalid username or password (%s)." \ - % plugin.last_status_code + % plugin.last_ddns_response self.assertTrue(plugin.status_message == expected_message, "Expected 'badauth' status code.") # good code - plugin.last_status_code = 'good 1.1.1.1' + plugin.last_ddns_response = 'good 1.1.1.1' expected_message = "SUCCESS: DNS hostname IP (1.1.1.1) successfully " \ "updated." self.assertTrue(plugin.status_message == expected_message, "Expected 'good <1.1.1.1>' status code.") # nochg code - plugin.last_status_code = 'nochg 1.1.1.1' + plugin.last_ddns_response = 'nochg 1.1.1.1' expected_message = "SUCCESS: IP address (1.1.1.1) is up to date, " \ "nothing was changed. Additional 'nochg' updates " \ "may be considered abusive." @@ -254,14 +263,14 @@ def test_dns_plugin_status_message(self): "Expected 'nochg <1.1.1.1>' status code.") # !donator code - plugin.last_status_code = '!donator' + plugin.last_ddns_response = '!donator' expected_message = "ERROR: Update request include a feature that is " \ "not available to informed user." self.assertTrue(plugin.status_message == expected_message, "Expected '!donator' status code.") # notfqdn code - plugin.last_status_code = 'notfqdn' + plugin.last_ddns_response = 'notfqdn' expected_message = "ERROR: The hostname specified is not a " \ "fully-qualified domain name (not in the form " \ "hostname.dyndns.org or domain.com)." @@ -269,14 +278,14 @@ def test_dns_plugin_status_message(self): "Expected 'notfqdn' status code.") # nohost code - plugin.last_status_code = 'nohost' + plugin.last_ddns_response = 'nohost' expected_message = "ERROR: Hostname specified does not exist in this" \ " user account." self.assertTrue(plugin.status_message == expected_message, "Expected 'nohost' status code.") # numhost code - plugin.last_status_code = 'numhost' + plugin.last_ddns_response = 'numhost' expected_message = "ERROR: Too many hosts (more than 20) specified " \ "in an update. Also returned if trying to update " \ "a round robin (which is not allowed)." @@ -284,47 +293,47 @@ def test_dns_plugin_status_message(self): "Expected 'numhost' status code.") # abuse code - plugin.last_status_code = 'abuse' + plugin.last_ddns_response = 'abuse' expected_message = "ERROR: Username/hostname is blocked due to " \ "update abuse." self.assertTrue(plugin.status_message == expected_message, "Expected 'abuse' status code.") # badagent code - plugin.last_status_code = 'badagent' + plugin.last_ddns_response = 'badagent' expected_message = "ERROR: User agent not sent or HTTP method not " \ "permitted." self.assertTrue(plugin.status_message == expected_message, "Expected 'badagent' status code.") # dnserr code - plugin.last_status_code = 'dnserr' + plugin.last_ddns_response = 'dnserr' expected_message = "ERROR: DNS error encountered." self.assertTrue(plugin.status_message == expected_message, "Expected 'dnserr' status code.") # 911 code - plugin.last_status_code = '911' + plugin.last_ddns_response = '911' expected_message = "ERROR: Problem on server side. Retry update in a" \ " few minutes." self.assertTrue(plugin.status_message == expected_message, "Expected '911' status code.") # OK code - plugin.last_status_code = 'OK' + plugin.last_ddns_response = 'OK' expected_message = "SUCCESS: DNS hostname successfully updated." self.assertTrue(plugin.status_message == expected_message, "Expected 'OK' status code.") # KO code - plugin.last_status_code = 'KO' + plugin.last_ddns_response = 'KO' expected_message = "ERROR: Hostname and/or token incorrect." self.assertTrue(plugin.status_message == expected_message, "Expected 'KO' status code.") # Unknown code - plugin.last_status_code = 'UNKNOWN_CODE' - expected_message = "WARNING: Ooops! Something went wrong !!!" + plugin.last_ddns_response = 'UNKNOWN_CODE' + expected_message = "ERROR: Ooops! Something went wrong !!!" self.assertTrue(plugin.status_message == expected_message, "Expected 'Ooops' warning message.") diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..11425b8 --- /dev/null +++ b/tox.ini @@ -0,0 +1,18 @@ +[tox] +envlist = py26,py27,py33,py34,pypy,pypy3,pep8 + +[testenv] +deps = coverage + +commands = + python --version + coverage run --source noipy setup.py test + coverage report -m + +[testenv:pep8] +deps = flake8 +commands = flake8 --statistics noipy/ test/ + +[flake8] +exclude = .tox,*.egg,build +select = E,W,F \ No newline at end of file