diff --git a/requirements.txt b/requirements.txt index c20f389..b62d135 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ setuptools>=41.6.0 requests>=2.20.0,<3.0 certbot>=1.18.0,<4.0 dnspython>=2.0.0,<3.0 +responses~=0.25 diff --git a/tests/cert_client.py b/tests/cert_client.py new file mode 100644 index 0000000..13e6333 --- /dev/null +++ b/tests/cert_client.py @@ -0,0 +1,132 @@ +import unittest +from argparse import Namespace + +import responses +from certbot.configuration import NamespaceConfig +from certbot.errors import PluginError + +from certbot_dns_duckdns.cert.client import Authenticator + + +class TestCertClient(unittest.TestCase): + @responses.activate + def test_valid_auth(self): + api_token = "token" + domain = "example.duckdns.org" + txt_value = "ABCDEF" + + responses.get( + url=f"https://www.duckdns.org/update?token={api_token}&domains={domain}&txt={txt_value}", + body="OK", + ) + + namespace = Namespace( + duckdns_token=api_token, + duckdns_token_token_env="DUCKDNS_TOKEN", + duckdns_no_txt_restore=False, + config_dir="config_dir", + work_dir="work_dir", + logs_dir="logs_dir", + http01_port=80, + https_port=443, + domains=["example.duckdns.org"], + ) + config = NamespaceConfig(namespace) + + authenticator = Authenticator(config, name="duckdns") + + authenticator._perform( + domain=domain, validation_name="", validation=txt_value + ) + + @responses.activate + def test_invalid_auth(self): + api_token = "token" + domain = "example.duckdns.org" + txt_value = "ABCDEF" + + responses.get( + url=f"https://www.duckdns.org/update?token={api_token}&domains={domain}&txt={txt_value}", + body="OK", + ) + + namespace = Namespace( + duckdns_token=api_token + "invalid", + duckdns_token_token_env="DUCKDNS_TOKEN", + duckdns_no_txt_restore=False, + config_dir="config_dir", + work_dir="work_dir", + logs_dir="logs_dir", + http01_port=80, + https_port=443, + domains=["example.duckdns.org"], + ) + config = NamespaceConfig(namespace) + + authenticator = Authenticator(config, name="duckdns") + + with self.assertRaises(PluginError): + authenticator._perform( + domain=domain, validation_name="", validation=txt_value + ) + + @responses.activate + def test_invalid_duckdns_domain(self): + api_token = "token" + domain = "example.org" + txt_value = "ABCDEF" + + responses.get( + url=f"https://www.duckdns.org/update?token={api_token}&domains={domain}&txt={txt_value}", + body="OK", + ) + + namespace = Namespace( + duckdns_token=api_token, + duckdns_token_token_env="DUCKDNS_TOKEN", + duckdns_no_txt_restore=False, + config_dir="config_dir", + work_dir="work_dir", + logs_dir="logs_dir", + http01_port=80, + https_port=443, + domains=[domain], + ) + config = NamespaceConfig(namespace) + + authenticator = Authenticator(config, name="duckdns") + + with self.assertRaises(PluginError): + authenticator._perform( + domain=domain, validation_name="", validation=txt_value + ) + + @responses.activate + def test_cleanup(self): + api_token = "token" + domain = "example.duckdns.org" + txt_value = "ABCDEF" + + responses.get( + url=f"https://www.duckdns.org/update?token={api_token}&domains={domain}&txt=&clear=true", + body="OK", + ) + + namespace = Namespace( + duckdns_token=api_token, + duckdns_token_token_env="DUCKDNS_TOKEN", + duckdns_no_txt_restore=False, + config_dir="config_dir", + work_dir="work_dir", + logs_dir="logs_dir", + http01_port=80, + https_port=443, + domains=["example.duckdns.org"], + ) + config = NamespaceConfig(namespace) + + authenticator = Authenticator(config, name="duckdns") + + authenticator._cleanup( + domain=domain, validation_name="", validation=txt_value + ) \ No newline at end of file diff --git a/tests/cert_test.py b/tests/cert_test.py deleted file mode 100644 index 2d1a511..0000000 --- a/tests/cert_test.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -ATTENTION: -These tests are not meant for the normal test case, -as this tries to test the integration to the Certbot by using -a subprocess call and installing this package globally. - -You should not run this test unless you know exactly what you are doing. -""" -import os -import subprocess -import unittest - -from certbot.errors import PluginError - -from certbot_dns_duckdns.cert.client import Authenticator - -TEST_DOMAIN = os.environ.get("TEST_DOMAIN") -TEST_DUCKDNS_TOKEN = os.environ.get("TEST_DUCKDNS_TOKEN") - - -class CertbotPluginTests(unittest.TestCase): - - def test_invalid_token(self): - assert TEST_DOMAIN is not None and len(TEST_DOMAIN) > 0 - assert TEST_DUCKDNS_TOKEN is not None and len(TEST_DUCKDNS_TOKEN) > 0 - - class TestConfig(object): - test42_token = "securetoken42" - - auth = Authenticator(config=TestConfig(), name="test42") - with self.assertRaises(PluginError): - auth._perform(domain=TEST_DOMAIN, validation_name="test=42", validation="42") - - def test_empty_token(self): - assert TEST_DOMAIN is not None and len(TEST_DOMAIN) > 0 - assert TEST_DUCKDNS_TOKEN is not None and len(TEST_DUCKDNS_TOKEN) > 0 - - class TestConfig(object): - test42_token = "" - - auth = Authenticator(config=TestConfig(), name="test42") - with self.assertRaises(PluginError): - auth._perform(domain=TEST_DOMAIN, validation_name="test=42", validation="42") - - def test_none_token(self): - assert TEST_DOMAIN is not None and len(TEST_DOMAIN) > 0 - assert TEST_DUCKDNS_TOKEN is not None and len(TEST_DUCKDNS_TOKEN) > 0 - - class TestConfig(object): - test42_token = None - - auth = Authenticator(config=TestConfig(), name="test42") - with self.assertRaises(PluginError): - auth._perform(domain=TEST_DOMAIN, validation_name="test=42", validation="42") - - def test_invalid_domain(self): - assert TEST_DOMAIN is not None and len(TEST_DOMAIN) > 0 - assert TEST_DUCKDNS_TOKEN is not None and len(TEST_DUCKDNS_TOKEN) > 0 - - class TestConfig(object): - test42_token = TEST_DUCKDNS_TOKEN - - auth = Authenticator(config=TestConfig(), name="test42") - with self.assertRaises(PluginError): - auth._perform(domain="thisdomainsisnotvalid", validation_name="test=42", validation="42") - - def test_certificate(self): - assert TEST_DOMAIN is not None and len(TEST_DOMAIN) > 0 - assert TEST_DUCKDNS_TOKEN is not None and len(TEST_DUCKDNS_TOKEN) > 0 - - # check if certbot is installed - subprocess.check_output(["certbot", "--version"]) - # install certbot_dns_duckdns plugin - subprocess.check_output(["pip", "install", ".."]) - # check if certbot works properly with the dns plugin - subprocess.check_output(["certbot", - "certonly", - "--non-interactive", - "--agree-tos", - "--register-unsafely-without-email", - "--authenticator", - "dns-duckdns", - "--dns-duckdns-token", - TEST_DUCKDNS_TOKEN, - "--dns-duckdns-propagation-seconds", - "60", - "--staging", - "-d", - TEST_DOMAIN, - # change the output dirs to allow running test without root permission - "--work-dir", - "test_certbot/config", - "--config-dir", - "test_certbot/config", - "--logs-dir", - "test_certbot/logs"]) - - def test_certificate_no_txt_restore(self): - assert TEST_DOMAIN is not None and len(TEST_DOMAIN) > 0 - assert TEST_DUCKDNS_TOKEN is not None and len(TEST_DUCKDNS_TOKEN) > 0 - - # check if certbot is installed - subprocess.check_output(["certbot", "--version"]) - # install certbot_dns_duckdns plugin - subprocess.check_output(["pip", "install", ".."]) - # check if certbot works properly with the dns plugin - subprocess.check_output(["certbot", - "certonly", - "--non-interactive", - "--agree-tos", - "--register-unsafely-without-email", - "--authenticator", - "dns-duckdns", - "--dns-duckdns-no-txt-restore", - "--dns-duckdns-token", - TEST_DUCKDNS_TOKEN, - "--dns-duckdns-propagation-seconds", - "60", - "--staging", - "-d", - TEST_DOMAIN, - # change the output dirs to allow running test without root permission - "--work-dir", - "test_certbot/config", - "--config-dir", - "test_certbot/config", - "--logs-dir", - "test_certbot/logs"]) - - def test_wildcard_certificate(self): - assert TEST_DOMAIN is not None and len(TEST_DOMAIN) > 0 and TEST_DOMAIN[0] not in [".", "*"] - assert TEST_DUCKDNS_TOKEN is not None and len(TEST_DUCKDNS_TOKEN) > 0 - - wildcard_domain = "*.{}".format(TEST_DOMAIN) - - # check if certbot is installed - subprocess.check_output(["certbot", "--version"]) - # install certbot_dns_duckdns plugin - subprocess.check_output(["pip", "install", ".."]) - # check if certbot works properly with the dns plugin - subprocess.check_output(["certbot", - "certonly", - "--non-interactive", - "--agree-tos", - "--register-unsafely-without-email", - "--authenticator", - "dns-duckdns", - "--dns-duckdns-token", - TEST_DUCKDNS_TOKEN, - "--dns-duckdns-propagation-seconds", - "60", - "--staging", - "-d", - wildcard_domain, - # change the output dirs to allow running test without root permission - "--work-dir", - "test_certbot/config", - "--config-dir", - "test_certbot/config", - "--logs-dir", - "test_certbot/logs"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/duckdns_tests.py b/tests/duckdns_tests.py index 33bdbc3..dbd5547 100644 --- a/tests/duckdns_tests.py +++ b/tests/duckdns_tests.py @@ -1,114 +1,13 @@ -import os -import time import unittest -from dns import resolver -from dns.resolver import Resolver +from certbot_dns_duckdns.duckdns.client import DuckDNSClient, is_valid_duckdns_domain, is_valid_full_duckdns_domain -from certbot_dns_duckdns.cert.client import DEFAULT_PROPAGATION_SECONDS -from certbot_dns_duckdns.duckdns.client import DuckDNSClient, TXTUpdateError, NotValidDuckdnsTokenError, \ - NotValidDuckdnsDomainError, is_valid_duckdns_domain, is_valid_full_duckdns_domain - -TEST_DOMAIN = os.environ.get("TEST_DOMAIN") -TEST_DUCKDNS_TOKEN = os.environ.get("TEST_DUCKDNS_TOKEN") - -NAMESERVER = ["1.1.1.1"] - -assert TEST_DOMAIN is not None and len(TEST_DOMAIN) > 0 -assert TEST_DUCKDNS_TOKEN is not None and len(TEST_DUCKDNS_TOKEN) > 0 +TEST_DOMAIN = "example.duckdns.org" +TEST_DUCKDNS_TOKEN = "1234567890abcdef" class DuckDNSTests(unittest.TestCase): - def test_invalid_token(self): - duckdns_client = DuckDNSClient("42") - - with self.subTest(): - # test set txt record - with self.assertRaises(TXTUpdateError): - duckdns_client.set_txt_record(TEST_DOMAIN, "simple text") - - with self.subTest(): - # test clear txt record - with self.assertRaises(TXTUpdateError): - duckdns_client.clear_txt_record(TEST_DOMAIN) - - def test_empty_token(self): - with self.subTest(): - with self.assertRaises(NotValidDuckdnsTokenError): - duckdns_client = DuckDNSClient("") - - # explicitly set the token to an empty string - duckdns_client = DuckDNSClient("test") - duckdns_client._token = "" - - with self.subTest(): - # test set txt record - with self.assertRaises(TXTUpdateError): - duckdns_client.set_txt_record(TEST_DOMAIN, "simple text") - - with self.subTest(): - # test clear txt record - with self.assertRaises(TXTUpdateError): - duckdns_client.clear_txt_record(TEST_DOMAIN) - - def test_none_token(self): - with self.subTest(): - with self.assertRaises(NotValidDuckdnsTokenError): - duckdns_client = DuckDNSClient(None) - - # explicitly set the token to None - duckdns_client = DuckDNSClient("test") - duckdns_client._token = None - - with self.subTest(): - # test set txt record - with self.assertRaises(TXTUpdateError): - duckdns_client.set_txt_record(TEST_DOMAIN, "simple text") - - with self.subTest(): - # test clear txt record - with self.assertRaises(TXTUpdateError): - duckdns_client.clear_txt_record(TEST_DOMAIN) - - def test_wrong_domain(self): - duckdns_client = DuckDNSClient(TEST_DUCKDNS_TOKEN) - - with self.subTest(): - with self.assertRaises(TXTUpdateError): - duckdns_client.set_txt_record("thisdomainsiswrong", "simple text") - - with self.subTest(): - with self.assertRaises(TXTUpdateError): - duckdns_client.clear_txt_record("thisdomainsiswrong") - - def test_empty_domain(self): - duckdns_client = DuckDNSClient(TEST_DUCKDNS_TOKEN) - - with self.subTest(): - with self.assertRaises(NotValidDuckdnsDomainError): - duckdns_client.set_txt_record("", "simple text") - - with self.subTest(): - with self.assertRaises(NotValidDuckdnsDomainError): - duckdns_client.clear_txt_record("") - - def test_add_txt(self): - txt_record_text = "simple text" - - duckdns_client = DuckDNSClient(TEST_DUCKDNS_TOKEN) - - duckdns_client.set_txt_record(TEST_DOMAIN, txt_record_text) - # wait sometime for propagation of the txt update - print("wait for the txt record update to propagate...") - time.sleep(DEFAULT_PROPAGATION_SECONDS) - # get the set txt record from the specified nameserver - custom_resolver = Resolver() - custom_resolver.nameservers = NAMESERVER - txt_value = custom_resolver.resolve(TEST_DOMAIN, "TXT").response.answer[0][0].strings[0].decode("utf-8") - - self.assertEqual(txt_record_text, txt_value) - def test_get_validated_root_domain_hyphen_domain(self): domain = "test-example.duckdns.org" root_domain = DuckDNSClient.__get_validated_root_domain__(domain) @@ -166,27 +65,6 @@ def test_get_validated_root_domain_special_subdomains(self): root_domain = DuckDNSClient.__get_validated_root_domain__("-123ad-sdas--45-as-." + TEST_DOMAIN) self.assertEqual(root_domain, TEST_DOMAIN) - def test_clear_txt(self): - duckdns_client = DuckDNSClient(TEST_DUCKDNS_TOKEN) - - txt_record_text = "simple text" - duckdns_client.set_txt_record(TEST_DOMAIN, txt_record_text) - # wait sometime for propagation of the txt update - print("wait for the txt record update to propagate...") - time.sleep(DEFAULT_PROPAGATION_SECONDS) - - duckdns_client.clear_txt_record(TEST_DOMAIN) - # wait sometime for propagation of the txt update - print("wait for the txt record update to propagate...") - time.sleep(DEFAULT_PROPAGATION_SECONDS) - - # get the cleared txt record from the specified nameserver - custom_resolver = resolver.Resolver() - custom_resolver.nameservers = NAMESERVER - - txt_value = custom_resolver.resolve(TEST_DOMAIN, "TXT").response.answer[0][0].strings[0].decode("utf-8") - - self.assertEqual("", txt_value) def test_is_valid_duckdns_domain(self): with self.subTest():