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

Issues/15 #36

Merged
merged 21 commits into from
Jul 29, 2017
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
9 changes: 6 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ jobs:
- run:
name: install dependencies
command: |
sudo pip install twine wheel pypandoc coverage yapf flake8 sewer
flake8 .
sudo pip install twine wheel pypandoc coverage yapf flake8 sewer mock

# run tests!
- run:
name: run tests
command: |
python sewer/tests/test_ACMEclient.py
find . -type f -name \*.pyc -delete | echo
coverage erase
coverage run --omit="*tests*,*.virtualenvs/*,*__init__*,*/usr/local/lib/python2.7/dist-packages*" -m unittest discover
coverage report --show-missing --fail-under=75
flake8 .

# run make upload

Expand Down
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[flake8]
max-line-length = 150
ignore = E125
ignore = E125, E123
exclude =
# No need to traverse our git directory
.git,
Expand Down
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ uploadprod:
@python setup.py bdist_wheel
@twine upload dist/*
@sudo pip install -U sewer

test:
@find . -type f -name \*.pyc -delete | echo
@coverage erase
@coverage run --omit="*tests*,*.virtualenvs/*,*__init__*,*/usr/local/lib/python2.7/dist-packages*" -m unittest discover
@coverage report --show-missing --fail-under=75
@flake8 .
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,7 @@ The commandline interface(app) is called `sewer` or alternatively you could use,

## TODO:
- support more DNS providers
- add robust tests
- be able to handle SAN(subject alternative names)
- add ci



## FAQ:
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@
# for example:
# $ pip install -e .[dev,test]
extras_require={
'dev': ['coverage', 'pypandoc', 'twine', 'wheel', 'yapf', 'flake8'],
'dev':
['coverage', 'pypandoc', 'twine', 'wheel', 'yapf', 'flake8', 'mock'],
},
# If there are data files included in your packages that need to be
# installed, specify them here. If using Python 2.6 or less, then these
Expand Down
2 changes: 1 addition & 1 deletion sewer/__version__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__title__ = "sewer"
__description__ = "Sewer is a programmatic Lets Encrypt(ACME) client"
__url__ = "https://github.com/komuW/sewer"
__version__ = "0.2.4.1"
__version__ = "0.2.5"
__author__ = "komuW"
__author_email__ = "komuw05@gmail.com"
__license__ = "MIT"
249 changes: 230 additions & 19 deletions sewer/tests/test_ACMEclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,243 @@
# not to pollute the global namespace.
# see: https://python-packaging.readthedocs.io/en/latest/testing.html

# from unittest import TestCase
import mock
import cryptography
from unittest import TestCase

import sewer

from . import test_utils


class TestACMEclient(TestCase):
"""
Todo:
- mock time.sleep
- make this tests DRY
- add tests for the cli
- modularize this tests
"""

def setUp(self):
self.domain_name = 'example.com'
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get:
mock_requests_post.return_value = test_utils.MockResponse()
mock_requests_get.return_value = test_utils.MockResponse()

self.dns_class = test_utils.ExmpleDnsProvider()
self.client = sewer.Client(
domain_name=self.domain_name,
dns_class=self.dns_class,
ACME_CHALLENGE_WAIT_PERIOD=0)

def tearDown(self):
pass

def test_user_agent_is_generated(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)

for i in [
'python-requests', 'sewer', 'https://github.com/komuW/sewer'
]:
self.assertIn(i, self.client.User_Agent)

