Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(unit tests): add basic unit tests [DNS-1282] #3

Merged
merged 3 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test:
python -m unittest
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@ Either run as root, or set --config-dir, --work-dir, and --logs-dir to writeable
```

As explained by the error message, to be able write to `/var/log/letsencrypt/`, root permissions are needed. However, when running as a root (e.g `sudo certbot`), the global `certbot` package will be used and not the one from the virtual environment. The solution is to set `--logs-dir`, `--config-dir`, and `--work-dir` to a different folder for which the current user has write permissions.

## Testing

unit tests can be run using: `make test`
File renamed without changes.
2 changes: 1 addition & 1 deletion certbot_dns_ionos/ionos.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def get_existing_txt_acme_record(self, zone_id, record_name):

for record_item in records_response["items"]:
record_item_properties = record_item["properties"]
if record_item_properties and record_item_properties["name"] == record_name:
if record_item_properties and record_item_properties.get("name") == record_name:
return record_item

return None
187 changes: 187 additions & 0 deletions certbot_dns_ionos/test_ionos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import unittest
from unittest.mock import patch, Mock

from certbot import errors
from certbot_dns_ionos.ionos import _IONOSClient


test_domain = "test_domain.de"
test_record_name = "_acme-challenge.test_domain.de"
test_record_content = "123456789"
zone_id = "test"
record_id = "12356"


class TestIONOSClient(unittest.TestCase):
def setUp(self):
self.client = _IONOSClient("test_token")
self.mock_response = Mock()

def test_add_txt_record_with_non_ok_result_raises_exception(self):
self.mock_response.status_code = 401

with patch("requests.get", return_value=self.mock_response) as mock_get:
with self.assertRaises(errors.PluginError) as context:
self.client.add_txt_record(test_domain, test_record_name, test_record_content)
self.assertEqual(str(context.exception), "Received non OK status from IONOS API 401")
mock_get.assert_called_once()


def test_add_txt_record_find_zone_id_with_no_result_raises_exception(self):
self.mock_response.json.return_value = {"items": []}
self.mock_response.status_code = 200

with patch("requests.get", return_value=self.mock_response) as mock_get:
with self.assertRaises(errors.PluginError) as context:
self.client.add_txt_record(test_domain, test_record_name, test_record_content)
self.assertEqual(str(context.exception), "Domain not known")
mock_get.assert_called_once()

def test_add_txt_record_find_zone_id_with_unkown_zone_raises_exception(self):
self.mock_response.json.return_value = {"items": [{"id": "any", "properties":{"zoneName": "any"}}]}
self.mock_response.status_code = 200

with patch("requests.get", return_value=self.mock_response) as mock_get:
with self.assertRaises(errors.PluginError) as context:
self.client.add_txt_record(test_domain, test_record_name, test_record_content)
self.assertEqual(str(context.exception), "Domain not known")
mock_get.assert_called_once()

def test_add_txt_record_with_not_found_record_creates_record(self):
get_zones_response = Mock()
get_zones_response.status_code = 200
get_zones_response.json.return_value = {"items": [{"id": zone_id, "properties":{"zoneName": test_domain}}]}

get_records_response = Mock()
get_records_response.status_code = 202
get_records_response.json.return_value = {"items":[]}

responses = [get_zones_response, get_records_response]

insert_response = Mock()
insert_response.status_code = 202

with patch('requests.get', side_effect=responses) as mock_get:
with patch("requests.post", return_value=insert_response) as mock_post:
self.client.add_txt_record(test_domain, test_record_name, test_record_content)
assert len(mock_get.mock_calls) == 2
mock_post.assert_called()


def test_add_txt_record_with_exisiting_record_same_content_does_nothing(self):
get_zones_response = Mock()
get_zones_response.status_code = 200
get_zones_response.json.return_value = {"items": [{"id": zone_id, "properties":{"zoneName": test_domain}}]}

get_records_response = Mock()
get_records_response.status_code = 202
record_name_without_domain = test_record_name.replace("."+test_domain, "")
get_records_response.json.return_value = {"items":[{"id": record_id, "properties":{"name": record_name_without_domain,
"content":test_record_content}}]}
responses = [get_zones_response, get_records_response]

with patch('requests.get', side_effect=responses) as mock_get:
self.client.add_txt_record(test_domain, test_record_name, test_record_content)
assert len(mock_get.mock_calls) == 2

def test_add_txt_record_with_exisiting_record_different_updates_record(self):
get_zones_response = Mock()
get_zones_response.status_code = 200
get_zones_response.json.return_value = {"items": [{"id": zone_id, "properties":{"zoneName": test_domain}}]}

get_records_response = Mock()
get_records_response.status_code = 202
record_name_without_domain = test_record_name.replace("."+test_domain, "")
get_records_response.json.return_value = {"items":[{"id": record_id, "properties":{"name": record_name_without_domain, "content":"new content"}}]}

responses = [get_zones_response, get_records_response]

update_response = Mock()
update_response.status_code = 202

with patch('requests.get', side_effect=responses) as mock_get:
with patch("requests.put", return_value=update_response) as mock_put:
self.client.add_txt_record(test_domain, test_record_name, test_record_content)
assert mock_get.call_count == 2
mock_put.assert_called()

def test_delete_txt_record_find_zone_id_with_no_result_raises_exception(self):
self.mock_response.json.return_value = {"items": []}
self.mock_response.status_code = 200

with patch("requests.get", return_value=self.mock_response) as mock_get:
with self.assertRaises(errors.PluginError) as context:
self.client.del_txt_record(test_domain, test_record_name, test_record_content)
self.assertEqual(str(context.exception), "Domain not known")
mock_get.assert_called_once()

def test_delete_record_find_zone_id_with_unkown_zone_raises_exception(self):
self.mock_response.json.return_value = {"items": [{"id": "any", "properties":{"zoneName": "any"}}]}
self.mock_response.status_code = 200

with patch("requests.get", return_value=self.mock_response) as mock_get:
with self.assertRaises(errors.PluginError) as context:
self.client.del_txt_record(test_domain, test_record_name, test_record_content)
self.assertEqual(str(context.exception), "Domain not known")
mock_get.assert_called_once()

def test_delete_txt_record_with_not_found_record_does_nothing(self):
get_zones_response = Mock()
get_zones_response.status_code = 200
get_zones_response.json.return_value = {"items": [{"id": zone_id, "properties":{"zoneName": test_domain}}]}

get_records_response = Mock()
get_records_response.status_code = 202
get_records_response.json.return_value = {"items":[]}

responses = [get_zones_response, get_records_response]

with patch('requests.get', side_effect=responses) as mock_get:
with patch("requests.delete", return_value={}) as mock_delete:
self.client.del_txt_record(test_domain, test_record_name, test_record_content)
mock_delete.assert_not_called()
assert mock_get.call_count == 2

def test_delete_txt_record_with_existing_record_and_different_content_does_nothing(self):
get_zones_response = Mock()
get_zones_response.status_code = 200
get_zones_response.json.return_value = {"items": [{"id": zone_id, "properties":{"zoneName": test_domain}}]}

get_records_response = Mock()
get_records_response.status_code = 202
record_name_without_domain = test_record_name.replace("."+test_domain, "")
get_records_response.json.return_value = {"items":[{"id": record_id, "properties":{"name": record_name_without_domain,
"content":"unmatching requested content"}}]}

responses = [get_zones_response, get_records_response]

with patch('requests.get', side_effect=responses) as mock_get:
with patch("requests.delete", return_value={}) as mock_delete:
self.client.del_txt_record(test_domain, test_record_name, test_record_content)
mock_delete.assert_not_called()
assert mock_get.call_count == 2

def test_delete_txt_record_with_existing_record_and_different_content_does_nothing(self):
get_zones_response = Mock()
get_zones_response.status_code = 200
get_zones_response.json.return_value = {"items": [{"id": zone_id, "properties":{"zoneName": test_domain}}]}

get_records_response = Mock()
get_records_response.status_code = 202
record_name_without_domain = test_record_name.replace("."+test_domain, "")
get_records_response.json.return_value = {"items":[{"id": record_id, "properties":{"name": record_name_without_domain,
"content":test_record_content}}]}

responses = [get_zones_response, get_records_response]

delete_response = Mock()
delete_response.status_code = 202

with patch('requests.get', side_effect=responses) as mock_get:
with patch("requests.delete", return_value=delete_response) as mock_delete:
self.client.del_txt_record(test_domain, test_record_name, test_record_content)
assert mock_delete.call_count == 1
assert mock_get.call_count == 2

if __name__ == "__main__":
unittest.main()
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"certbot>=2.9.0",
"setuptools",
"requests",
#"mock",
#"requests-mock",
"mock",
]


Expand All @@ -30,5 +29,5 @@
"dns-ionos = certbot_dns_ionos.ionos:Authenticator"
]
},
test_suite="",
test_suite="certbot-dns-ionos",
)