#!/usr/bin/env python
# -*- coding: utf-8 -*-

# test.test_noipy
# Copyright (c) 2013 Pablo O Vieira (povieira)
# See README.rst and LICENSE for details.

import unittest
import os
import re
import shutil

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]' \
                 r'[0-9]|25[0-5])$'


class SanityTest(unittest.TestCase):

    def setUp(self):
        self.parser = main.create_parser()
        self.test_dir = os.path.join(os.path.expanduser("~"), "noipy_test")

    def tearDown(self):
        if os.path.exists(self.test_dir):
            shutil.rmtree(self.test_dir)

    def test_get_ip(self):
        ip = utils.get_ip()

        self.assertTrue(re.match(VALID_IP_REGEX, ip), 'get_ip() failed.')

    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):

    def setUp(self):
        self.parser = main.create_parser()
        self.test_ip = "10.1.2.3"

    def tearDown(self):
        pass

    def test_noip_plugin(self):
        cmd_args = ['-u', 'username', '-p', 'password',
                    '--provider', 'noip',
                    '-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_OK,
                        "Update with 'No-IP' provider failed.")

        self.assertTrue(status_message.startswith("ERROR:"),
                        "Status message should be an 'ERROR'")

    def test_dyndns_plugin(self):
        cmd_args = ['-u', 'test', '-p', 'test',
                    '--provider', 'dyn',
                    '-n', 'test.dyndns.org', self.test_ip]

        args = self.parser.parse_args(cmd_args)
        result, status_message = main.execute_update(args)

        self.assertTrue(result == main.EXECUTION_RESULT_OK,
                        "Update with 'DynDNS' provider failed.")

        self.assertTrue(status_message.startswith("SUCCESS:"),
                        "Status message should be 'SUCCESS'")

    def test_duckdns_plugin(self):
        cmd_args = ['-u', '1234567890ABC',
                    '--provider', 'duck',
                    '-n', 'noipy.duckdns.org', self.test_ip]

        args = self.parser.parse_args(cmd_args)
        result, status_message = main.execute_update(args)

        self.assertTrue(result == main.EXECUTION_RESULT_OK,
                        "Update with 'DuckDNS' provider failed.")

        self.assertTrue(status_message.startswith("ERROR:"),
                        "Status message should be an 'ERROR'")

    def test_generic_plugin(self):
        cmd_args = ['--provider', 'generic']

        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 not specified")

        cmd_args = ['-u', 'username', '-p', 'password',
                    '--url', 'https://dynupdate.no-ip.com/nic/update',
                    '--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_OK,
                        "Update with 'No-IP' using generic provider failed.")
        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):

    def setUp(self):
        self.parser = main.create_parser()
        self.test_ip = "10.1.2.3"
        self.test_dir = os.path.join(os.path.expanduser("~"), "noipy_test")

    def tearDown(self):
        if os.path.exists(self.test_dir):
            shutil.rmtree(self.test_dir)

    def test_auth_get_instance_password(self):
        auth1 = authinfo.ApiAuth("username", "password")
        auth2 = authinfo.ApiAuth.get_instance(b"dXNlcm5hbWU6cGFzc3dvcmQ=")

        self.assertEqual(auth1, auth2, "ApiAuth.get_instance fail for "
                                       "password.")

    def test_auth_get_instance_token(self):
        token = "1234567890ABCDEFG"
        auth1 = authinfo.ApiAuth(usertoken=token)
        auth2 = authinfo.ApiAuth.get_instance(b"MTIzNDU2Nzg5MEFCQ0RFRkc6")

        self.assertEqual(auth1, auth2, 'ApiAuth.get_instance fail for token.')
        self.assertEqual(auth1.token, auth2.token, 'ApiAuth.token fail.')

    def test_store_and_load_auth_info(self):
        cmd_args = ['--store', '-u', "username", '-p', "password",
                    '--provider', 'noip', '-c', self.test_dir, self.test_ip]

        # store
        args = self.parser.parse_args(cmd_args)
        result, status_message = main.execute_update(args)

        self.assertTrue(result == main.EXECUTION_RESULT_OK,
                        "Error storing auth info")

        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]
        args = self.parser.parse_args(cmd_args)
        result, status_message = main.execute_update(args)

        self.assertTrue(result == main.EXECUTION_RESULT_OK,
                        "Error loading auth info")


class GeneralTest(unittest.TestCase):

    def setUp(self):
        self.parser = main.create_parser()

    def tearDown(self):
        pass

    def test_cmd_line_no_args(self):
        cmd_args = []

        args = self.parser.parse_args(cmd_args)
        result, status_message = main.execute_update(args)

        self.assertTrue(result == main.EXECUTION_RESULT_NOK,
                        "Execution without args failed.")

        self.assertTrue(
            status_message.startswith("Warning: The hostname to be updated "
                                      "must be provided."),
            "Status message should start with 'Warning: The hostname to be "
            "updated must be provided.'")

    def test_unchanged_ip(self):
        cmd_args = ['-u', 'username', '-p', 'password',
                    '--url', 'https://dynupdate.no-ip.com/nic/update',
                    '--provider', 'generic',
                    '-n', 'localhost', '127.0.0.1']

        args = self.parser.parse_args(cmd_args)
        result, status_message = main.execute_update(args)

        self.assertTrue(result == main.EXECUTION_RESULT_OK,
                        "Update with unchanged IP failed.")
        self.assertTrue(status_message == "No update required.",
                        "Status message should be 'No update required'")

    def test_not_implemented_plugin(self):
        auth = authinfo.ApiAuth('username', 'password')
        hostname = "hostname"
        plugin = dnsupdater.DnsUpdaterPlugin(auth, hostname)
        try:
            plugin.update_dns("10.1.1.1")
            self.fail("Not implemented plugin should fail: "
                      "'NoneType' object has no attribute 'format'")
        except AttributeError as e:
            self.assertTrue(str(e) == "'NoneType' object has no attribute "
                                      "'format'",
                            "_get_base_url() should return 'NoneType'")
        except Exception as e:
            self.fail("_get_base_url() should return 'AttributeError'. "
                      "Got %s:%s" % (type(e).__name__, e))

    def test_dns_plugin_status_message(self):
        auth = authinfo.ApiAuth('username', 'password')
        hostname = "hostname"
        plugin = dnsupdater.DnsUpdaterPlugin(auth, hostname)

        # badauth code
        plugin.last_ddns_response = 'badauth'
        expected_message = "ERROR: Invalid username or password (%s)." \
                           % plugin.last_ddns_response
        self.assertTrue(plugin.status_message == expected_message,
                        "Expected 'badauth' status code.")

        # good <IP> code
        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 <IP> code
        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."
        self.assertTrue(plugin.status_message == expected_message,
                        "Expected 'nochg <1.1.1.1>' status code.")

        # !donator code
        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_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)."
        self.assertTrue(plugin.status_message == expected_message,
                        "Expected 'notfqdn' status code.")

        # nohost code
        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_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)."
        self.assertTrue(plugin.status_message == expected_message,
                        "Expected 'numhost' status code.")

        # abuse code
        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_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_ddns_response = 'dnserr'
        expected_message = "ERROR: DNS error encountered."
        self.assertTrue(plugin.status_message == expected_message,
                        "Expected 'dnserr' status code.")

        # 911 code
        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_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_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_ddns_response = 'UNKNOWN_CODE'
        expected_message = "ERROR: Ooops! Something went wrong !!!"
        self.assertTrue(plugin.status_message == expected_message,
                        "Expected 'Ooops' warning message.")

if __name__ == "__main__":
    unittest.main()