def test_certificate_key_is_generated(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
certificate_key = self.client.certificate_key

certificate_key_private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key(
certificate_key,
password=None,
backend=cryptography.hazmat.backends.default_backend())
self.assertIsInstance(
certificate_key_private_key,
cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey)

def test_account_key_is_generated(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
account_key = self.client.account_key

account_key_private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key(
account_key,
password=None,
backend=cryptography.hazmat.backends.default_backend())
self.assertIsInstance(
account_key_private_key,
cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey)

def test_certificate_chain_is_generated(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
self.assertIsInstance(self.client.certificate_chain, basestring)

def test_acme_registration_is_done(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get, mock.patch(
'sewer.Client.acme_register') as mock_acme_registration:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
self.client.cert()
self.assertTrue(mock_acme_registration.called)

def test_get_challenge_is_called(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get, mock.patch(
'sewer.Client.get_challenge') as mock_get_challenge:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
mock_get_challenge.return_value = 'dns_token', 'dns_challenge_url'
self.client.cert()
self.assertTrue(mock_get_challenge.called)

def test_create_dns_record_is_called(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get, mock.patch(
'sewer.tests.test_utils.ExmpleDnsProvider.create_dns_record'
) as mock_create_dns_record:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
self.client.cert()
self.assertTrue(mock_create_dns_record.called)

def test_notify_acme_challenge_set_is_called(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get, mock.patch(
'sewer.Client.notify_acme_challenge_set'
) as mock_notify_acme_challenge_set:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
self.client.cert()
self.assertTrue(mock_notify_acme_challenge_set.called)

# import funniest
def test_check_challenge_status_set_is_called(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get, mock.patch(
'sewer.Client.check_challenge_status'
) as mock_check_challenge_status:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
self.client.cert()
self.assertTrue(mock_check_challenge_status.called)

# class TestJoke(TestCase):
# def test_is_string(self):
# s = funniest.joke()
# self.assertTrue(isinstance(s, basestring))
def test_delete_dns_record_not_called(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get, mock.patch(
'sewer.tests.test_utils.ExmpleDnsProvider.delete_dns_record'
) as mock_delete_dns_record:
content = """
{"status": "pending", "challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
self.client.cert()
self.assertFalse(mock_delete_dns_record.called)

# The best way to get these tests going (particularly if you're not sure what to use) is Nose.
# With those files added, it's just a matter of running this from the root of the repository:
def test_delete_dns_record_is_called(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get, mock.patch(
'sewer.tests.test_utils.ExmpleDnsProvider.delete_dns_record'
) as mock_delete_dns_record:
content = """
{"status": "valid", "challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
self.client.cert()
self.assertTrue(mock_delete_dns_record.called)

# $ pip install nose
# $ nosetests
# To integrate this with our setup.py, and ensure that Nose is installed when we run the tests, we'll add a few lines to setup():
def test_get_certicate_is_called(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get, mock.patch(
'sewer.Client.get_certicate') as mock_get_certicate:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
self.client.cert()
self.assertTrue(mock_get_certicate.called)

# setup(
# ...
# test_suite='nose.collector',
# tests_require=['nose'],
# )
# Then, to run tests, we can simply do:
def test_certicate_is_issued(self):
with mock.patch('requests.post') as mock_requests_post, mock.patch(
'requests.get') as mock_requests_get:
content = """
{"challenges": [{"type": "dns-01", "token": "example-token", "uri": "example-uri"}]}
"""
mock_requests_post.return_value = test_utils.MockResponse(
content=content)
mock_requests_get.return_value = test_utils.MockResponse(
content=content)
for i in [
'-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----'
]:
self.assertIn(i, self.client.cert())

# $ python setup.py test
# Setuptools will take care of installing nose and running the test suite.

# TEST cli
# from unittest import TestCase
# from funniest.command_line import main

Expand Down
29 changes: 29 additions & 0 deletions sewer/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import json

import sewer


class ExmpleDnsProvider(sewer.dns_providers.common.BaseDns):

def __init__(self):
self.dns_provider_name = 'example_dns_provider'

def create_dns_record(self, domain_name, base64_of_acme_keyauthorization):
pass

def delete_dns_record(self, domain_name, base64_of_acme_keyauthorization):
pass


class MockResponse(object):
"""
mock python-requests Response object
"""

def __init__(self, status_code=200, content='{"something": "ok"}'):
self.status_code = status_code
self.content = content
self.headers = {'Replay-Nonce': 'example-replay-Nonce'}

def json(self):
return json.loads(self.content)