diff --git a/.azure-pipelines/main.yml b/.azure-pipelines/main.yml index cae4f799c..c82647bd8 100644 --- a/.azure-pipelines/main.yml +++ b/.azure-pipelines/main.yml @@ -1,8 +1,18 @@ -trigger: none +# We run the test suite on commits to master so codecov gets coverage data +# about the master branch and can use it to track coverage changes. +trigger: + - master pr: - master - '*.x' +variables: + # We set this here to avoid coverage data being uploaded from things like our + # nightly pipeline. This is done because codecov (helpfully) keeps track of + # the number of coverage uploads for a commit and displays a warning when + # comparing two commits with an unequal number of uploads. Only uploading + # coverage here should keep the number of uploads it sees consistent. + uploadCoverage: true + jobs: - template: templates/jobs/standard-tests-jobs.yml - diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 2374289e3..edef2d1c3 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -15,11 +15,5 @@ stages: - template: templates/stages/changelog-stage.yml - template: templates/stages/deploy-stage.yml parameters: - ${{ if startsWith(variables['Build.SourceBranchName'], 'v2') }}: - snapReleaseChannel: beta - ${{ elseif startsWith(variables['Build.SourceBranchName'], 'v1') }}: - snapReleaseChannel: candidate - ${{ else }}: - # This should never happen - snapReleaseChannel: somethingInvalid + snapReleaseChannel: beta - template: templates/stages/notify-failure-stage.yml diff --git a/.azure-pipelines/templates/jobs/snap-deploy-job.yml b/.azure-pipelines/templates/jobs/snap-deploy-job.yml index d1d709cb9..a3c68af36 100644 --- a/.azure-pipelines/templates/jobs/snap-deploy-job.yml +++ b/.azure-pipelines/templates/jobs/snap-deploy-job.yml @@ -12,14 +12,13 @@ parameters: values: - edge - beta - - candidate jobs: # This job relies on credentials used to publish the Certbot snaps. This # credential file was created by running: # # snapcraft logout - # snapcraft export-login --channels=candidate,beta,edge snapcraft.cfg + # snapcraft export-login --channels=beta,edge snapcraft.cfg # (provide the shared snapcraft credentials when prompted) # # Then the file was added as a secure file in Azure pipelines @@ -30,7 +29,7 @@ jobs: # https://docs.microsoft.com/en-us/azure/devops/pipelines/library/secure-files?view=azure-devops#q-how-do-i-authorize-a-secure-file-for-use-in-a-specific-pipeline. # # This file has a maximum lifetime of one year and the current file will - # expire on 2023-09-06. The file will need to be updated before then to + # expire on 2024-02-10. The file will need to be updated before then to # prevent automated deploys from breaking. # # Revoking these credentials can be done by changing the password of the diff --git a/.azure-pipelines/templates/jobs/standard-tests-jobs.yml b/.azure-pipelines/templates/jobs/standard-tests-jobs.yml index c49e22bc1..1e6d7a352 100644 --- a/.azure-pipelines/templates/jobs/standard-tests-jobs.yml +++ b/.azure-pipelines/templates/jobs/standard-tests-jobs.yml @@ -7,10 +7,10 @@ jobs: macos-py37-cover: IMAGE_NAME: macOS-12 PYTHON_VERSION: 3.7 - TOXENV: py37-cover + TOXENV: cover macos-cover: IMAGE_NAME: macOS-12 - TOXENV: py3-cover + TOXENV: cover windows-py37: IMAGE_NAME: windows-2019 PYTHON_VERSION: 3.7 @@ -18,7 +18,7 @@ jobs: windows-py39-cover: IMAGE_NAME: windows-2019 PYTHON_VERSION: 3.9 - TOXENV: py39-cover-win + TOXENV: cover-win windows-integration-certbot: IMAGE_NAME: windows-2019 PYTHON_VERSION: 3.9 @@ -37,7 +37,7 @@ jobs: TOXENV: py37 linux-cover: IMAGE_NAME: ubuntu-22.04 - TOXENV: py3-cover + TOXENV: cover linux-lint: IMAGE_NAME: ubuntu-22.04 TOXENV: lint-posix diff --git a/.azure-pipelines/templates/stages/deploy-stage.yml b/.azure-pipelines/templates/stages/deploy-stage.yml index cb8b24e28..f04242751 100644 --- a/.azure-pipelines/templates/stages/deploy-stage.yml +++ b/.azure-pipelines/templates/stages/deploy-stage.yml @@ -11,7 +11,23 @@ stages: - template: ../jobs/snap-deploy-job.yml parameters: snapReleaseChannel: ${{ parameters.snapReleaseChannel }} - - job: publish_docker + # The credentials used in the following jobs are for the shared + # certbotbot account on Docker Hub. The credentials are stored + # in a service account which was created by following the + # instructions at + # https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#sep-docreg. + # The name given to this service account must match the value + # given to containerRegistry below. The authentication used when + # creating this service account was a personal access token + # rather than a password to bypass 2FA. When Brad set this up, + # Azure Pipelines failed to verify the credentials with an error + # like "access is forbidden with a JWT issued from a personal + # access token", but after saving them without verification, the + # access token worked when the pipeline actually ran. "Grant + # access to all pipelines" should also be checked on the service + # account. The access token can be deleted on Docker Hub if + # these credentials need to be revoked. + - job: publish_docker_by_arch pool: vmImage: ubuntu-22.04 strategy: @@ -33,22 +49,19 @@ stages: - task: Docker@2 inputs: command: login - # The credentials used here are for the shared certbotbot account - # on Docker Hub. The credentials are stored in a service account - # which was created by following the instructions at - # https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#sep-docreg. - # The name given to this service account must match the value - # given to containerRegistry below. The authentication used when - # creating this service account was a personal access token - # rather than a password to bypass 2FA. When Brad set this up, - # Azure Pipelines failed to verify the credentials with an error - # like "access is forbidden with a JWT issued from a personal - # access token", but after saving them without verification, the - # access token worked when the pipeline actually ran. "Grant - # access to all pipelines" should also be checked on the service - # account. The access token can be deleted on Docker Hub if - # these credentials need to be revoked. containerRegistry: docker-hub displayName: Login to Docker Hub - - bash: set -e && tools/docker/deploy.sh $(dockerTag) $DOCKER_ARCH - displayName: Deploy the Docker images + - bash: set -e && tools/docker/deploy_by_arch.sh $(dockerTag) $DOCKER_ARCH + displayName: Deploy the Docker images by architecture + - job: publish_docker_multiarch + dependsOn: publish_docker_by_arch + pool: + vmImage: ubuntu-22.04 + steps: + - task: Docker@2 + inputs: + command: login + containerRegistry: docker-hub + displayName: Login to Docker Hub + - bash: set -e && tools/docker/deploy_multiarch.sh $(dockerTag) + displayName: Deploy the Docker multiarch manifests diff --git a/.azure-pipelines/templates/steps/tox-steps.yml b/.azure-pipelines/templates/steps/tox-steps.yml index fbda960a5..5557bac68 100644 --- a/.azure-pipelines/templates/steps/tox-steps.yml +++ b/.azure-pipelines/templates/steps/tox-steps.yml @@ -21,6 +21,7 @@ steps: nginx-light \ openssl sudo systemctl stop nginx + sudo sysctl net.ipv4.ip_unprivileged_port_start=0 condition: startswith(variables['IMAGE_NAME'], 'ubuntu') displayName: Install Linux dependencies - task: UsePythonVersion@0 @@ -55,3 +56,28 @@ steps: AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY) AWS_EC2_PEM_FILE: $(testFarmPem.secureFilePath) displayName: Run tox + # For now, let's omit `set -e` and avoid the script exiting with a nonzero + # status code to prevent problems here from causing build failures. If + # this turns out to work well, we can change this. + - bash: | + python3 tools/pip_install.py -I coverage + case "$AGENT_OS" in + Darwin) + CODECOV_URL="https://uploader.codecov.io/latest/macos/codecov" + ;; + Linux) + CODECOV_URL="https://uploader.codecov.io/latest/linux/codecov" + ;; + Windows_NT) + CODECOV_URL="https://uploader.codecov.io/latest/windows/codecov.exe" + ;; + *) + echo "Unexpected OS" + exit 0 + esac + curl --retry 3 -o codecov "$CODECOV_URL" + chmod +x codecov + coverage xml + ./codecov || echo "Uploading coverage data failed" + condition: and(eq(variables['uploadCoverage'], true), or(startsWith(variables['TOXENV'], 'cover'), startsWith(variables['TOXENV'], 'integration'))) + displayName: Upload coverage data diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 000000000..23d2fbbe9 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,7 @@ +# This disables all reporting from codecov. Let's just set it up to collect +# data for now and then we can play with the settings here. +comment: false +coverage: + status: + project: off + patch: off diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 2e4106314..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Configuration for https://github.com/marketplace/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 365 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -# When changing this value, be sure to also update markComment below. -daysUntilClose: 30 - -# Ignore issues with an assignee (defaults to false) -exemptAssignees: true - -# Label to use when marking as stale -staleLabel: needs-update - -# Comment to post when marking as stale. Set to `false` to disable -markComment: > - We've made a lot of changes to Certbot since this issue was opened. If you - still have this issue with an up-to-date version of Certbot, can you please - add a comment letting us know? This helps us to better see what issues are - still affecting our users. If there is no activity in the next 30 days, this - issue will be automatically closed. - -# Comment to post when closing a stale Issue or Pull Request. -closeComment: > - This issue has been closed due to lack of activity, but if you think it - should be reopened, please open a new issue with a link to this one and we'll - take a look. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 1 - -# Don't mark pull requests as stale. -only: issues diff --git a/.github/workflows/notify_weekly.yaml b/.github/workflows/notify_weekly.yaml new file mode 100644 index 000000000..3a758db83 --- /dev/null +++ b/.github/workflows/notify_weekly.yaml @@ -0,0 +1,25 @@ +name: Weekly Github Update + +on: + schedule: + # Every week on Thursday @ 13:00 + - cron: "0 13 * * 4" +jobs: + send-mattermost-message: + runs-on: ubuntu-latest + + steps: + - name: Create Mattermost Message + run: | + DATE=$(date --date="7 days ago" +"%Y-%m-%d") + MERGED_URL="https://github.com/pulls?q=merged%3A%3E${DATE}+org%3Acertbot" + UPDATED_URL="https://github.com/pulls?q=updated%3A%3E${DATE}+org%3Acertbot" + echo "{\"text\":\"## Updates Across Certbot Repos\n\n + - Certbot team members SHOULD look at: [link]($MERGED_URL)\n\n + - Certbot team members MAY also want to look at: [link]($UPDATED_URL)\n\n + - Want to Discuss something today? Place it [here](https://docs.google.com/document/d/17YMUbtC1yg6MfiTMwT8zVm9LmO-cuGVBom0qFn8XJBM/edit?usp=sharing) and we can meet today on Zoom.\n\n + - The key words SHOULD and MAY in this message are to be interpreted as described in [RFC 8147](https://www.rfc-editor.org/rfc/rfc8174). \" + }" > mattermost.json + - uses: mattermost/action-mattermost-notify@master + env: + MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..327930d70 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,45 @@ +name: Update Stale Issues +on: + schedule: + # Run at midnight every night + - cron: '24 1 * * *' +permissions: + issues: write +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v6 + with: + # Idle number of days before marking issues stale + days-before-issue-stale: 365 + + # Never mark PRs as stale + days-before-pr-stale: -1 + + # Idle number of days before closing stale issues + days-before-issue-close: 30 + + # Never close PRs + days-before-pr-close: -1 + + # Ignore issues with an assignee + exempt-all-issue-assignees: true + + # Label to use when marking as stale + stale-issue-label: needs-update + + stale-issue-message: > + We've made a lot of changes to Certbot since this issue was opened. If you + still have this issue with an up-to-date version of Certbot, can you please + add a comment letting us know? This helps us to better see what issues are + still affecting our users. If there is no activity in the next 30 days, this + issue will be automatically closed. + + close-issue-message: > + This issue has been closed due to lack of activity, but if you think it + should be reopened, please open a new issue with a link to this one and we'll + take a look. + + # Limit the number of actions per hour, from 1-30. Default is 30 + operations-per-run: 30 diff --git a/.isort.cfg b/.isort.cfg index 6b17b459b..ce2d6c6de 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -4,3 +4,4 @@ force_sort_within_sections=True force_single_line=True order_by_type=False line_length=400 +src_paths=acme/acme,acme/tests,certbot*/certbot*,certbot*/tests diff --git a/AUTHORS.md b/AUTHORS.md index 4b8dd9e73..410e72030 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -68,6 +68,7 @@ Authors * [Daniel Convissor](https://github.com/convissor) * [Daniel "Drex" Drexler](https://github.com/aeturnum) * [Daniel Huang](https://github.com/dhuang) +* [Daniel McMahon] (https://github.com/igloodan) * [Dave Guarino](https://github.com/daguar) * [David cz](https://github.com/dave-cz) * [David Dworken](https://github.com/ddworken) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..f9c1e57b1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Security vulnerabilities can be reported using GitHub's [private vulnerability reporting tool](https://github.com/certbot/certbot/security/advisories/new). diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 61af415bd..818df9032 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -5,8 +5,8 @@ import functools import hashlib import logging import socket -from typing import cast from typing import Any +from typing import cast from typing import Dict from typing import Mapping from typing import Optional diff --git a/acme/acme/fields.py b/acme/acme/fields.py index d642d10c5..bcd0346d8 100644 --- a/acme/acme/fields.py +++ b/acme/acme/fields.py @@ -1,8 +1,7 @@ """ACME JSON fields.""" import datetime -from typing import Any - import logging +from typing import Any import josepy as jose import pyrfc3339 diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 0e02a054e..07a6f4ec5 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -1,6 +1,6 @@ """ACME protocol messages.""" -import datetime from collections.abc import Hashable +import datetime import json from typing import Any from typing import Dict @@ -21,7 +21,6 @@ from acme import fields from acme import jws from acme import util - ERROR_PREFIX = "urn:ietf:params:acme:error:" ERROR_CODES = { @@ -123,6 +122,9 @@ class Error(jose.JSONObjectWithFields, errors.Error): https://datatracker.ietf.org/doc/html/rfc7807 + Note: Although Error inherits from JSONObjectWithFields, which is immutable, + we add mutability for Error to comply with the Python exception API. + :ivar str typ: :ivar str title: :ivar str detail: @@ -185,6 +187,10 @@ class Error(jose.JSONObjectWithFields, errors.Error): return code return None + # Hack to allow mutability on Errors (see GH #9539) + def __setattr__(self, name: str, value: Any) -> None: + return object.__setattr__(self, name, value) + def __str__(self) -> str: result = b' :: '.join( part.encode('ascii', 'backslashreplace') for part in diff --git a/acme/docs/jws-help.txt b/acme/docs/jws-help.txt index 34cf5ce23..bfd16dff4 100644 --- a/acme/docs/jws-help.txt +++ b/acme/docs/jws-help.txt @@ -3,6 +3,6 @@ usage: jws [-h] [--compact] {sign,verify} ... positional arguments: {sign,verify} -optional arguments: +options: -h, --help show this help message and exit --compact diff --git a/acme/setup.py b/acme/setup.py index 1dbdc1291..577a43be3 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'cryptography>=2.5.0', diff --git a/acme/tests/challenges_test.py b/acme/tests/challenges_test.py index 9e31b36c1..1febaff54 100644 --- a/acme/tests/challenges_test.py +++ b/acme/tests/challenges_test.py @@ -1,15 +1,16 @@ """Tests for acme.challenges.""" -import urllib.parse as urllib_parse +import sys import unittest from unittest import mock +import urllib.parse as urllib_parse import josepy as jose -import OpenSSL -import requests from josepy.jwk import JWKEC +import OpenSSL +import pytest +import requests from acme import errors - import test_util CERT = test_util.load_comparable_cert('cert.pem') @@ -22,7 +23,7 @@ class ChallengeTest(unittest.TestCase): from acme.challenges import Challenge from acme.challenges import UnrecognizedChallenge chall = UnrecognizedChallenge({"type": "foo"}) - self.assertEqual(chall, Challenge.from_json(chall.jobj)) + assert chall == Challenge.from_json(chall.jobj) class UnrecognizedChallengeTest(unittest.TestCase): @@ -33,12 +34,11 @@ class UnrecognizedChallengeTest(unittest.TestCase): self.chall = UnrecognizedChallenge(self.jobj) def test_to_partial_json(self): - self.assertEqual(self.jobj, self.chall.to_partial_json()) + assert self.jobj == self.chall.to_partial_json() def test_from_json(self): from acme.challenges import UnrecognizedChallenge - self.assertEqual( - self.chall, UnrecognizedChallenge.from_json(self.jobj)) + assert self.chall == UnrecognizedChallenge.from_json(self.jobj) class KeyAuthorizationChallengeResponseTest(unittest.TestCase): @@ -54,26 +54,26 @@ class KeyAuthorizationChallengeResponseTest(unittest.TestCase): from acme.challenges import KeyAuthorizationChallengeResponse response = KeyAuthorizationChallengeResponse( key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') - self.assertTrue(response.verify(self.chall, KEY.public_key())) + assert response.verify(self.chall, KEY.public_key()) def test_verify_wrong_token(self): from acme.challenges import KeyAuthorizationChallengeResponse response = KeyAuthorizationChallengeResponse( key_authorization='bar.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') - self.assertFalse(response.verify(self.chall, KEY.public_key())) + assert not response.verify(self.chall, KEY.public_key()) def test_verify_wrong_thumbprint(self): from acme.challenges import KeyAuthorizationChallengeResponse response = KeyAuthorizationChallengeResponse( key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxv') - self.assertFalse(response.verify(self.chall, KEY.public_key())) + assert not response.verify(self.chall, KEY.public_key()) def test_verify_wrong_form(self): from acme.challenges import KeyAuthorizationChallengeResponse response = KeyAuthorizationChallengeResponse( key_authorization='.foo.oKGqedy-b-acd5eoybm2f-' 'NVFxvyOoET5CNy3xnv8WY') - self.assertFalse(response.verify(self.chall, KEY.public_key())) + assert not response.verify(self.chall, KEY.public_key()) class DNS01ResponseTest(unittest.TestCase): @@ -92,11 +92,11 @@ class DNS01ResponseTest(unittest.TestCase): self.response = self.chall.response(KEY) def test_to_partial_json(self): - self.assertEqual({}, self.msg.to_partial_json()) + assert {} == self.msg.to_partial_json() def test_from_json(self): from acme.challenges import DNS01Response - self.assertEqual(self.msg, DNS01Response.from_json(self.jmsg)) + assert self.msg == DNS01Response.from_json(self.jmsg) def test_from_json_hashable(self): from acme.challenges import DNS01Response @@ -106,12 +106,12 @@ class DNS01ResponseTest(unittest.TestCase): key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) public_key = key2.public_key() verified = self.response.simple_verify(self.chall, "local", public_key) - self.assertFalse(verified) + assert not verified def test_simple_verify_success(self): public_key = KEY.public_key() verified = self.response.simple_verify(self.chall, "local", public_key) - self.assertTrue(verified) + assert verified class DNS01Test(unittest.TestCase): @@ -126,20 +126,19 @@ class DNS01Test(unittest.TestCase): } def test_validation_domain_name(self): - self.assertEqual('_acme-challenge.www.example.com', - self.msg.validation_domain_name('www.example.com')) + assert '_acme-challenge.www.example.com' == \ + self.msg.validation_domain_name('www.example.com') def test_validation(self): - self.assertEqual( - "rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk", - self.msg.validation(KEY)) + assert "rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk" == \ + self.msg.validation(KEY) def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) + assert self.jmsg == self.msg.to_partial_json() def test_from_json(self): from acme.challenges import DNS01 - self.assertEqual(self.msg, DNS01.from_json(self.jmsg)) + assert self.msg == DNS01.from_json(self.jmsg) def test_from_json_hashable(self): from acme.challenges import DNS01 @@ -162,12 +161,11 @@ class HTTP01ResponseTest(unittest.TestCase): self.response = self.chall.response(KEY) def test_to_partial_json(self): - self.assertEqual({}, self.msg.to_partial_json()) + assert {} == self.msg.to_partial_json() def test_from_json(self): from acme.challenges import HTTP01Response - self.assertEqual( - self.msg, HTTP01Response.from_json(self.jmsg)) + assert self.msg == HTTP01Response.from_json(self.jmsg) def test_from_json_hashable(self): from acme.challenges import HTTP01Response @@ -181,16 +179,16 @@ class HTTP01ResponseTest(unittest.TestCase): def test_simple_verify_good_validation(self, mock_get): validation = self.chall.validation(KEY) mock_get.return_value = mock.MagicMock(text=validation) - self.assertTrue(self.response.simple_verify( - self.chall, "local", KEY.public_key())) + assert self.response.simple_verify( + self.chall, "local", KEY.public_key()) mock_get.assert_called_once_with(self.chall.uri("local"), verify=False, timeout=mock.ANY) @mock.patch("acme.challenges.requests.get") def test_simple_verify_bad_validation(self, mock_get): mock_get.return_value = mock.MagicMock(text="!") - self.assertFalse(self.response.simple_verify( - self.chall, "local", KEY.public_key())) + assert not self.response.simple_verify( + self.chall, "local", KEY.public_key()) @mock.patch("acme.challenges.requests.get") def test_simple_verify_whitespace_validation(self, mock_get): @@ -198,24 +196,24 @@ class HTTP01ResponseTest(unittest.TestCase): mock_get.return_value = mock.MagicMock( text=(self.chall.validation(KEY) + HTTP01Response.WHITESPACE_CUTSET)) - self.assertTrue(self.response.simple_verify( - self.chall, "local", KEY.public_key())) + assert self.response.simple_verify( + self.chall, "local", KEY.public_key()) mock_get.assert_called_once_with(self.chall.uri("local"), verify=False, timeout=mock.ANY) @mock.patch("acme.challenges.requests.get") def test_simple_verify_connection_error(self, mock_get): mock_get.side_effect = requests.exceptions.RequestException - self.assertFalse(self.response.simple_verify( - self.chall, "local", KEY.public_key())) + assert not self.response.simple_verify( + self.chall, "local", KEY.public_key()) @mock.patch("acme.challenges.requests.get") def test_simple_verify_port(self, mock_get): self.response.simple_verify( self.chall, domain="local", account_public_key=KEY.public_key(), port=8080) - self.assertEqual("local:8080", urllib_parse.urlparse( - mock_get.mock_calls[0][1][0]).netloc) + assert "local:8080" == urllib_parse.urlparse( + mock_get.mock_calls[0][1][0]).netloc @mock.patch("acme.challenges.requests.get") def test_simple_verify_timeout(self, mock_get): @@ -241,30 +239,28 @@ class HTTP01Test(unittest.TestCase): } def test_path(self): - self.assertEqual(self.msg.path, '/.well-known/acme-challenge/' - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA') + assert self.msg.path == '/.well-known/acme-challenge/' \ + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA' def test_uri(self): - self.assertEqual( - 'http://example.com/.well-known/acme-challenge/' - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - self.msg.uri('example.com')) + assert 'http://example.com/.well-known/acme-challenge/' \ + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA' == \ + self.msg.uri('example.com') def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) + assert self.jmsg == self.msg.to_partial_json() def test_from_json(self): from acme.challenges import HTTP01 - self.assertEqual(self.msg, HTTP01.from_json(self.jmsg)) + assert self.msg == HTTP01.from_json(self.jmsg) def test_from_json_hashable(self): from acme.challenges import HTTP01 hash(HTTP01.from_json(self.jmsg)) def test_good_token(self): - self.assertTrue(self.msg.good_token) - self.assertFalse( - self.msg.update(token=b'..').good_token) + assert self.msg.good_token + assert not self.msg.update(token=b'..').good_token class TLSALPN01ResponseTest(unittest.TestCase): @@ -284,11 +280,11 @@ class TLSALPN01ResponseTest(unittest.TestCase): } def test_to_partial_json(self): - self.assertEqual({}, self.response.to_partial_json()) + assert {} == self.response.to_partial_json() def test_from_json(self): from acme.challenges import TLSALPN01Response - self.assertEqual(self.response, TLSALPN01Response.from_json(self.jmsg)) + assert self.response == TLSALPN01Response.from_json(self.jmsg) def test_from_json_hashable(self): from acme.challenges import TLSALPN01Response @@ -297,23 +293,23 @@ class TLSALPN01ResponseTest(unittest.TestCase): def test_gen_verify_cert(self): key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem') cert, key2 = self.response.gen_cert(self.domain, key1) - self.assertEqual(key1, key2) - self.assertTrue(self.response.verify_cert(self.domain, cert)) + assert key1 == key2 + assert self.response.verify_cert(self.domain, cert) def test_gen_verify_cert_gen_key(self): cert, key = self.response.gen_cert(self.domain) - self.assertIsInstance(key, OpenSSL.crypto.PKey) - self.assertTrue(self.response.verify_cert(self.domain, cert)) + assert isinstance(key, OpenSSL.crypto.PKey) + assert self.response.verify_cert(self.domain, cert) def test_verify_bad_cert(self): - self.assertFalse(self.response.verify_cert(self.domain, - test_util.load_cert('cert.pem'))) + assert not self.response.verify_cert(self.domain, + test_util.load_cert('cert.pem')) def test_verify_bad_domain(self): key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem') cert, key2 = self.response.gen_cert(self.domain, key1) - self.assertEqual(key1, key2) - self.assertFalse(self.response.verify_cert(self.domain2, cert)) + assert key1 == key2 + assert not self.response.verify_cert(self.domain2, cert) def test_simple_verify_bad_key_authorization(self): key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) @@ -322,10 +318,9 @@ class TLSALPN01ResponseTest(unittest.TestCase): @mock.patch('acme.challenges.TLSALPN01Response.verify_cert', autospec=True) def test_simple_verify(self, mock_verify_cert): mock_verify_cert.return_value = mock.sentinel.verification - self.assertEqual( - mock.sentinel.verification, self.response.simple_verify( + assert mock.sentinel.verification == self.response.simple_verify( self.chall, self.domain, KEY.public_key(), - cert=mock.sentinel.cert)) + cert=mock.sentinel.cert) mock_verify_cert.assert_called_once_with( self.response, self.domain, mock.sentinel.cert) @@ -347,8 +342,8 @@ class TLSALPN01ResponseTest(unittest.TestCase): @mock.patch('acme.challenges.TLSALPN01Response.probe_cert') def test_simple_verify_false_on_probe_error(self, mock_probe_cert): mock_probe_cert.side_effect = errors.Error - self.assertFalse(self.response.simple_verify( - self.chall, self.domain, KEY.public_key())) + assert not self.response.simple_verify( + self.chall, self.domain, KEY.public_key()) class TLSALPN01Test(unittest.TestCase): @@ -363,11 +358,11 @@ class TLSALPN01Test(unittest.TestCase): } def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) + assert self.jmsg == self.msg.to_partial_json() def test_from_json(self): from acme.challenges import TLSALPN01 - self.assertEqual(self.msg, TLSALPN01.from_json(self.jmsg)) + assert self.msg == TLSALPN01.from_json(self.jmsg) def test_from_json_hashable(self): from acme.challenges import TLSALPN01 @@ -376,14 +371,14 @@ class TLSALPN01Test(unittest.TestCase): def test_from_json_invalid_token_length(self): from acme.challenges import TLSALPN01 self.jmsg['token'] = jose.encode_b64jose(b'abcd') - self.assertRaises( - jose.DeserializationError, TLSALPN01.from_json, self.jmsg) + with pytest.raises(jose.DeserializationError): + TLSALPN01.from_json(self.jmsg) @mock.patch('acme.challenges.TLSALPN01Response.gen_cert') def test_validation(self, mock_gen_cert): mock_gen_cert.return_value = ('cert', 'key') - self.assertEqual(('cert', 'key'), self.msg.validation( - KEY, cert_key=mock.sentinel.cert_key, domain=mock.sentinel.domain)) + assert ('cert', 'key') == self.msg.validation( + KEY, cert_key=mock.sentinel.cert_key, domain=mock.sentinel.domain) mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key, domain=mock.sentinel.domain) @@ -400,11 +395,11 @@ class DNSTest(unittest.TestCase): } def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) + assert self.jmsg == self.msg.to_partial_json() def test_from_json(self): from acme.challenges import DNS - self.assertEqual(self.msg, DNS.from_json(self.jmsg)) + assert self.msg == DNS.from_json(self.jmsg) def test_from_json_hashable(self): from acme.challenges import DNS @@ -414,13 +409,13 @@ class DNSTest(unittest.TestCase): ec_key_secp384r1 = JWKEC(key=test_util.load_ecdsa_private_key('ec_secp384r1_key.pem')) for key, alg in [(KEY, jose.RS256), (ec_key_secp384r1, jose.ES384)]: with self.subTest(key=key, alg=alg): - self.assertTrue(self.msg.check_validation( - self.msg.gen_validation(key, alg=alg), key.public_key())) + assert self.msg.check_validation( + self.msg.gen_validation(key, alg=alg), key.public_key()) def test_gen_check_validation_wrong_key(self): key2 = jose.JWKRSA.load(test_util.load_vector('rsa1024_key.pem')) - self.assertFalse(self.msg.check_validation( - self.msg.gen_validation(KEY), key2.public_key())) + assert not self.msg.check_validation( + self.msg.gen_validation(KEY), key2.public_key()) def test_check_validation_wrong_payload(self): validations = tuple( @@ -428,33 +423,32 @@ class DNSTest(unittest.TestCase): for payload in (b'', b'{}') ) for validation in validations: - self.assertFalse(self.msg.check_validation( - validation, KEY.public_key())) + assert not self.msg.check_validation( + validation, KEY.public_key()) def test_check_validation_wrong_fields(self): bad_validation = jose.JWS.sign( payload=self.msg.update( token=b'x' * 20).json_dumps().encode('utf-8'), alg=jose.RS256, key=KEY) - self.assertFalse(self.msg.check_validation(bad_validation, KEY.public_key())) + assert not self.msg.check_validation(bad_validation, KEY.public_key()) def test_gen_response(self): with mock.patch('acme.challenges.DNS.gen_validation') as mock_gen: mock_gen.return_value = mock.sentinel.validation response = self.msg.gen_response(KEY) from acme.challenges import DNSResponse - self.assertIsInstance(response, DNSResponse) - self.assertEqual(response.validation, mock.sentinel.validation) + assert isinstance(response, DNSResponse) + assert response.validation == mock.sentinel.validation def test_validation_domain_name(self): - self.assertEqual('_acme-challenge.le.wtf', self.msg.validation_domain_name('le.wtf')) + assert '_acme-challenge.le.wtf' == self.msg.validation_domain_name('le.wtf') def test_validation_domain_name_ecdsa(self): ec_key_secp384r1 = JWKEC(key=test_util.load_ecdsa_private_key('ec_secp384r1_key.pem')) - self.assertIs(self.msg.check_validation( + assert self.msg.check_validation( self.msg.gen_validation(ec_key_secp384r1, alg=jose.ES384), - ec_key_secp384r1.public_key()), True - ) + ec_key_secp384r1.public_key()) is True class DNSResponseTest(unittest.TestCase): @@ -479,18 +473,18 @@ class DNSResponseTest(unittest.TestCase): } def test_to_partial_json(self): - self.assertEqual(self.jmsg_to, self.msg.to_partial_json()) + assert self.jmsg_to == self.msg.to_partial_json() def test_from_json(self): from acme.challenges import DNSResponse - self.assertEqual(self.msg, DNSResponse.from_json(self.jmsg_from)) + assert self.msg == DNSResponse.from_json(self.jmsg_from) def test_from_json_hashable(self): from acme.challenges import DNSResponse hash(DNSResponse.from_json(self.jmsg_from)) def test_check_validation(self): - self.assertTrue(self.msg.check_validation(self.chall, KEY.public_key())) + assert self.msg.check_validation(self.chall, KEY.public_key()) class JWSPayloadRFC8555Compliant(unittest.TestCase): @@ -502,8 +496,8 @@ class JWSPayloadRFC8555Compliant(unittest.TestCase): jobj = challenge_body.json_dumps(indent=2).encode() # RFC8555 states that challenge responses must have an empty payload. - self.assertEqual(jobj, b'{}') + assert jobj == b'{}' if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/acme/tests/client_test.py b/acme/tests/client_test.py index 093ac519a..79153a31b 100644 --- a/acme/tests/client_test.py +++ b/acme/tests/client_test.py @@ -4,11 +4,13 @@ import copy import datetime import http.client as http_client import json -import unittest +import sys from typing import Dict +import unittest from unittest import mock import josepy as jose +import pytest import requests from acme import challenges @@ -101,7 +103,7 @@ class ClientV2Test(unittest.TestCase): self.response.json.return_value = self.regr.body.to_json() self.response.headers['Location'] = self.regr.uri - self.assertEqual(self.regr, self.client.new_account(self.new_reg)) + assert self.regr == self.client.new_account(self.new_reg) def test_new_account_tos_link(self): self.response.status_code = http_client.CREATED @@ -111,14 +113,15 @@ class ClientV2Test(unittest.TestCase): 'terms-of-service': {'url': 'https://www.letsencrypt-demo.org/tos'}, }) - self.assertEqual(self.client.new_account(self.new_reg).terms_of_service, - 'https://www.letsencrypt-demo.org/tos') + assert self.client.new_account(self.new_reg).terms_of_service == \ + 'https://www.letsencrypt-demo.org/tos' def test_new_account_conflict(self): self.response.status_code = http_client.OK self.response.headers['Location'] = self.regr.uri - self.assertRaises(errors.ConflictError, self.client.new_account, self.new_reg) + with pytest.raises(errors.ConflictError): + self.client.new_account(self.new_reg) def test_deactivate_account(self): deactivated_regr = self.regr.update( @@ -126,16 +129,16 @@ class ClientV2Test(unittest.TestCase): self.response.json.return_value = deactivated_regr.body.to_json() self.response.status_code = http_client.OK self.response.headers['Location'] = self.regr.uri - self.assertEqual(self.client.deactivate_registration(self.regr), deactivated_regr) + assert self.client.deactivate_registration(self.regr) == deactivated_regr def test_deactivate_authorization(self): deactivated_authz = self.authzr.update( body=self.authzr.body.update(status=messages.STATUS_DEACTIVATED)) self.response.json.return_value = deactivated_authz.body.to_json() authzr = self.client.deactivate_authorization(self.authzr) - self.assertEqual(deactivated_authz.body, authzr.body) - self.assertEqual(self.client.net.post.call_count, 1) - self.assertIn(self.authzr.uri, self.net.post.call_args_list[0][0]) + assert deactivated_authz.body == authzr.body + assert self.client.net.post.call_count == 1 + assert self.authzr.uri in self.net.post.call_args_list[0][0] def test_new_order(self): order_response = copy.deepcopy(self.response) @@ -153,7 +156,7 @@ class ClientV2Test(unittest.TestCase): with mock.patch('acme.client.ClientV2._post_as_get') as mock_post_as_get: mock_post_as_get.side_effect = (authz_response, authz_response2) - self.assertEqual(self.client.new_order(CSR_MIXED_PEM), self.orderr) + assert self.client.new_order(CSR_MIXED_PEM) == self.orderr def test_answer_challege(self): self.response.links['up'] = {'url': self.challr.authzr_uri} @@ -161,13 +164,12 @@ class ClientV2Test(unittest.TestCase): chall_response = challenges.DNSResponse(validation=None) self.client.answer_challenge(self.challr.body, chall_response) - self.assertRaises(errors.UnexpectedUpdate, self.client.answer_challenge, - self.challr.body.update(uri='foo'), chall_response) + with pytest.raises(errors.UnexpectedUpdate): + self.client.answer_challenge(self.challr.body.update(uri='foo'), chall_response) def test_answer_challenge_missing_next(self): - self.assertRaises( - errors.ClientError, self.client.answer_challenge, - self.challr.body, challenges.DNSResponse(validation=None)) + with pytest.raises(errors.ClientError): + self.client.answer_challenge(self.challr.body, challenges.DNSResponse(validation=None)) @mock.patch('acme.client.datetime') def test_poll_and_finalize(self, mock_datetime): @@ -178,7 +180,7 @@ class ClientV2Test(unittest.TestCase): self.client.poll_authorizations = mock.Mock(return_value=self.orderr) self.client.finalize_order = mock.Mock(return_value=self.orderr) - self.assertEqual(self.client.poll_and_finalize(self.orderr), self.orderr) + assert self.client.poll_and_finalize(self.orderr) == self.orderr self.client.poll_authorizations.assert_called_once_with(self.orderr, expected_deadline) self.client.finalize_order.assert_called_once_with(self.orderr, expected_deadline) @@ -191,8 +193,8 @@ class ClientV2Test(unittest.TestCase): self.response.json.side_effect = [ self.authz.to_json(), self.authz2.to_json(), self.authz2.to_json()] - self.assertRaises( - errors.TimeoutError, self.client.poll_authorizations, self.orderr, now_side_effect[1]) + with pytest.raises(errors.TimeoutError): + self.client.poll_authorizations(self.orderr, now_side_effect[1]) def test_poll_authorizations_failure(self): deadline = datetime.datetime(9999, 9, 9) @@ -201,8 +203,8 @@ class ClientV2Test(unittest.TestCase): authz = self.authz.update(status=messages.STATUS_INVALID, challenges=(challb,)) self.response.json.return_value = authz.to_json() - self.assertRaises( - errors.ValidationError, self.client.poll_authorizations, self.orderr, deadline) + with pytest.raises(errors.ValidationError): + self.client.poll_authorizations(self.orderr, deadline) def test_poll_authorizations_success(self): deadline = datetime.datetime(9999, 9, 9) @@ -213,12 +215,13 @@ class ClientV2Test(unittest.TestCase): self.response.json.side_effect = ( self.authz.to_json(), self.authz2.to_json(), updated_authz2.to_json()) - self.assertEqual(self.client.poll_authorizations(self.orderr, deadline), updated_orderr) + assert self.client.poll_authorizations(self.orderr, deadline) == updated_orderr def test_poll_unexpected_update(self): updated_authz = self.authz.update(identifier=self.identifier.update(value='foo')) self.response.json.return_value = updated_authz.to_json() - self.assertRaises(errors.UnexpectedUpdate, self.client.poll, self.authzr) + with pytest.raises(errors.UnexpectedUpdate): + self.client.poll(self.authzr) def test_finalize_order_success(self): updated_order = self.order.update( @@ -230,7 +233,7 @@ class ClientV2Test(unittest.TestCase): self.response.text = CERT_SAN_PEM deadline = datetime.datetime(9999, 9, 9) - self.assertEqual(self.client.finalize_order(self.orderr, deadline), updated_orderr) + assert self.client.finalize_order(self.orderr, deadline) == updated_orderr def test_finalize_order_error(self): updated_order = self.order.update( @@ -239,19 +242,20 @@ class ClientV2Test(unittest.TestCase): self.response.json.return_value = updated_order.to_json() deadline = datetime.datetime(9999, 9, 9) - self.assertRaises(errors.IssuanceError, self.client.finalize_order, self.orderr, deadline) + with pytest.raises(errors.IssuanceError): + self.client.finalize_order(self.orderr, deadline) def test_finalize_order_invalid_status(self): # https://github.com/certbot/certbot/issues/9296 order = self.order.update(error=None, status=messages.STATUS_INVALID) self.response.json.return_value = order.to_json() - with self.assertRaises(errors.Error) as error: + with pytest.raises(errors.Error, match="The certificate order failed"): self.client.finalize_order(self.orderr, datetime.datetime(9999, 9, 9)) - self.assertIn("The certificate order failed", str(error.exception)) def test_finalize_order_timeout(self): deadline = datetime.datetime.now() - datetime.timedelta(seconds=60) - self.assertRaises(errors.TimeoutError, self.client.finalize_order, self.orderr, deadline) + with pytest.raises(errors.TimeoutError): + self.client.finalize_order(self.orderr, deadline) def test_finalize_order_alt_chains(self): updated_order = self.order.update( @@ -274,11 +278,11 @@ class ClientV2Test(unittest.TestCase): mock.ANY, new_nonce_url=mock.ANY) self.net.post.assert_any_call('https://example.com/acme/cert/2', mock.ANY, new_nonce_url=mock.ANY) - self.assertEqual(resp, updated_orderr) + assert resp == updated_orderr del self.response.headers['Link'] resp = self.client.finalize_order(self.orderr, deadline, fetch_alternative_chains=True) - self.assertEqual(resp, updated_orderr.update(alternative_fullchains_pem=[])) + assert resp == updated_orderr.update(alternative_fullchains_pem=[]) def test_revoke(self): self.client.revoke(messages_test.CERT, self.rsn) @@ -287,20 +291,18 @@ class ClientV2Test(unittest.TestCase): def test_revoke_bad_status_raises_error(self): self.response.status_code = http_client.METHOD_NOT_ALLOWED - self.assertRaises( - errors.ClientError, - self.client.revoke, - messages_test.CERT, + with pytest.raises(errors.ClientError): + self.client.revoke(messages_test.CERT, self.rsn) def test_update_registration(self): # "Instance of 'Field' has no to_json/update member" bug: self.response.headers['Location'] = self.regr.uri self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, self.client.update_registration(self.regr)) - self.assertIsNotNone(self.client.net.account) - self.assertEqual(self.client.net.post.call_count, 2) - self.assertIn(DIRECTORY_V2.newAccount, self.net.post.call_args_list[0][0]) + assert self.regr == self.client.update_registration(self.regr) + assert self.client.net.account is not None + assert self.client.net.post.call_count == 2 + assert DIRECTORY_V2.newAccount in self.net.post.call_args_list[0][0] self.response.json.return_value = self.regr.body.update( contact=()).to_json() @@ -310,22 +312,22 @@ class ClientV2Test(unittest.TestCase): 'meta': messages.Directory.Meta(external_account_required=True) }) - self.assertTrue(self.client.external_account_required()) + assert self.client.external_account_required() def test_external_account_required_false(self): self.client.directory = messages.Directory({ 'meta': messages.Directory.Meta(external_account_required=False) }) - self.assertFalse(self.client.external_account_required()) + assert not self.client.external_account_required() def test_external_account_required_default(self): - self.assertFalse(self.client.external_account_required()) + assert not self.client.external_account_required() def test_query_registration_client(self): self.response.json.return_value = self.regr.body.to_json() self.response.headers['Location'] = 'https://www.letsencrypt-demo.org/acme/reg/1' - self.assertEqual(self.regr, self.client.query_registration(self.regr)) + assert self.regr == self.client.query_registration(self.regr) def test_post_as_get(self): with mock.patch('acme.client.ClientV2._authzr_from_response') as mock_client: @@ -340,9 +342,8 @@ class ClientV2Test(unittest.TestCase): def test_retry_after_date(self): self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT' - self.assertEqual( - datetime.datetime(1999, 12, 31, 23, 59, 59), - self.client.retry_after(response=self.response, default=10)) + assert datetime.datetime(1999, 12, 31, 23, 59, 59) == \ + self.client.retry_after(response=self.response, default=10) @mock.patch('acme.client.datetime') def test_retry_after_invalid(self, dt_mock): @@ -350,9 +351,8 @@ class ClientV2Test(unittest.TestCase): dt_mock.timedelta = datetime.timedelta self.response.headers['Retry-After'] = 'foooo' - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 10), - self.client.retry_after(response=self.response, default=10)) + assert datetime.datetime(2015, 3, 27, 0, 0, 10) == \ + self.client.retry_after(response=self.response, default=10) @mock.patch('acme.client.datetime') def test_retry_after_overflow(self, dt_mock): @@ -361,9 +361,8 @@ class ClientV2Test(unittest.TestCase): dt_mock.datetime.side_effect = datetime.datetime self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST" - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 10), - self.client.retry_after(response=self.response, default=10)) + assert datetime.datetime(2015, 3, 27, 0, 0, 10) == \ + self.client.retry_after(response=self.response, default=10) @mock.patch('acme.client.datetime') def test_retry_after_seconds(self, dt_mock): @@ -371,24 +370,21 @@ class ClientV2Test(unittest.TestCase): dt_mock.timedelta = datetime.timedelta self.response.headers['Retry-After'] = '50' - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 50), - self.client.retry_after(response=self.response, default=10)) + assert datetime.datetime(2015, 3, 27, 0, 0, 50) == \ + self.client.retry_after(response=self.response, default=10) @mock.patch('acme.client.datetime') def test_retry_after_missing(self, dt_mock): dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) dt_mock.timedelta = datetime.timedelta - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 10), - self.client.retry_after(response=self.response, default=10)) + assert datetime.datetime(2015, 3, 27, 0, 0, 10) == \ + self.client.retry_after(response=self.response, default=10) def test_get_directory(self): self.response.json.return_value = DIRECTORY_V2.to_json() - self.assertEqual( - DIRECTORY_V2.to_partial_json(), - ClientV2.get_directory('https://example.com/dir', self.net).to_partial_json()) + assert DIRECTORY_V2.to_partial_json() == \ + ClientV2.get_directory('https://example.com/dir', self.net).to_partial_json() class MockJSONDeSerializable(jose.JSONDeSerializable): @@ -420,15 +416,15 @@ class ClientNetworkTest(unittest.TestCase): self.response.links = {} def test_init(self): - self.assertIs(self.net.verify_ssl, self.verify_ssl) + assert self.net.verify_ssl is self.verify_ssl def test_wrap_in_jws(self): # pylint: disable=protected-access jws_dump = self.net._wrap_in_jws( MockJSONDeSerializable('foo'), nonce=b'Tg', url="url") jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) - self.assertEqual(jws.signature.combined.nonce, b'Tg') + assert json.loads(jws.payload.decode()) == {'foo': 'foo'} + assert jws.signature.combined.nonce == b'Tg' def test_wrap_in_jws_v2(self): self.net.account = {'uri': 'acct-uri'} @@ -436,10 +432,10 @@ class ClientNetworkTest(unittest.TestCase): jws_dump = self.net._wrap_in_jws( MockJSONDeSerializable('foo'), nonce=b'Tg', url="url") jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) - self.assertEqual(jws.signature.combined.nonce, b'Tg') - self.assertEqual(jws.signature.combined.kid, u'acct-uri') - self.assertEqual(jws.signature.combined.url, u'url') + assert json.loads(jws.payload.decode()) == {'foo': 'foo'} + assert jws.signature.combined.nonce == b'Tg' + assert jws.signature.combined.kid == u'acct-uri' + assert jws.signature.combined.url == u'url' def test_check_response_not_ok_jobj_no_error(self): self.response.ok = False @@ -447,31 +443,31 @@ class ClientNetworkTest(unittest.TestCase): with mock.patch('acme.client.messages.Error.from_json') as from_json: from_json.side_effect = jose.DeserializationError # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response) + with pytest.raises(errors.ClientError): + self.net._check_response(self.response) def test_check_response_not_ok_jobj_error(self): self.response.ok = False self.response.json.return_value = messages.Error.with_code( 'serverInternal', detail='foo', title='some title').to_json() # pylint: disable=protected-access - self.assertRaises( - messages.Error, self.net._check_response, self.response) + with pytest.raises(messages.Error): + self.net._check_response(self.response) def test_check_response_not_ok_no_jobj(self): self.response.ok = False self.response.json.side_effect = ValueError # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response) + with pytest.raises(errors.ClientError): + self.net._check_response(self.response) def test_check_response_ok_no_jobj_ct_required(self): self.response.json.side_effect = ValueError for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: self.response.headers['Content-Type'] = response_ct # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response, + with pytest.raises(errors.ClientError): + self.net._check_response(self.response, content_type=self.net.JSON_CONTENT_TYPE) def test_check_response_ok_no_jobj_no_ct(self): @@ -479,16 +475,15 @@ class ClientNetworkTest(unittest.TestCase): for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: self.response.headers['Content-Type'] = response_ct # pylint: disable=protected-access - self.assertEqual( - self.response, self.net._check_response(self.response)) + assert self.response == self.net._check_response(self.response) @mock.patch('acme.client.logger') def test_check_response_ok_ct_with_charset(self, mock_logger): self.response.json.return_value = {} self.response.headers['Content-Type'] = 'application/json; charset=utf-8' # pylint: disable=protected-access - self.assertEqual(self.response, self.net._check_response( - self.response, content_type='application/json')) + assert self.response == self.net._check_response( + self.response, content_type='application/json') try: mock_logger.debug.assert_called_with( 'Ignoring wrong Content-Type (%r) for JSON decodable response', @@ -504,8 +499,8 @@ class ClientNetworkTest(unittest.TestCase): self.response.json.return_value = {} self.response.headers['Content-Type'] = 'text/plain' # pylint: disable=protected-access - self.assertEqual(self.response, self.net._check_response( - self.response, content_type='application/json')) + assert self.response == self.net._check_response( + self.response, content_type='application/json') mock_logger.debug.assert_called_with( 'Ignoring wrong Content-Type (%r) for JSON decodable response', 'text/plain' @@ -515,22 +510,22 @@ class ClientNetworkTest(unittest.TestCase): self.response.ok = False self.response.status_code = 409 # pylint: disable=protected-access - self.assertRaises(errors.ConflictError, self.net._check_response, self.response) + with pytest.raises(errors.ConflictError): + self.net._check_response(self.response) def test_check_response_jobj(self): self.response.json.return_value = {} for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: self.response.headers['Content-Type'] = response_ct # pylint: disable=protected-access - self.assertEqual( - self.response, self.net._check_response(self.response)) + assert self.response == self.net._check_response(self.response) def test_send_request(self): self.net.session = mock.MagicMock() self.net.session.request.return_value = self.response # pylint: disable=protected-access - self.assertEqual(self.response, self.net._send_request( - 'HEAD', 'http://example.com/', 'foo', bar='baz')) + assert self.response == self.net._send_request( + 'HEAD', 'http://example.com/', 'foo', bar='baz') self.net.session.request.assert_called_once_with( 'HEAD', 'http://example.com/', 'foo', headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, bar='baz') @@ -552,8 +547,8 @@ class ClientNetworkTest(unittest.TestCase): self.net.session = mock.MagicMock() self.net.session.request.return_value = self.response # pylint: disable=protected-access - self.assertEqual(self.response, self.net._send_request( - 'POST', 'http://example.com/', 'foo', data='qux', bar='baz')) + assert self.response == self.net._send_request( + 'POST', 'http://example.com/', 'foo', data='qux', bar='baz') self.net.session.request.assert_called_once_with( 'POST', 'http://example.com/', 'foo', headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, data='qux', bar='baz') @@ -565,9 +560,8 @@ class ClientNetworkTest(unittest.TestCase): self.net.session.request.return_value = self.response self.net.verify_ssl = verify # pylint: disable=protected-access - self.assertEqual( - self.response, - self.net._send_request('GET', 'http://example.com/')) + assert self.response == \ + self.net._send_request('GET', 'http://example.com/') self.net.session.request.assert_called_once_with( 'GET', 'http://example.com/', verify=verify, timeout=mock.ANY, headers=mock.ANY) @@ -615,8 +609,8 @@ class ClientNetworkTest(unittest.TestCase): mock_requests.exceptions = requests.exceptions mock_requests.request.side_effect = requests.exceptions.RequestException # pylint: disable=protected-access - self.assertRaises(requests.exceptions.RequestException, - self.net._send_request, 'GET', 'uri') + with pytest.raises(requests.exceptions.RequestException): + self.net._send_request('GET', 'uri') def test_urllib_error(self): # Using a connection error to test a properly formatted error message @@ -626,12 +620,12 @@ class ClientNetworkTest(unittest.TestCase): # Value Error Generated Exceptions except ValueError as y: - self.assertEqual("Requesting localhost/nonexistent: " - "Connection refused", str(y)) + assert "Requesting localhost/nonexistent: " \ + "Connection refused" == str(y) # Requests Library Exceptions except requests.exceptions.ConnectionError as z: #pragma: no cover - self.assertTrue("'Connection aborted.'" in str(z) or "[WinError 10061]" in str(z)) + assert "'Connection aborted.'" in str(z) or "[WinError 10061]" in str(z) class ClientNetworkWithMockedResponseTest(unittest.TestCase): @@ -658,7 +652,7 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase): def send_request(*args, **kwargs): # pylint: disable=unused-argument,missing-docstring - self.assertNotIn("new_nonce_url", kwargs) + assert "new_nonce_url" not in kwargs method = args[0] uri = args[1] if method == 'HEAD' and uri != "new_nonce_uri": @@ -682,58 +676,60 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase): def check_response(self, response, content_type): # pylint: disable=missing-docstring - self.assertEqual(self.response, response) - self.assertEqual(self.content_type, content_type) - self.assertTrue(self.response.ok) + assert self.response == response + assert self.content_type == content_type + assert self.response.ok self.response.checked = True return self.response def test_head(self): - self.assertEqual(self.acmev1_nonce_response, self.net.head( - 'http://example.com/', 'foo', bar='baz')) + assert self.acmev1_nonce_response == self.net.head( + 'http://example.com/', 'foo', bar='baz') self.send_request.assert_called_once_with( 'HEAD', 'http://example.com/', 'foo', bar='baz') def test_head_v2(self): - self.assertEqual(self.response, self.net.head( - 'new_nonce_uri', 'foo', bar='baz')) + assert self.response == self.net.head( + 'new_nonce_uri', 'foo', bar='baz') self.send_request.assert_called_once_with( 'HEAD', 'new_nonce_uri', 'foo', bar='baz') def test_get(self): - self.assertEqual(self.response, self.net.get( - 'http://example.com/', content_type=self.content_type, bar='baz')) - self.assertTrue(self.response.checked) + assert self.response == self.net.get( + 'http://example.com/', content_type=self.content_type, bar='baz') + assert self.response.checked self.send_request.assert_called_once_with( 'GET', 'http://example.com/', bar='baz') def test_post_no_content_type(self): self.content_type = self.net.JOSE_CONTENT_TYPE - self.assertEqual(self.response, self.net.post('uri', self.obj)) - self.assertTrue(self.response.checked) + assert self.response == self.net.post('uri', self.obj) + assert self.response.checked def test_post(self): # pylint: disable=protected-access - self.assertEqual(self.response, self.net.post( - 'uri', self.obj, content_type=self.content_type)) - self.assertTrue(self.response.checked) + assert self.response == self.net.post( + 'uri', self.obj, content_type=self.content_type) + assert self.response.checked self.net._wrap_in_jws.assert_called_once_with( self.obj, jose.b64decode(self.all_nonces.pop()), "uri") self.available_nonces = [] - self.assertRaises(errors.MissingNonce, self.net.post, - 'uri', self.obj, content_type=self.content_type) + with pytest.raises(errors.MissingNonce): + self.net.post('uri', self.obj, content_type=self.content_type) self.net._wrap_in_jws.assert_called_with( self.obj, jose.b64decode(self.all_nonces.pop()), "uri") def test_post_wrong_initial_nonce(self): # HEAD self.available_nonces = [b'f', jose.b64encode(b'good')] - self.assertRaises(errors.BadNonce, self.net.post, 'uri', + with pytest.raises(errors.BadNonce): + self.net.post('uri', self.obj, content_type=self.content_type) def test_post_wrong_post_response_nonce(self): self.available_nonces = [jose.b64encode(b'good'), b'f'] - self.assertRaises(errors.BadNonce, self.net.post, 'uri', + with pytest.raises(errors.BadNonce): + self.net.post('uri', self.obj, content_type=self.content_type) def test_post_failed_retry(self): @@ -742,7 +738,8 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase): # pylint: disable=protected-access self.net._check_response = check_response - self.assertRaises(messages.Error, self.net.post, 'uri', + with pytest.raises(messages.Error): + self.net.post('uri', self.obj, content_type=self.content_type) def test_post_not_retried(self): @@ -752,7 +749,8 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase): # pylint: disable=protected-access self.net._check_response = check_response - self.assertRaises(messages.Error, self.net.post, 'uri', + with pytest.raises(messages.Error): + self.net.post('uri', self.obj, content_type=self.content_type) def test_post_successful_retry(self): @@ -761,16 +759,16 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase): self.response] # pylint: disable=protected-access - self.assertEqual(self.response, self.net.post( - 'uri', self.obj, content_type=self.content_type)) + assert self.response == self.net.post( + 'uri', self.obj, content_type=self.content_type) def test_head_get_post_error_passthrough(self): self.send_request.side_effect = requests.exceptions.RequestException for method in self.net.head, self.net.get: - self.assertRaises( - requests.exceptions.RequestException, method, 'GET', 'uri') - self.assertRaises(requests.exceptions.RequestException, - self.net.post, 'uri', obj=self.obj) + with pytest.raises(requests.exceptions.RequestException): + method('GET', 'uri') + with pytest.raises(requests.exceptions.RequestException): + self.net.post('uri', obj=self.obj) def test_post_bad_nonce_head(self): # pylint: disable=protected-access @@ -781,10 +779,11 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase): self.content_type = None check_response = mock.MagicMock() self.net._check_response = check_response - self.assertRaises(errors.ClientError, self.net.post, 'uri', + with pytest.raises(errors.ClientError): + self.net.post('uri', self.obj, content_type=self.content_type, new_nonce_url='new_nonce_uri') - self.assertEqual(check_response.call_count, 1) + assert check_response.call_count == 1 def test_new_nonce_uri_removed(self): self.content_type = None @@ -792,4 +791,4 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/acme/tests/crypto_util_test.py b/acme/tests/crypto_util_test.py index 0244bf835..409a2ce27 100644 --- a/acme/tests/crypto_util_test.py +++ b/acme/tests/crypto_util_test.py @@ -1,15 +1,17 @@ """Tests for acme.crypto_util.""" -import itertools import ipaddress +import itertools import socket import socketserver +import sys import threading import time -import unittest from typing import List +import unittest import josepy as jose import OpenSSL +import pytest from acme import errors import test_util @@ -54,18 +56,20 @@ class SSLSocketAndProbeSNITest(unittest.TestCase): def test_probe_ok(self): self._start_server() - self.assertEqual(self.cert, self._probe(b'foo')) + assert self.cert == self._probe(b'foo') def test_probe_not_recognized_name(self): self._start_server() - self.assertRaises(errors.Error, self._probe, b'bar') + with pytest.raises(errors.Error): + self._probe(b'bar') def test_probe_connection_error(self): self.server.server_close() original_timeout = socket.getdefaulttimeout() try: socket.setdefaulttimeout(1) - self.assertRaises(errors.Error, self._probe, b'bar') + with pytest.raises(errors.Error): + self._probe(b'bar') finally: socket.setdefaulttimeout(original_timeout) @@ -75,10 +79,10 @@ class SSLSocketTest(unittest.TestCase): def test_ssl_socket_invalid_arguments(self): from acme.crypto_util import SSLSocket - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = SSLSocket(None, {'sni': ('key', 'cert')}, cert_selection=lambda _: None) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = SSLSocket(None) @@ -95,15 +99,15 @@ class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase): return self._call(test_util.load_cert, name) def test_cert_one_san_no_common(self): - self.assertEqual(self._call_cert('cert-nocn.der'), - ['no-common-name.badssl.com']) + assert self._call_cert('cert-nocn.der') == \ + ['no-common-name.badssl.com'] def test_cert_no_sans_yes_common(self): - self.assertEqual(self._call_cert('cert.pem'), ['example.com']) + assert self._call_cert('cert.pem') == ['example.com'] def test_cert_two_sans_yes_common(self): - self.assertEqual(self._call_cert('cert-san.pem'), - ['example.com', 'www.example.com']) + assert self._call_cert('cert-san.pem') == \ + ['example.com', 'www.example.com'] class PyOpenSSLCertOrReqSANTest(unittest.TestCase): @@ -131,47 +135,47 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase): return self._call(test_util.load_csr, name) def test_cert_no_sans(self): - self.assertEqual(self._call_cert('cert.pem'), []) + assert self._call_cert('cert.pem') == [] def test_cert_two_sans(self): - self.assertEqual(self._call_cert('cert-san.pem'), - ['example.com', 'www.example.com']) + assert self._call_cert('cert-san.pem') == \ + ['example.com', 'www.example.com'] def test_cert_hundred_sans(self): - self.assertEqual(self._call_cert('cert-100sans.pem'), - ['example{0}.com'.format(i) for i in range(1, 101)]) + assert self._call_cert('cert-100sans.pem') == \ + ['example{0}.com'.format(i) for i in range(1, 101)] def test_cert_idn_sans(self): - self.assertEqual(self._call_cert('cert-idnsans.pem'), - self._get_idn_names()) + assert self._call_cert('cert-idnsans.pem') == \ + self._get_idn_names() def test_csr_no_sans(self): - self.assertEqual(self._call_csr('csr-nosans.pem'), []) + assert self._call_csr('csr-nosans.pem') == [] def test_csr_one_san(self): - self.assertEqual(self._call_csr('csr.pem'), ['example.com']) + assert self._call_csr('csr.pem') == ['example.com'] def test_csr_two_sans(self): - self.assertEqual(self._call_csr('csr-san.pem'), - ['example.com', 'www.example.com']) + assert self._call_csr('csr-san.pem') == \ + ['example.com', 'www.example.com'] def test_csr_six_sans(self): - self.assertEqual(self._call_csr('csr-6sans.pem'), + assert self._call_csr('csr-6sans.pem') == \ ['example.com', 'example.org', 'example.net', 'example.info', 'subdomain.example.com', - 'other.subdomain.example.com']) + 'other.subdomain.example.com'] def test_csr_hundred_sans(self): - self.assertEqual(self._call_csr('csr-100sans.pem'), - ['example{0}.com'.format(i) for i in range(1, 101)]) + assert self._call_csr('csr-100sans.pem') == \ + ['example{0}.com'.format(i) for i in range(1, 101)] def test_csr_idn_sans(self): - self.assertEqual(self._call_csr('csr-idnsans.pem'), - self._get_idn_names()) + assert self._call_csr('csr-idnsans.pem') == \ + self._get_idn_names() def test_critical_san(self): - self.assertEqual(self._call_cert('critical-san.pem'), - ['chicago-cubs.venafi.example', 'cubs.venafi.example']) + assert self._call_cert('critical-san.pem') == \ + ['chicago-cubs.venafi.example', 'cubs.venafi.example'] class PyOpenSSLCertOrReqSANIPTest(unittest.TestCase): @@ -190,30 +194,30 @@ class PyOpenSSLCertOrReqSANIPTest(unittest.TestCase): return self._call(test_util.load_csr, name) def test_cert_no_sans(self): - self.assertEqual(self._call_cert('cert.pem'), []) + assert self._call_cert('cert.pem') == [] def test_csr_no_sans(self): - self.assertEqual(self._call_csr('csr-nosans.pem'), []) + assert self._call_csr('csr-nosans.pem') == [] def test_cert_domain_sans(self): - self.assertEqual(self._call_cert('cert-san.pem'), []) + assert self._call_cert('cert-san.pem') == [] def test_csr_domain_sans(self): - self.assertEqual(self._call_csr('csr-san.pem'), []) + assert self._call_csr('csr-san.pem') == [] def test_cert_ip_two_sans(self): - self.assertEqual(self._call_cert('cert-ipsans.pem'), ['192.0.2.145', '203.0.113.1']) + assert self._call_cert('cert-ipsans.pem') == ['192.0.2.145', '203.0.113.1'] def test_csr_ip_two_sans(self): - self.assertEqual(self._call_csr('csr-ipsans.pem'), ['192.0.2.145', '203.0.113.1']) + assert self._call_csr('csr-ipsans.pem') == ['192.0.2.145', '203.0.113.1'] def test_csr_ipv6_sans(self): - self.assertEqual(self._call_csr('csr-ipv6sans.pem'), - ['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5']) + assert self._call_csr('csr-ipv6sans.pem') == \ + ['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5'] def test_cert_ipv6_sans(self): - self.assertEqual(self._call_cert('cert-ipv6sans.pem'), - ['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5']) + assert self._call_cert('cert-ipv6sans.pem') == \ + ['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5'] class GenSsCertTest(unittest.TestCase): @@ -232,12 +236,12 @@ class GenSsCertTest(unittest.TestCase): cert = gen_ss_cert(self.key, ['dummy'], force_san=True, ips=[ipaddress.ip_address("10.10.10.10")]) self.serial_num.append(cert.get_serial_number()) - self.assertGreaterEqual(len(set(self.serial_num)), self.cert_count) + assert len(set(self.serial_num)) >= self.cert_count def test_no_name(self): from acme.crypto_util import gen_ss_cert - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): gen_ss_cert(self.key, ips=[ipaddress.ip_address("1.1.1.1")]) gen_ss_cert(self.key) @@ -255,41 +259,39 @@ class MakeCSRTest(unittest.TestCase): def test_make_csr(self): csr_pem = self._call_with_key(["a.example", "b.example"]) - self.assertIn(b'--BEGIN CERTIFICATE REQUEST--', csr_pem) - self.assertIn(b'--END CERTIFICATE REQUEST--', csr_pem) + assert b'--BEGIN CERTIFICATE REQUEST--' in csr_pem + assert b'--END CERTIFICATE REQUEST--' in csr_pem csr = OpenSSL.crypto.load_certificate_request( OpenSSL.crypto.FILETYPE_PEM, csr_pem) # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't # have a get_extensions() method, so we skip this test if the method # isn't available. if hasattr(csr, 'get_extensions'): - self.assertEqual(len(csr.get_extensions()), 1) - self.assertEqual(csr.get_extensions()[0].get_data(), + assert len(csr.get_extensions()) == 1 + assert csr.get_extensions()[0].get_data() == \ OpenSSL.crypto.X509Extension( b'subjectAltName', critical=False, value=b'DNS:a.example, DNS:b.example', - ).get_data(), - ) + ).get_data() def test_make_csr_ip(self): csr_pem = self._call_with_key(["a.example"], False, [ipaddress.ip_address('127.0.0.1'), ipaddress.ip_address('::1')]) - self.assertIn(b'--BEGIN CERTIFICATE REQUEST--' , csr_pem) - self.assertIn(b'--END CERTIFICATE REQUEST--' , csr_pem) + assert b'--BEGIN CERTIFICATE REQUEST--' in csr_pem + assert b'--END CERTIFICATE REQUEST--' in csr_pem csr = OpenSSL.crypto.load_certificate_request( OpenSSL.crypto.FILETYPE_PEM, csr_pem) # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't # have a get_extensions() method, so we skip this test if the method # isn't available. if hasattr(csr, 'get_extensions'): - self.assertEqual(len(csr.get_extensions()), 1) - self.assertEqual(csr.get_extensions()[0].get_data(), + assert len(csr.get_extensions()) == 1 + assert csr.get_extensions()[0].get_data() == \ OpenSSL.crypto.X509Extension( b'subjectAltName', critical=False, value=b'DNS:a.example, IP:127.0.0.1, IP:::1', - ).get_data(), - ) + ).get_data() # for IP san it's actually need to be octet-string, # but somewhere downstream thankfully handle it for us @@ -302,25 +304,26 @@ class MakeCSRTest(unittest.TestCase): # have a get_extensions() method, so we skip this test if the method # isn't available. if hasattr(csr, 'get_extensions'): - self.assertEqual(len(csr.get_extensions()), 2) + assert len(csr.get_extensions()) == 2 # NOTE: Ideally we would filter by the TLS Feature OID, but # OpenSSL.crypto.X509Extension doesn't give us the extension's raw OID, # and the shortname field is just "UNDEF" must_staple_exts = [e for e in csr.get_extensions() if e.get_data() == b"0\x03\x02\x01\x05"] - self.assertEqual(len(must_staple_exts), 1, - "Expected exactly one Must Staple extension") + assert len(must_staple_exts) == 1, \ + "Expected exactly one Must Staple extension" def test_make_csr_without_hostname(self): - self.assertRaises(ValueError, self._call_with_key) + with pytest.raises(ValueError): + self._call_with_key() def test_make_csr_correct_version(self): csr_pem = self._call_with_key(["a.example"]) csr = OpenSSL.crypto.load_certificate_request( OpenSSL.crypto.FILETYPE_PEM, csr_pem) - self.assertEqual(csr.get_version(), 0, - "Expected CSR version to be v1 (encoded as 0), per RFC 2986, section 4") + assert csr.get_version() == 0, \ + "Expected CSR version to be v1 (encoded as 0), per RFC 2986, section 4" class DumpPyopensslChainTest(unittest.TestCase): @@ -338,7 +341,7 @@ class DumpPyopensslChainTest(unittest.TestCase): length = sum( len(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)) for cert in loaded) - self.assertEqual(len(self._call(loaded)), length) + assert len(self._call(loaded)) == length def test_dump_pyopenssl_chain_wrapped(self): names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem'] @@ -347,8 +350,8 @@ class DumpPyopensslChainTest(unittest.TestCase): wrapped = [wrap_func(cert) for cert in loaded] dump_func = OpenSSL.crypto.dump_certificate length = sum(len(dump_func(OpenSSL.crypto.FILETYPE_PEM, cert)) for cert in loaded) - self.assertEqual(len(self._call(wrapped)), length) + assert len(self._call(wrapped)) == length if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/acme/tests/errors_test.py b/acme/tests/errors_test.py index f325b284e..97f739d40 100644 --- a/acme/tests/errors_test.py +++ b/acme/tests/errors_test.py @@ -1,7 +1,10 @@ """Tests for acme.errors.""" +import sys import unittest from unittest import mock +import pytest + class BadNonceTest(unittest.TestCase): """Tests for acme.errors.BadNonce.""" @@ -11,7 +14,7 @@ class BadNonceTest(unittest.TestCase): self.error = BadNonce(nonce="xxx", error="error") def test_str(self): - self.assertEqual("Invalid nonce ('xxx'): error", str(self.error)) + assert "Invalid nonce ('xxx'): error" == str(self.error) class MissingNonceTest(unittest.TestCase): @@ -24,8 +27,8 @@ class MissingNonceTest(unittest.TestCase): self.error = MissingNonce(self.response) def test_str(self): - self.assertIn("FOO", str(self.error)) - self.assertIn("{}", str(self.error)) + assert "FOO" in str(self.error) + assert "{}" in str(self.error) class PollErrorTest(unittest.TestCase): @@ -40,13 +43,13 @@ class PollErrorTest(unittest.TestCase): mock.sentinel.AR: mock.sentinel.AR2}) def test_timeout(self): - self.assertTrue(self.timeout.timeout) - self.assertFalse(self.invalid.timeout) + assert self.timeout.timeout + assert not self.invalid.timeout def test_repr(self): - self.assertEqual('PollError(exhausted=%s, updated={sentinel.AR: ' - 'sentinel.AR2})' % repr(set()), repr(self.invalid)) + assert 'PollError(exhausted=%s, updated={sentinel.AR: ' \ + 'sentinel.AR2})' % repr(set()) == repr(self.invalid) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/acme/tests/fields_test.py b/acme/tests/fields_test.py index 68e5c9a7f..a45bdc47b 100644 --- a/acme/tests/fields_test.py +++ b/acme/tests/fields_test.py @@ -1,9 +1,11 @@ """Tests for acme.fields.""" import datetime +import sys import unittest import warnings import josepy as jose +import pytest import pytz @@ -15,16 +17,17 @@ class FixedTest(unittest.TestCase): self.field = fixed('name', 'x') def test_decode(self): - self.assertEqual('x', self.field.decode('x')) + assert 'x' == self.field.decode('x') def test_decode_bad(self): - self.assertRaises(jose.DeserializationError, self.field.decode, 'y') + with pytest.raises(jose.DeserializationError): + self.field.decode('y') def test_encode(self): - self.assertEqual('x', self.field.encode('x')) + assert 'x' == self.field.encode('x') def test_encode_override(self): - self.assertEqual('y', self.field.encode('y')) + assert 'y' == self.field.encode('y') class RFC3339FieldTest(unittest.TestCase): @@ -36,24 +39,22 @@ class RFC3339FieldTest(unittest.TestCase): def test_default_encoder(self): from acme.fields import RFC3339Field - self.assertEqual( - self.encoded, RFC3339Field.default_encoder(self.decoded)) + assert self.encoded == RFC3339Field.default_encoder(self.decoded) def test_default_encoder_naive_fails(self): from acme.fields import RFC3339Field - self.assertRaises( - ValueError, RFC3339Field.default_encoder, datetime.datetime.now()) + with pytest.raises(ValueError): + RFC3339Field.default_encoder(datetime.datetime.now()) def test_default_decoder(self): from acme.fields import RFC3339Field - self.assertEqual( - self.decoded, RFC3339Field.default_decoder(self.encoded)) + assert self.decoded == RFC3339Field.default_decoder(self.encoded) def test_default_decoder_raises_deserialization_error(self): from acme.fields import RFC3339Field - self.assertRaises( - jose.DeserializationError, RFC3339Field.default_decoder, '') + with pytest.raises(jose.DeserializationError): + RFC3339Field.default_decoder('') if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/acme/tests/jose_test.py b/acme/tests/jose_test.py index e008cb6fc..216bc69ca 100644 --- a/acme/tests/jose_test.py +++ b/acme/tests/jose_test.py @@ -1,53 +1,54 @@ """Tests for acme.jose shim.""" import importlib +import sys import unittest +import pytest -class JoseTest(unittest.TestCase): - """Tests for acme.jose shim.""" - def _test_it(self, submodule, attribute): - if submodule: - acme_jose_path = 'acme.jose.' + submodule - josepy_path = 'josepy.' + submodule - else: - acme_jose_path = 'acme.jose' - josepy_path = 'josepy' - acme_jose_mod = importlib.import_module(acme_jose_path) - josepy_mod = importlib.import_module(josepy_path) +def _test_it(submodule, attribute): + if submodule: + acme_jose_path = 'acme.jose.' + submodule + josepy_path = 'josepy.' + submodule + else: + acme_jose_path = 'acme.jose' + josepy_path = 'josepy' + acme_jose_mod = importlib.import_module(acme_jose_path) + josepy_mod = importlib.import_module(josepy_path) - self.assertIs(acme_jose_mod, josepy_mod) - self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute)) + assert acme_jose_mod is josepy_mod + assert getattr(acme_jose_mod, attribute) is getattr(josepy_mod, attribute) - # We use the imports below with eval, but pylint doesn't - # understand that. - import acme # pylint: disable=unused-import - import josepy # pylint: disable=unused-import - acme_jose_mod = eval(acme_jose_path) # pylint: disable=eval-used - josepy_mod = eval(josepy_path) # pylint: disable=eval-used - self.assertIs(acme_jose_mod, josepy_mod) - self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute)) + # We use the imports below with eval, but pylint doesn't + # understand that. + import josepy # pylint: disable=unused-import - def test_top_level(self): - self._test_it('', 'RS512') + import acme # pylint: disable=unused-import + acme_jose_mod = eval(acme_jose_path) # pylint: disable=eval-used + josepy_mod = eval(josepy_path) # pylint: disable=eval-used + assert acme_jose_mod is josepy_mod + assert getattr(acme_jose_mod, attribute) is getattr(josepy_mod, attribute) - def test_submodules(self): - # This test ensures that the modules in josepy that were - # available at the time it was moved into its own package are - # available under acme.jose. Backwards compatibility with new - # modules or testing code is not maintained. - mods_and_attrs = [('b64', 'b64decode',), - ('errors', 'Error',), - ('interfaces', 'JSONDeSerializable',), - ('json_util', 'Field',), - ('jwa', 'HS256',), - ('jwk', 'JWK',), - ('jws', 'JWS',), - ('util', 'ImmutableMap',),] +def test_top_level(): + _test_it('', 'RS512') - for mod, attr in mods_and_attrs: - self._test_it(mod, attr) +def test_submodules(): + # This test ensures that the modules in josepy that were + # available at the time it was moved into its own package are + # available under acme.jose. Backwards compatibility with new + # modules or testing code is not maintained. + mods_and_attrs = [('b64', 'b64decode',), + ('errors', 'Error',), + ('interfaces', 'JSONDeSerializable',), + ('json_util', 'Field',), + ('jwa', 'HS256',), + ('jwk', 'JWK',), + ('jws', 'JWS',), + ('util', 'ImmutableMap',),] + + for mod, attr in mods_and_attrs: + _test_it(mod, attr) if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/acme/tests/jws_test.py b/acme/tests/jws_test.py index 0787fb340..901ae970b 100644 --- a/acme/tests/jws_test.py +++ b/acme/tests/jws_test.py @@ -1,7 +1,9 @@ """Tests for acme.jws.""" +import sys import unittest import josepy as jose +import pytest import test_util @@ -25,9 +27,9 @@ class HeaderTest(unittest.TestCase): from acme.jws import Header nonce_field = Header._fields['nonce'] - self.assertRaises( - jose.DeserializationError, nonce_field.decode, self.wrong_nonce) - self.assertEqual(b'foo', nonce_field.decode(self.good_nonce)) + with pytest.raises(jose.DeserializationError): + nonce_field.decode(self.wrong_nonce) + assert b'foo' == nonce_field.decode(self.good_nonce) class JWSTest(unittest.TestCase): @@ -45,22 +47,22 @@ class JWSTest(unittest.TestCase): jws = JWS.sign(payload=b'foo', key=self.privkey, alg=jose.RS256, nonce=self.nonce, url=self.url, kid=self.kid) - self.assertEqual(jws.signature.combined.nonce, self.nonce) - self.assertEqual(jws.signature.combined.url, self.url) - self.assertEqual(jws.signature.combined.kid, self.kid) - self.assertIsNone(jws.signature.combined.jwk) + assert jws.signature.combined.nonce == self.nonce + assert jws.signature.combined.url == self.url + assert jws.signature.combined.kid == self.kid + assert jws.signature.combined.jwk is None # TODO: check that nonce is in protected header - self.assertEqual(jws, JWS.from_json(jws.to_json())) + assert jws == JWS.from_json(jws.to_json()) def test_jwk_serialize(self): from acme.jws import JWS jws = JWS.sign(payload=b'foo', key=self.privkey, alg=jose.RS256, nonce=self.nonce, url=self.url) - self.assertIsNone(jws.signature.combined.kid) - self.assertEqual(jws.signature.combined.jwk, self.pubkey) + assert jws.signature.combined.kid is None + assert jws.signature.combined.jwk == self.pubkey if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/acme/tests/messages_test.py b/acme/tests/messages_test.py index cbff65771..781a1b1ac 100644 --- a/acme/tests/messages_test.py +++ b/acme/tests/messages_test.py @@ -1,10 +1,13 @@ """Tests for acme.messages.""" +import contextlib +import sys from typing import Dict import unittest from unittest import mock import warnings import josepy as jose +import pytest from acme import challenges import test_util @@ -18,7 +21,10 @@ class ErrorTest(unittest.TestCase): """Tests for acme.messages.Error.""" def setUp(self): - from acme.messages import Error, ERROR_PREFIX, Identifier, IDENTIFIER_FQDN + from acme.messages import Error + from acme.messages import ERROR_PREFIX + from acme.messages import Identifier + from acme.messages import IDENTIFIER_FQDN self.error = Error.with_code('malformed', detail='foo', title='title') self.jobj = { 'detail': 'foo', @@ -33,11 +39,11 @@ class ErrorTest(unittest.TestCase): def test_default_typ(self): from acme.messages import Error - self.assertEqual(Error().typ, 'about:blank') + assert Error().typ == 'about:blank' def test_from_json_empty(self): from acme.messages import Error - self.assertEqual(Error(), Error.from_json('{}')) + assert Error() == Error.from_json('{}') def test_from_json_hashable(self): from acme.messages import Error @@ -48,48 +54,63 @@ class ErrorTest(unittest.TestCase): parsed_error = Error.from_json(self.error_with_subproblems.to_json()) - self.assertEqual(1, len(parsed_error.subproblems)) - self.assertEqual(self.subproblem, parsed_error.subproblems[0]) + assert 1 == len(parsed_error.subproblems) + assert self.subproblem == parsed_error.subproblems[0] def test_description(self): - self.assertEqual('The request message was malformed', self.error.description) - self.assertIsNone(self.error_custom.description) + assert 'The request message was malformed' == self.error.description + assert self.error_custom.description is None def test_code(self): from acme.messages import Error - self.assertEqual('malformed', self.error.code) - self.assertIsNone(self.error_custom.code) - self.assertIsNone(Error().code) + assert 'malformed' == self.error.code + assert self.error_custom.code is None + assert Error().code is None def test_is_acme_error(self): - from acme.messages import is_acme_error, Error - self.assertTrue(is_acme_error(self.error)) - self.assertFalse(is_acme_error(self.error_custom)) - self.assertFalse(is_acme_error(Error())) - self.assertFalse(is_acme_error(self.empty_error)) - self.assertFalse(is_acme_error("must pet all the {dogs|rabbits}")) + from acme.messages import Error + from acme.messages import is_acme_error + assert is_acme_error(self.error) + assert not is_acme_error(self.error_custom) + assert not is_acme_error(Error()) + assert not is_acme_error(self.empty_error) + assert not is_acme_error("must pet all the {dogs|rabbits}") def test_unicode_error(self): - from acme.messages import Error, is_acme_error + from acme.messages import Error + from acme.messages import is_acme_error arabic_error = Error.with_code( 'malformed', detail=u'\u0639\u062f\u0627\u0644\u0629', title='title') - self.assertTrue(is_acme_error(arabic_error)) + assert is_acme_error(arabic_error) def test_with_code(self): - from acme.messages import Error, is_acme_error - self.assertTrue(is_acme_error(Error.with_code('badCSR'))) - self.assertRaises(ValueError, Error.with_code, 'not an ACME error code') + from acme.messages import Error + from acme.messages import is_acme_error + assert is_acme_error(Error.with_code('badCSR')) + with pytest.raises(ValueError): + Error.with_code('not an ACME error code') def test_str(self): - self.assertEqual( - str(self.error), - u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}" - .format(self.error)) - self.assertEqual( - str(self.error_with_subproblems), + assert str(self.error) == \ + u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}" \ + .format(self.error) + assert str(self.error_with_subproblems) == \ (u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}\n"+ u"Problem for {1.identifier.value}: {1.typ} :: {1.description} :: {1.detail} :: {1.title}").format( - self.error_with_subproblems, self.subproblem)) + self.error_with_subproblems, self.subproblem) + + # this test is based on a minimal reproduction of a contextmanager/immutable + # exception related error: https://github.com/python/cpython/issues/99856 + def test_setting_traceback(self): + assert self.error_custom.__traceback__ is None + + try: + 1/0 + except ZeroDivisionError as e: + self.error_custom.__traceback__ = e.__traceback__ + + assert self.error_custom.__traceback__ is not None + class ConstantTest(unittest.TestCase): """Tests for acme.messages._Constant.""" @@ -105,28 +126,28 @@ class ConstantTest(unittest.TestCase): self.const_b = MockConstant('b') def test_to_partial_json(self): - self.assertEqual('a', self.const_a.to_partial_json()) - self.assertEqual('b', self.const_b.to_partial_json()) + assert 'a' == self.const_a.to_partial_json() + assert 'b' == self.const_b.to_partial_json() def test_from_json(self): - self.assertEqual(self.const_a, self.MockConstant.from_json('a')) - self.assertRaises( - jose.DeserializationError, self.MockConstant.from_json, 'c') + assert self.const_a == self.MockConstant.from_json('a') + with pytest.raises(jose.DeserializationError): + self.MockConstant.from_json('c') def test_from_json_hashable(self): hash(self.MockConstant.from_json('a')) def test_repr(self): - self.assertEqual('MockConstant(a)', repr(self.const_a)) - self.assertEqual('MockConstant(b)', repr(self.const_b)) + assert 'MockConstant(a)' == repr(self.const_a) + assert 'MockConstant(b)' == repr(self.const_b) def test_equality(self): const_a_prime = self.MockConstant('a') - self.assertNotEqual(self.const_a, self.const_b) - self.assertEqual(self.const_a, const_a_prime) + assert self.const_a != self.const_b + assert self.const_a == const_a_prime - self.assertNotEqual(self.const_a, self.const_b) - self.assertEqual(self.const_a, const_a_prime) + assert self.const_a != self.const_b + assert self.const_a == const_a_prime class DirectoryTest(unittest.TestCase): @@ -149,19 +170,21 @@ class DirectoryTest(unittest.TestCase): Directory({'foo': 'bar'}) def test_getitem(self): - self.assertEqual('reg', self.dir['newReg']) + assert 'reg' == self.dir['newReg'] def test_getitem_fails_with_key_error(self): - self.assertRaises(KeyError, self.dir.__getitem__, 'foo') + with pytest.raises(KeyError): + self.dir.__getitem__('foo') def test_getattr(self): - self.assertEqual('reg', self.dir.newReg) + assert 'reg' == self.dir.newReg def test_getattr_fails_with_attribute_error(self): - self.assertRaises(AttributeError, self.dir.__getattr__, 'foo') + with pytest.raises(AttributeError): + self.dir.__getattr__('foo') def test_to_json(self): - self.assertEqual(self.dir.to_json(), { + assert self.dir.to_json() == { 'newReg': 'reg', 'newCert': 'cert', 'meta': { @@ -169,7 +192,7 @@ class DirectoryTest(unittest.TestCase): 'website': 'https://www.example.com/', 'caaIdentities': ['example.com'], }, - }) + } def test_from_json_deserialization_unknown_key_success(self): # pylint: disable=no-self-use from acme.messages import Directory @@ -180,7 +203,7 @@ class DirectoryTest(unittest.TestCase): for k in self.dir.meta: if k == 'terms_of_service': result = self.dir.meta[k] == 'https://example.com/acme/terms' - self.assertTrue(result) + assert result class ExternalAccountBindingTest(unittest.TestCase): @@ -197,8 +220,8 @@ class ExternalAccountBindingTest(unittest.TestCase): from acme.messages import ExternalAccountBinding eab = ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir) - self.assertEqual(len(eab), 3) - self.assertEqual(sorted(eab.keys()), sorted(['protected', 'payload', 'signature'])) + assert len(eab) == 3 + assert sorted(eab.keys()) == sorted(['protected', 'payload', 'signature']) class RegistrationTest(unittest.TestCase): @@ -227,13 +250,15 @@ class RegistrationTest(unittest.TestCase): def test_from_data(self): from acme.messages import Registration reg = Registration.from_data(phone='1234', email='admin@foo.com') - self.assertEqual(reg.contact, ( + assert reg.contact == ( 'tel:1234', 'mailto:admin@foo.com', - )) + ) def test_new_registration_from_data_with_eab(self): - from acme.messages import NewRegistration, ExternalAccountBinding, Directory + from acme.messages import Directory + from acme.messages import ExternalAccountBinding + from acme.messages import NewRegistration key = jose.jwk.JWKRSA(key=KEY.public_key()) kid = "kid-for-testing" hmac_key = "hmac-key-for-testing" @@ -242,24 +267,24 @@ class RegistrationTest(unittest.TestCase): }) eab = ExternalAccountBinding.from_data(key, kid, hmac_key, directory) reg = NewRegistration.from_data(email='admin@foo.com', external_account_binding=eab) - self.assertEqual(reg.contact, ( + assert reg.contact == ( 'mailto:admin@foo.com', - )) - self.assertEqual(sorted(reg.external_account_binding.keys()), - sorted(['protected', 'payload', 'signature'])) + ) + assert sorted(reg.external_account_binding.keys()) == \ + sorted(['protected', 'payload', 'signature']) def test_phones(self): - self.assertEqual(('1234',), self.reg.phones) + assert ('1234',) == self.reg.phones def test_emails(self): - self.assertEqual(('admin@foo.com',), self.reg.emails) + assert ('admin@foo.com',) == self.reg.emails def test_to_partial_json(self): - self.assertEqual(self.jobj_to, self.reg.to_partial_json()) + assert self.jobj_to == self.reg.to_partial_json() def test_from_json(self): from acme.messages import Registration - self.assertEqual(self.reg, Registration.from_json(self.jobj_from)) + assert self.reg == Registration.from_json(self.jobj_from) def test_from_json_hashable(self): from acme.messages import Registration @@ -270,13 +295,13 @@ class RegistrationTest(unittest.TestCase): empty_new_reg = NewRegistration() new_reg_with_contact = NewRegistration(contact=()) - self.assertEqual(empty_new_reg.contact, ()) - self.assertEqual(new_reg_with_contact.contact, ()) + assert empty_new_reg.contact == () + assert new_reg_with_contact.contact == () - self.assertNotIn('contact', empty_new_reg.to_partial_json()) - self.assertNotIn('contact', empty_new_reg.fields_to_partial_json()) - self.assertIn('contact', new_reg_with_contact.to_partial_json()) - self.assertIn('contact', new_reg_with_contact.fields_to_partial_json()) + assert 'contact' not in empty_new_reg.to_partial_json() + assert 'contact' not in empty_new_reg.fields_to_partial_json() + assert 'contact' in new_reg_with_contact.to_partial_json() + assert 'contact' in new_reg_with_contact.fields_to_partial_json() class UpdateRegistrationTest(unittest.TestCase): @@ -285,9 +310,8 @@ class UpdateRegistrationTest(unittest.TestCase): def test_empty(self): from acme.messages import UpdateRegistration jstring = '{"resource": "reg"}' - self.assertEqual('{}', UpdateRegistration().json_dumps()) - self.assertEqual( - UpdateRegistration(), UpdateRegistration.json_loads(jstring)) + assert '{}' == UpdateRegistration().json_dumps() + assert UpdateRegistration() == UpdateRegistration.json_loads(jstring) class RegistrationResourceTest(unittest.TestCase): @@ -300,11 +324,11 @@ class RegistrationResourceTest(unittest.TestCase): terms_of_service=mock.sentinel.terms_of_service) def test_to_partial_json(self): - self.assertEqual(self.regr.to_json(), { + assert self.regr.to_json() == { 'body': mock.sentinel.body, 'uri': mock.sentinel.uri, 'terms_of_service': mock.sentinel.terms_of_service, - }) + } class ChallengeResourceTest(unittest.TestCase): @@ -312,8 +336,8 @@ class ChallengeResourceTest(unittest.TestCase): def test_uri(self): from acme.messages import ChallengeResource - self.assertEqual('http://challb', ChallengeResource(body=mock.MagicMock( - uri='http://challb'), authzr_uri='http://authz').uri) + assert 'http://challb' == ChallengeResource(body=mock.MagicMock( + uri='http://challb'), authzr_uri='http://authz').uri class ChallengeBodyTest(unittest.TestCase): @@ -347,22 +371,22 @@ class ChallengeBodyTest(unittest.TestCase): } def test_encode(self): - self.assertEqual(self.challb.encode('uri'), self.challb.uri) + assert self.challb.encode('uri') == self.challb.uri def test_to_partial_json(self): - self.assertEqual(self.jobj_to, self.challb.to_partial_json()) + assert self.jobj_to == self.challb.to_partial_json() def test_from_json(self): from acme.messages import ChallengeBody - self.assertEqual(self.challb, ChallengeBody.from_json(self.jobj_from)) + assert self.challb == ChallengeBody.from_json(self.jobj_from) def test_from_json_hashable(self): from acme.messages import ChallengeBody hash(ChallengeBody.from_json(self.jobj_from)) def test_proxy(self): - self.assertEqual(jose.b64decode( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'), self.challb.token) + assert jose.b64decode( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA') == self.challb.token class AuthorizationTest(unittest.TestCase): @@ -410,7 +434,7 @@ class AuthorizationResourceTest(unittest.TestCase): authzr = AuthorizationResource( uri=mock.sentinel.uri, body=mock.sentinel.body) - self.assertIsInstance(authzr, jose.JSONDeSerializable) + assert isinstance(authzr, jose.JSONDeSerializable) class CertificateRequestTest(unittest.TestCase): @@ -421,10 +445,9 @@ class CertificateRequestTest(unittest.TestCase): self.req = CertificateRequest(csr=CSR) def test_json_de_serializable(self): - self.assertIsInstance(self.req, jose.JSONDeSerializable) + assert isinstance(self.req, jose.JSONDeSerializable) from acme.messages import CertificateRequest - self.assertEqual( - self.req, CertificateRequest.from_json(self.req.to_json())) + assert self.req == CertificateRequest.from_json(self.req.to_json()) class CertificateResourceTest(unittest.TestCase): @@ -437,10 +460,9 @@ class CertificateResourceTest(unittest.TestCase): cert_chain_uri=mock.sentinel.cert_chain_uri) def test_json_de_serializable(self): - self.assertIsInstance(self.certr, jose.JSONDeSerializable) + assert isinstance(self.certr, jose.JSONDeSerializable) from acme.messages import CertificateResource - self.assertEqual( - self.certr, CertificateResource.from_json(self.certr.to_json())) + assert self.certr == CertificateResource.from_json(self.certr.to_json()) class RevocationTest(unittest.TestCase): @@ -464,11 +486,11 @@ class OrderResourceTest(unittest.TestCase): body=mock.sentinel.body, uri=mock.sentinel.uri) def test_to_partial_json(self): - self.assertEqual(self.regr.to_json(), { + assert self.regr.to_json() == { 'body': mock.sentinel.body, 'uri': mock.sentinel.uri, 'authorizations': None, - }) + } class NewOrderTest(unittest.TestCase): @@ -480,9 +502,9 @@ class NewOrderTest(unittest.TestCase): identifiers=mock.sentinel.identifiers) def test_to_partial_json(self): - self.assertEqual(self.reg.to_json(), { + assert self.reg.to_json() == { 'identifiers': mock.sentinel.identifiers, - }) + } class JWSPayloadRFC8555Compliant(unittest.TestCase): @@ -494,8 +516,8 @@ class JWSPayloadRFC8555Compliant(unittest.TestCase): jobj = new_order.json_dumps(indent=2).encode() # RFC8555 states that JWS bodies must not have a resource field. - self.assertEqual(jobj, b'{}') + assert jobj == b'{}' if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/acme/tests/standalone_test.py b/acme/tests/standalone_test.py index ad5751fcf..4fcdceb57 100644 --- a/acme/tests/standalone_test.py +++ b/acme/tests/standalone_test.py @@ -2,18 +2,19 @@ import http.client as http_client import socket import socketserver +import sys import threading -import unittest from typing import Set +import unittest from unittest import mock import josepy as jose +import pytest import requests from acme import challenges from acme import crypto_util from acme import errors - import test_util @@ -58,14 +59,13 @@ class HTTP01ServerTest(unittest.TestCase): def test_index(self): response = requests.get( 'http://localhost:{0}'.format(self.port), verify=False) - self.assertEqual( - response.text, 'ACME client standalone challenge solver') - self.assertTrue(response.ok) + assert response.text == 'ACME client standalone challenge solver' + assert response.ok def test_404(self): response = requests.get( 'http://localhost:{0}/foo'.format(self.port), verify=False) - self.assertEqual(response.status_code, http_client.NOT_FOUND) + assert response.status_code == http_client.NOT_FOUND def _test_http01(self, add): chall = challenges.HTTP01(token=(b'x' * 16)) @@ -81,10 +81,10 @@ class HTTP01ServerTest(unittest.TestCase): port=self.port) def test_http01_found(self): - self.assertTrue(self._test_http01(add=True)) + assert self._test_http01(add=True) def test_http01_not_found(self): - self.assertFalse(self._test_http01(add=False)) + assert not self._test_http01(add=False) def test_timely_shutdown(self): from acme.standalone import HTTP01Server @@ -106,7 +106,7 @@ class HTTP01ServerTest(unittest.TestCase): # may raise error because socket could already be closed pass - self.assertFalse(is_hung, msg='Server shutdown should not be hung') + assert not is_hung, 'Server shutdown should not be hung' @unittest.skipIf(not challenges.TLSALPN01.is_supported(), "pyOpenSSL too old") @@ -149,14 +149,12 @@ class TLSALPN01ServerTest(unittest.TestCase): b'localhost', host=host, port=port, timeout=1, alpn_protocols=[b"acme-tls/1"]) # Expect challenge cert when connecting with ALPN. - self.assertEqual( - jose.ComparableX509(cert), + assert jose.ComparableX509(cert) == \ jose.ComparableX509(self.challenge_certs[b'localhost'][1]) - ) def test_bad_alpn(self): host, port = self.server.socket.getsockname()[:2] - with self.assertRaises(errors.Error): + with pytest.raises(errors.Error): crypto_util.probe_sni( b'localhost', host=host, port=port, timeout=1, alpn_protocols=[b"bad-alpn"]) @@ -190,16 +188,17 @@ class BaseDualNetworkedServersTest(unittest.TestCase): @mock.patch("socket.socket.bind") def test_fail_to_bind(self, mock_bind): from errno import EADDRINUSE + from acme.standalone import BaseDualNetworkedServers mock_bind.side_effect = socket.error(EADDRINUSE, "Fake addr in use error") - with self.assertRaises(socket.error) as em: + with pytest.raises(socket.error) as exc_info: BaseDualNetworkedServers( BaseDualNetworkedServersTest.SingleProtocolServer, ('', 0), socketserver.BaseRequestHandler) - self.assertEqual(em.exception.errno, EADDRINUSE) + assert exc_info.value.errno == EADDRINUSE def test_ports_equal(self): from acme.standalone import BaseDualNetworkedServers @@ -213,7 +212,7 @@ class BaseDualNetworkedServersTest(unittest.TestCase): for sockname in socknames: port = sockname[1] if prev_port: - self.assertEqual(prev_port, port) + assert prev_port == port prev_port = port @@ -237,14 +236,13 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase): def test_index(self): response = requests.get( 'http://localhost:{0}'.format(self.port), verify=False) - self.assertEqual( - response.text, 'ACME client standalone challenge solver') - self.assertTrue(response.ok) + assert response.text == 'ACME client standalone challenge solver' + assert response.ok def test_404(self): response = requests.get( 'http://localhost:{0}/foo'.format(self.port), verify=False) - self.assertEqual(response.status_code, http_client.NOT_FOUND) + assert response.status_code == http_client.NOT_FOUND def _test_http01(self, add): chall = challenges.HTTP01(token=(b'x' * 16)) @@ -260,11 +258,11 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase): port=self.port) def test_http01_found(self): - self.assertTrue(self._test_http01(add=True)) + assert self._test_http01(add=True) def test_http01_not_found(self): - self.assertFalse(self._test_http01(add=False)) + assert not self._test_http01(add=False) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/acme/tests/test_util.py b/acme/tests/test_util.py index efa5a219c..9a5de4b25 100644 --- a/acme/tests/test_util.py +++ b/acme/tests/test_util.py @@ -8,9 +8,9 @@ import os from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization import josepy as jose +from josepy.util import ComparableECKey from OpenSSL import crypto import pkg_resources -from josepy.util import ComparableECKey def load_vector(*names): diff --git a/acme/tests/util_test.py b/acme/tests/util_test.py index 00aa8b02d..8918755fe 100644 --- a/acme/tests/util_test.py +++ b/acme/tests/util_test.py @@ -1,16 +1,16 @@ """Tests for acme.util.""" +import sys import unittest +import pytest -class MapKeysTest(unittest.TestCase): - """Tests for acme.util.map_keys.""" - def test_it(self): - from acme.util import map_keys - self.assertEqual({'a': 'b', 'c': 'd'}, - map_keys({'a': 'b', 'c': 'd'}, lambda key: key)) - self.assertEqual({2: 2, 4: 4}, map_keys({1: 2, 3: 4}, lambda x: x + 1)) +def test_it(): + from acme.util import map_keys + assert {'a': 'b', 'c': 'd'} == \ + map_keys({'a': 'b', 'c': 'd'}, lambda key: key) + assert {2: 2, 4: 4} == map_keys({1: 2, 3: 4}, lambda x: x + 1) if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/certbot_apache/_internal/augeasparser.py b/certbot-apache/certbot_apache/_internal/augeasparser.py index cc57c9cb6..7eca461a3 100644 --- a/certbot-apache/certbot_apache/_internal/augeasparser.py +++ b/certbot-apache/certbot_apache/_internal/augeasparser.py @@ -74,15 +74,14 @@ from typing import Set from typing import Tuple from typing import Union +from certbot import errors +from certbot.compat import os from certbot_apache._internal import apache_util from certbot_apache._internal import assertions from certbot_apache._internal import interfaces from certbot_apache._internal import parser from certbot_apache._internal import parsernode_util as util -from certbot import errors -from certbot.compat import os - class AugeasParserNode(interfaces.ParserNode): """ Augeas implementation of ParserNode interface """ diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index 41209deb4..37d32f699 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -21,16 +21,6 @@ from typing import Tuple from typing import Type from typing import Union -from certbot_apache._internal import apache_util -from certbot_apache._internal import assertions -from certbot_apache._internal import constants -from certbot_apache._internal import display_ops -from certbot_apache._internal import dualparser -from certbot_apache._internal import http_01 -from certbot_apache._internal import obj -from certbot_apache._internal import parser -from certbot_apache._internal.apacheparser import ApacheBlockNode - from acme import challenges from certbot import achallenges from certbot import errors @@ -42,6 +32,15 @@ from certbot.interfaces import RenewableCert from certbot.plugins import common from certbot.plugins.enhancements import AutoHSTSEnhancement from certbot.plugins.util import path_surgery +from certbot_apache._internal import apache_util +from certbot_apache._internal import assertions +from certbot_apache._internal import constants +from certbot_apache._internal import display_ops +from certbot_apache._internal import dualparser +from certbot_apache._internal import http_01 +from certbot_apache._internal import obj +from certbot_apache._internal import parser +from certbot_apache._internal.apacheparser import ApacheBlockNode try: import apacheconfig diff --git a/certbot-apache/certbot_apache/_internal/display_ops.py b/certbot-apache/certbot_apache/_internal/display_ops.py index b943f988d..251be1da4 100644 --- a/certbot-apache/certbot_apache/_internal/display_ops.py +++ b/certbot-apache/certbot_apache/_internal/display_ops.py @@ -6,11 +6,10 @@ from typing import Optional from typing import Sequence from typing import Tuple -from certbot_apache._internal.obj import VirtualHost - from certbot import errors from certbot.compat import os from certbot.display import util as display_util +from certbot_apache._internal.obj import VirtualHost logger = logging.getLogger(__name__) diff --git a/certbot-apache/certbot_apache/_internal/entrypoint.py b/certbot-apache/certbot_apache/_internal/entrypoint.py index 3f8a00411..c4714b382 100644 --- a/certbot-apache/certbot_apache/_internal/entrypoint.py +++ b/certbot-apache/certbot_apache/_internal/entrypoint.py @@ -2,6 +2,7 @@ from typing import Dict from typing import Type +from certbot import util from certbot_apache._internal import configurator from certbot_apache._internal import override_arch from certbot_apache._internal import override_centos @@ -12,8 +13,6 @@ from certbot_apache._internal import override_gentoo from certbot_apache._internal import override_suse from certbot_apache._internal import override_void -from certbot import util - OVERRIDE_CLASSES: Dict[str, Type[configurator.ApacheConfigurator]] = { "arch": override_arch.ArchConfigurator, "cloudlinux": override_centos.CentOSConfigurator, diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index e7ca87608..bba7cbaef 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -5,15 +5,14 @@ from typing import List from typing import Set from typing import TYPE_CHECKING -from certbot_apache._internal.obj import VirtualHost -from certbot_apache._internal.parser import get_aug_path - from acme.challenges import KeyAuthorizationChallengeResponse from certbot import errors from certbot.achallenges import KeyAuthorizationAnnotatedChallenge from certbot.compat import filesystem from certbot.compat import os from certbot.plugins import common +from certbot_apache._internal.obj import VirtualHost +from certbot_apache._internal.parser import get_aug_path if TYPE_CHECKING: from certbot_apache._internal.configurator import ApacheConfigurator # pragma: no cover diff --git a/certbot-apache/certbot_apache/_internal/obj.py b/certbot-apache/certbot_apache/_internal/obj.py index b05608699..9a14a60a2 100644 --- a/certbot-apache/certbot_apache/_internal/obj.py +++ b/certbot-apache/certbot_apache/_internal/obj.py @@ -7,12 +7,11 @@ from typing import Pattern from typing import Set from typing import Union +from certbot.plugins import common from certbot_apache._internal.apacheparser import ApacheBlockNode from certbot_apache._internal.augeasparser import AugeasBlockNode from certbot_apache._internal.dualparser import DualBlockNode -from certbot.plugins import common - class Addr(common.Addr): """Represents an Apache address.""" diff --git a/certbot-apache/certbot_apache/_internal/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py index 9883bb1f1..59044769b 100644 --- a/certbot-apache/certbot_apache/_internal/override_centos.py +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -2,14 +2,13 @@ import logging from typing import Any +from certbot import errors +from certbot import util from certbot_apache._internal import apache_util from certbot_apache._internal import configurator from certbot_apache._internal import parser from certbot_apache._internal.configurator import OsOptions -from certbot import errors -from certbot import util - logger = logging.getLogger(__name__) diff --git a/certbot-apache/certbot_apache/_internal/override_debian.py b/certbot-apache/certbot_apache/_internal/override_debian.py index 8c0465538..0d138ee9d 100644 --- a/certbot-apache/certbot_apache/_internal/override_debian.py +++ b/certbot-apache/certbot_apache/_internal/override_debian.py @@ -1,15 +1,14 @@ """ Distribution specific override class for Debian family (Ubuntu/Debian) """ import logging -from certbot_apache._internal import apache_util -from certbot_apache._internal import configurator -from certbot_apache._internal.configurator import OsOptions -from certbot_apache._internal.obj import VirtualHost - from certbot import errors from certbot import util from certbot.compat import filesystem from certbot.compat import os +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator +from certbot_apache._internal.configurator import OsOptions +from certbot_apache._internal.obj import VirtualHost logger = logging.getLogger(__name__) diff --git a/certbot-apache/certbot_apache/_internal/override_fedora.py b/certbot-apache/certbot_apache/_internal/override_fedora.py index 2e7115df6..a93f103b1 100644 --- a/certbot-apache/certbot_apache/_internal/override_fedora.py +++ b/certbot-apache/certbot_apache/_internal/override_fedora.py @@ -1,14 +1,13 @@ """ Distribution specific override class for Fedora 29+ """ from typing import Any +from certbot import errors +from certbot import util from certbot_apache._internal import apache_util from certbot_apache._internal import configurator from certbot_apache._internal import parser from certbot_apache._internal.configurator import OsOptions -from certbot import errors -from certbot import util - class FedoraConfigurator(configurator.ApacheConfigurator): """Fedora 29+ specific ApacheConfigurator override class""" diff --git a/certbot-apache/certbot_apache/_internal/parser.py b/certbot-apache/certbot_apache/_internal/parser.py index 9805ad781..acfaad762 100644 --- a/certbot-apache/certbot_apache/_internal/parser.py +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -15,11 +15,10 @@ from typing import Tuple from typing import TYPE_CHECKING from typing import Union -from certbot_apache._internal import apache_util -from certbot_apache._internal import constants - from certbot import errors from certbot.compat import os +from certbot_apache._internal import apache_util +from certbot_apache._internal import constants if TYPE_CHECKING: from certbot_apache._internal.configurator import ApacheConfigurator # pragma: no cover diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 3eb927042..7665a0a30 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -1,7 +1,7 @@ from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ # We specify the minimum acme and certbot version as the current plugin @@ -17,6 +17,10 @@ dev_extras = [ 'apacheconfig>=0.3.2', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-apache', version=version, @@ -52,6 +56,7 @@ setup( install_requires=install_requires, extras_require={ 'dev': dev_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-apache/tests/augeasnode_test.py b/certbot-apache/tests/augeasnode_test.py index 591634d35..2e977c307 100644 --- a/certbot-apache/tests/augeasnode_test.py +++ b/certbot-apache/tests/augeasnode_test.py @@ -1,14 +1,13 @@ """Tests for AugeasParserNode classes""" -from typing import List - import os -import util +from typing import List from unittest import mock from certbot import errors - from certbot_apache._internal import assertions from certbot_apache._internal import augeasparser +import util +import pytest def _get_augeasnode_mock(filepath): @@ -43,14 +42,14 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- def test_save(self): with mock.patch('certbot_apache._internal.parser.ApacheParser.save') as mock_save: self.config.parser_root.save("A save message") - self.assertIs(mock_save.called, True) - self.assertEqual(mock_save.call_args[0][0], "A save message") + assert mock_save.called is True + assert mock_save.call_args[0][0] == "A save message" def test_unsaved_files(self): with mock.patch('certbot_apache._internal.parser.ApacheParser.unsaved_files') as mock_uf: mock_uf.return_value = ["first", "second"] files = self.config.parser_root.unsaved_files() - self.assertEqual(files, ["first", "second"]) + assert files == ["first", "second"] def test_get_block_node_name(self): from certbot_apache._internal.augeasparser import AugeasBlockNode @@ -69,26 +68,26 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- } for test in testcases: # pylint: disable=protected-access - self.assertEqual(block._aug_get_name(test), testcases[test]) + assert block._aug_get_name(test) == testcases[test] def test_find_blocks(self): blocks = self.config.parser_root.find_blocks("VirtualHost", exclude=False) - self.assertEqual(len(blocks), 12) + assert len(blocks) == 12 def test_find_blocks_case_insensitive(self): vhs = self.config.parser_root.find_blocks("VirtualHost") vhs2 = self.config.parser_root.find_blocks("viRtuAlHoST") - self.assertEqual(len(vhs), len(vhs2)) + assert len(vhs) == len(vhs2) def test_find_directive_found(self): directives = self.config.parser_root.find_directives("Listen") - self.assertEqual(len(directives), 1) - self.assertIs(directives[0].filepath.endswith("/apache2/ports.conf"), True) - self.assertEqual(directives[0].parameters, (u'80',)) + assert len(directives) == 1 + assert directives[0].filepath.endswith("/apache2/ports.conf") is True + assert directives[0].parameters == (u'80',) def test_find_directive_notfound(self): directives = self.config.parser_root.find_directives("Nonexistent") - self.assertEqual(len(directives), 0) + assert len(directives) == 0 def test_find_directive_from_block(self): blocks = self.config.parser_root.find_blocks("virtualhost") @@ -96,31 +95,31 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- for vh in blocks: if vh.filepath.endswith("sites-enabled/certbot.conf"): servername = vh.find_directives("servername") - self.assertEqual(servername[0].parameters[0], "certbot.demo") + assert servername[0].parameters[0] == "certbot.demo" found = True - self.assertIs(found, True) + assert found is True def test_find_comments(self): rootcomment = self.config.parser_root.find_comments( "This is the main Apache server configuration file. " ) - self.assertEqual(len(rootcomment), 1) - self.assertIs(rootcomment[0].filepath.endswith( + assert len(rootcomment) == 1 + assert rootcomment[0].filepath.endswith( "debian_apache_2_4/multiple_vhosts/apache2/apache2.conf" - ), True) + ) is True def test_set_parameters(self): servernames = self.config.parser_root.find_directives("servername") names: List[str] = [] for servername in servernames: names += servername.parameters - self.assertNotIn("going_to_set_this", names) + assert "going_to_set_this" not in names servernames[0].set_parameters(["something", "going_to_set_this"]) servernames = self.config.parser_root.find_directives("servername") names = [] for servername in servernames: names += servername.parameters - self.assertIn("going_to_set_this", names) + assert "going_to_set_this" in names def test_set_parameters_atinit(self): from certbot_apache._internal.augeasparser import AugeasDirectiveNode @@ -133,11 +132,9 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- ancestor=assertions.PASS, metadata=servernames[0].metadata ) - self.assertIs(mock_set.called, True) - self.assertEqual( - mock_set.call_args_list[0][0][0], + assert mock_set.called is True + assert mock_set.call_args_list[0][0][0] == \ ["test", "setting", "these"] - ) def test_set_parameters_delete(self): # Set params @@ -150,48 +147,43 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- found = False for servername in servernames: if "thisshouldnotexistpreviously" in servername.parameters: - self.assertEqual(len(servername.parameters), 3) + assert len(servername.parameters) == 3 servername.set_parameters(["thisshouldnotexistpreviously"]) found = True - self.assertIs(found, True) + assert found is True # Verify params servernames = self.config.parser_root.find_directives("servername") found = False for servername in servernames: if "thisshouldnotexistpreviously" in servername.parameters: - self.assertEqual(len(servername.parameters), 1) + assert len(servername.parameters) == 1 servername.set_parameters(["thisshouldnotexistpreviously"]) found = True - self.assertIs(found, True) + assert found is True def test_add_child_comment(self): newc = self.config.parser_root.add_child_comment("The content") comments = self.config.parser_root.find_comments("The content") - self.assertEqual(len(comments), 1) - self.assertEqual( - newc.metadata["augeaspath"], + assert len(comments) == 1 + assert newc.metadata["augeaspath"] == \ comments[0].metadata["augeaspath"] - ) - self.assertEqual(newc.comment, comments[0].comment) + assert newc.comment == comments[0].comment def test_delete_child(self): listens = self.config.parser_root.find_directives("Listen") - self.assertEqual(len(listens), 1) + assert len(listens) == 1 self.config.parser_root.delete_child(listens[0]) listens = self.config.parser_root.find_directives("Listen") - self.assertEqual(len(listens), 0) + assert len(listens) == 0 def test_delete_child_not_found(self): listen = self.config.parser_root.find_directives("Listen")[0] listen.metadata["augeaspath"] = "/files/something/nonexistent" - self.assertRaises( - errors.PluginError, - self.config.parser_root.delete_child, - listen - ) + with pytest.raises(errors.PluginError): + self.config.parser_root.delete_child(listen) def test_add_child_block(self): nb = self.config.parser_root.add_child_block( @@ -199,11 +191,9 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- ["first", "second"] ) rpath, _, directive = nb.metadata["augeaspath"].rpartition("/") - self.assertEqual( - rpath, + assert rpath == \ self.config.parser_root.metadata["augeaspath"] - ) - self.assertIs(directive.startswith("NewBlock"), True) + assert directive.startswith("NewBlock") is True def test_add_child_block_beginning(self): self.config.parser_root.add_child_block( @@ -214,7 +204,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- root_path = self.config.parser_root.metadata["augeaspath"] # Get first child first = parser.aug.match("{}/*[1]".format(root_path)) - self.assertIs(first[0].endswith("Beginning"), True) + assert first[0].endswith("Beginning") is True def test_add_child_block_append(self): self.config.parser_root.add_child_block( @@ -224,7 +214,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- root_path = self.config.parser_root.metadata["augeaspath"] # Get last child last = parser.aug.match("{}/*[last()]".format(root_path)) - self.assertIs(last[0].endswith("VeryLast"), True) + assert last[0].endswith("VeryLast") is True def test_add_child_block_append_alt(self): self.config.parser_root.add_child_block( @@ -235,7 +225,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- root_path = self.config.parser_root.metadata["augeaspath"] # Get last child last = parser.aug.match("{}/*[last()]".format(root_path)) - self.assertIs(last[0].endswith("VeryLastAlt"), True) + assert last[0].endswith("VeryLastAlt") is True def test_add_child_block_middle(self): self.config.parser_root.add_child_block( @@ -246,20 +236,20 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- root_path = self.config.parser_root.metadata["augeaspath"] # Augeas indices start at 1 :( middle = parser.aug.match("{}/*[6]".format(root_path)) - self.assertIs(middle[0].endswith("Middle"), True) + assert middle[0].endswith("Middle") is True def test_add_child_block_existing_name(self): parser = self.config.parser_root.parser root_path = self.config.parser_root.metadata["augeaspath"] # There already exists a single VirtualHost in the base config new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path)) - self.assertEqual(len(new_block), 0) + assert len(new_block) == 0 vh = self.config.parser_root.add_child_block( "VirtualHost", ) new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path)) - self.assertEqual(len(new_block), 1) - self.assertIs(vh.metadata["augeaspath"].endswith("VirtualHost[2]"), True) + assert len(new_block) == 1 + assert vh.metadata["augeaspath"].endswith("VirtualHost[2]") is True def test_node_init_error_bad_augeaspath(self): from certbot_apache._internal.augeasparser import AugeasBlockNode @@ -272,11 +262,8 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- "augeaspath": "/files/path/endswith/slash/" } } - self.assertRaises( - errors.PluginError, - AugeasBlockNode, - **parameters - ) + with pytest.raises(errors.PluginError): + AugeasBlockNode(**parameters) def test_node_init_error_missing_augeaspath(self): from certbot_apache._internal.augeasparser import AugeasBlockNode @@ -288,11 +275,8 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- "augeasparser": mock.Mock(), } } - self.assertRaises( - errors.PluginError, - AugeasBlockNode, - **parameters - ) + with pytest.raises(errors.PluginError): + AugeasBlockNode(**parameters) def test_add_child_directive(self): self.config.parser_root.add_child_directive( @@ -301,21 +285,18 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- position=0 ) dirs = self.config.parser_root.find_directives("ThisWasAdded") - self.assertEqual(len(dirs), 1) - self.assertEqual(dirs[0].parameters, ("with", "parameters")) + assert len(dirs) == 1 + assert dirs[0].parameters == ("with", "parameters") # The new directive was added to the very first line of the config - self.assertIs(dirs[0].metadata["augeaspath"].endswith("[1]"), True) + assert dirs[0].metadata["augeaspath"].endswith("[1]") is True def test_add_child_directive_exception(self): - self.assertRaises( - errors.PluginError, - self.config.parser_root.add_child_directive, - "ThisRaisesErrorBecauseMissingParameters" - ) + with pytest.raises(errors.PluginError): + self.config.parser_root.add_child_directive("ThisRaisesErrorBecauseMissingParameters") def test_parsed_paths(self): paths = self.config.parser_root.parsed_paths() - self.assertEqual(len(paths), 6) + assert len(paths) == 6 def test_find_ancestors(self): vhsblocks = self.config.parser_root.find_blocks("VirtualHost") @@ -324,16 +305,16 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- for vh in vhsblocks: if "/macro/" in vh.metadata["augeaspath"].lower(): ancs = vh.find_ancestors("Macro") - self.assertEqual(len(ancs), 1) + assert len(ancs) == 1 macro_test = True else: ancs = vh.find_ancestors("Macro") - self.assertEqual(len(ancs), 0) + assert len(ancs) == 0 nonmacro_test = True - self.assertIs(macro_test, True) - self.assertIs(nonmacro_test, True) + assert macro_test is True + assert nonmacro_test is True def test_find_ancestors_bad_path(self): self.config.parser_root.metadata["augeaspath"] = "" ancs = self.config.parser_root.find_ancestors("Anything") - self.assertEqual(len(ancs), 0) + assert len(ancs) == 0 diff --git a/certbot-apache/tests/autohsts_test.py b/certbot-apache/tests/autohsts_test.py index 70ed2ca1a..48e4f7bf9 100644 --- a/certbot-apache/tests/autohsts_test.py +++ b/certbot-apache/tests/autohsts_test.py @@ -1,9 +1,12 @@ # pylint: disable=too-many-lines """Test for certbot_apache._internal.configurator AutoHSTS functionality""" import re +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot_apache._internal import constants import util @@ -43,14 +46,13 @@ class AutoHSTSTest(util.ApacheTest): self.config.parser.modules.pop("headers_module", None) self.config.parser.modules.pop("mod_header.c", None) self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) - self.assertIs(mock_enable.called, True) + assert mock_enable.called is True @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") def test_autohsts_deploy_already_exists(self, _restart): self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) - self.assertRaises(errors.PluginEnhancementAlreadyPresent, - self.config.enable_autohsts, - mock.MagicMock(), ["ocspvhost.com"]) + with pytest.raises(errors.PluginEnhancementAlreadyPresent): + self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") @@ -63,14 +65,14 @@ class AutoHSTSTest(util.ApacheTest): self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) # Verify initial value - self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - initial_val) + assert self.get_autohsts_value(self.vh_truth[7].path) == \ + initial_val # Increase self.config.update_autohsts(mock.MagicMock()) # Verify increased value - self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - inc_val) - self.assertIs(mock_prepare.called, True) + assert self.get_autohsts_value(self.vh_truth[7].path) == \ + inc_val + assert mock_prepare.called is True @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._autohsts_increase") @@ -79,12 +81,12 @@ class AutoHSTSTest(util.ApacheTest): initial_val = maxage.format(constants.AUTOHSTS_STEPS[0]) self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) # Verify initial value - self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - initial_val) + assert self.get_autohsts_value(self.vh_truth[7].path) == \ + initial_val self.config.update_autohsts(mock.MagicMock()) # Freq not patched, so value shouldn't increase - self.assertIs(mock_increase.called, False) + assert mock_increase.called is False @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") @@ -96,9 +98,8 @@ class AutoHSTSTest(util.ApacheTest): self.vh_truth[7].path) dir_loc = "/".join(dir_locs[0].split("/")[:-1]) self.config.parser.aug.remove(dir_loc) - self.assertRaises(errors.PluginError, - self.config.update_autohsts, - mock.MagicMock()) + with pytest.raises(errors.PluginError): + self.config.update_autohsts(mock.MagicMock()) @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") @@ -111,49 +112,48 @@ class AutoHSTSTest(util.ApacheTest): for i in range(len(constants.AUTOHSTS_STEPS)-1): # Ensure that value is not made permanent prematurely self.config.deploy_autohsts(mock_lineage) - self.assertNotEqual(self.get_autohsts_value(self.vh_truth[7].path), - max_val) + assert self.get_autohsts_value(self.vh_truth[7].path) != \ + max_val self.config.update_autohsts(mock.MagicMock()) # Value should match pre-permanent increment step cur_val = maxage.format(constants.AUTOHSTS_STEPS[i+1]) - self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - cur_val) + assert self.get_autohsts_value(self.vh_truth[7].path) == \ + cur_val # Ensure that the value is raised to max - self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - maxage.format(constants.AUTOHSTS_STEPS[-1])) + assert self.get_autohsts_value(self.vh_truth[7].path) == \ + maxage.format(constants.AUTOHSTS_STEPS[-1]) # Make permanent self.config.deploy_autohsts(mock_lineage) - self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - max_val) + assert self.get_autohsts_value(self.vh_truth[7].path) == \ + max_val def test_autohsts_update_noop(self): with mock.patch("time.time") as mock_time: # Time mock is used to make sure that the execution does not # continue when no autohsts entries exist in pluginstorage self.config.update_autohsts(mock.MagicMock()) - self.assertIs(mock_time.called, False) + assert mock_time.called is False def test_autohsts_make_permanent_noop(self): self.config.storage.put = mock.MagicMock() self.config.deploy_autohsts(mock.MagicMock()) # Make sure that the execution does not continue when no entries in store - self.assertIs(self.config.storage.put.called, False) + assert self.config.storage.put.called is False @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_autohsts_no_ssl_vhost(self, mock_select): mock_select.return_value = self.vh_truth[0] with mock.patch("certbot_apache._internal.configurator.logger.error") as mock_log: - self.assertRaises(errors.PluginError, - self.config.enable_autohsts, - mock.MagicMock(), "invalid.example.com") - self.assertIn("Certbot was not able to find SSL", mock_log.call_args[0][0]) + with pytest.raises(errors.PluginError): + self.config.enable_autohsts(mock.MagicMock(), "invalid.example.com") + assert "Certbot was not able to find SSL" in mock_log.call_args[0][0] @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.add_vhost_id") def test_autohsts_dont_enhance_twice(self, mock_id, _restart): mock_id.return_value = "1234567" self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com", "ocspvhost.com"]) - self.assertEqual(mock_id.call_count, 1) + assert mock_id.call_count == 1 def test_autohsts_remove_orphaned(self): # pylint: disable=protected-access @@ -162,11 +162,11 @@ class AutoHSTSTest(util.ApacheTest): self.config._autohsts_save_state() self.config.update_autohsts(mock.MagicMock()) - self.assertNotIn("orphan_id", self.config._autohsts) + assert "orphan_id" not in self.config._autohsts # Make sure it's removed from the pluginstorage file as well self.config._autohsts = None self.config._autohsts_fetch_state() - self.assertFalse(self.config._autohsts) + assert not self.config._autohsts def test_autohsts_make_permanent_vhost_not_found(self): # pylint: disable=protected-access @@ -175,9 +175,9 @@ class AutoHSTSTest(util.ApacheTest): self.config._autohsts_save_state() with mock.patch("certbot_apache._internal.configurator.logger.error") as mock_log: self.config.deploy_autohsts(mock.MagicMock()) - self.assertIs(mock_log.called, True) - self.assertIn("VirtualHost with id orphan_id was not", mock_log.call_args[0][0]) + assert mock_log.called is True + assert "VirtualHost with id orphan_id was not" in mock_log.call_args[0][0] if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/centos_test.py b/certbot-apache/tests/centos_test.py index 3f8e88467..6b0b33377 100644 --- a/certbot-apache/tests/centos_test.py +++ b/certbot-apache/tests/centos_test.py @@ -1,12 +1,15 @@ """Test for certbot_apache._internal.configurator for Centos overrides""" +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import filesystem from certbot.compat import os -from certbot_apache._internal import override_centos from certbot_apache._internal import obj +from certbot_apache._internal import override_centos import util @@ -48,7 +51,7 @@ class FedoraRestartTest(util.ApacheTest): self.temp_dir, "centos7_apache/apache") def _run_fedora_test(self): - self.assertIsInstance(self.config, override_centos.CentOSConfigurator) + assert isinstance(self.config, override_centos.CentOSConfigurator) with mock.patch("certbot.util.get_os_info") as mock_info: mock_info.return_value = ["fedora", "28"] self.config.config_test() @@ -59,8 +62,8 @@ class FedoraRestartTest(util.ApacheTest): mock_test.side_effect = errors.MisconfigurationError with mock.patch("certbot.util.get_os_info") as mock_info: mock_info.return_value = ["not_fedora"] - self.assertRaises(errors.MisconfigurationError, - self.config.config_test) + with pytest.raises(errors.MisconfigurationError): + self.config.config_test() def test_fedora_restart_error(self): c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" @@ -69,8 +72,8 @@ class FedoraRestartTest(util.ApacheTest): mock_test.side_effect = [errors.MisconfigurationError, ''] with mock.patch("certbot.util.run_script") as mock_run: mock_run.side_effect = errors.SubprocessError - self.assertRaises(errors.MisconfigurationError, - self._run_fedora_test) + with pytest.raises(errors.MisconfigurationError): + self._run_fedora_test() def test_fedora_restart(self): c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" @@ -79,9 +82,9 @@ class FedoraRestartTest(util.ApacheTest): # First call raises error, second doesn't mock_test.side_effect = [errors.MisconfigurationError, ''] self._run_fedora_test() - self.assertEqual(mock_test.call_count, 2) - self.assertEqual(mock_run.call_args[0][0], - ['systemctl', 'restart', 'httpd']) + assert mock_test.call_count == 2 + assert mock_run.call_args[0][0] == \ + ['systemctl', 'restart', 'httpd'] class UseCorrectApacheExecutableTest(util.ApacheTest): @@ -102,15 +105,15 @@ class UseCorrectApacheExecutableTest(util.ApacheTest): config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, os_info="centos") - self.assertEqual(config.options.ctl, "apachectl") - self.assertEqual(config.options.bin, "httpd") - self.assertEqual(config.options.version_cmd, ["apachectl", "-v"]) - self.assertEqual(config.options.restart_cmd, ["apachectl", "graceful"]) - self.assertEqual(config.options.restart_cmd_alt, ["apachectl", "restart"]) - self.assertEqual(config.options.conftest_cmd, ["apachectl", "configtest"]) - self.assertEqual(config.options.get_defines_cmd, ["apachectl", "-t", "-D", "DUMP_RUN_CFG"]) - self.assertEqual(config.options.get_includes_cmd, ["apachectl", "-t", "-D", "DUMP_INCLUDES"]) - self.assertEqual(config.options.get_modules_cmd, ["apachectl", "-t", "-D", "DUMP_MODULES"]) + assert config.options.ctl == "apachectl" + assert config.options.bin == "httpd" + assert config.options.version_cmd == ["apachectl", "-v"] + assert config.options.restart_cmd == ["apachectl", "graceful"] + assert config.options.restart_cmd_alt == ["apachectl", "restart"] + assert config.options.conftest_cmd == ["apachectl", "configtest"] + assert config.options.get_defines_cmd == ["apachectl", "-t", "-D", "DUMP_RUN_CFG"] + assert config.options.get_includes_cmd == ["apachectl", "-t", "-D", "DUMP_INCLUDES"] + assert config.options.get_modules_cmd == ["apachectl", "-t", "-D", "DUMP_MODULES"] @mock.patch("certbot.util.get_os_info") def test_new_rhel_derived(self, mock_get_os_info): @@ -119,22 +122,20 @@ class UseCorrectApacheExecutableTest(util.ApacheTest): config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, os_info=os_info[0]) - self.assertEqual(config.options.ctl, "apachectl") - self.assertEqual(config.options.bin, "httpd") - self.assertEqual(config.options.version_cmd, ["httpd", "-v"]) - self.assertEqual(config.options.restart_cmd, ["apachectl", "graceful"]) - self.assertEqual(config.options.restart_cmd_alt, ["apachectl", "restart"]) - self.assertEqual(config.options.conftest_cmd, ["apachectl", "configtest"]) - self.assertEqual(config.options.get_defines_cmd, ["httpd", "-t", "-D", "DUMP_RUN_CFG"]) - self.assertEqual(config.options.get_includes_cmd, ["httpd", "-t", "-D", "DUMP_INCLUDES"]) - self.assertEqual(config.options.get_modules_cmd, ["httpd", "-t", "-D", "DUMP_MODULES"]) + assert config.options.ctl == "apachectl" + assert config.options.bin == "httpd" + assert config.options.version_cmd == ["httpd", "-v"] + assert config.options.restart_cmd == ["apachectl", "graceful"] + assert config.options.restart_cmd_alt == ["apachectl", "restart"] + assert config.options.conftest_cmd == ["apachectl", "configtest"] + assert config.options.get_defines_cmd == ["httpd", "-t", "-D", "DUMP_RUN_CFG"] + assert config.options.get_includes_cmd == ["httpd", "-t", "-D", "DUMP_INCLUDES"] + assert config.options.get_modules_cmd == ["httpd", "-t", "-D", "DUMP_MODULES"] class MultipleVhostsTestCentOS(util.ApacheTest): """Multiple vhost tests for CentOS / RHEL family of distros""" - _multiprocess_can_split_ = True - @mock.patch("certbot.util.get_os_info") def setUp(self, mock_get_os_info): # pylint: disable=arguments-differ test_dir = "centos7_apache/apache" @@ -151,7 +152,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): self.temp_dir, "centos7_apache/apache") def test_get_parser(self): - self.assertIsInstance(self.config.parser, override_centos.CentOSParser) + assert isinstance(self.config.parser, override_centos.CentOSParser) @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): @@ -181,16 +182,16 @@ class MultipleVhostsTestCentOS(util.ApacheTest): mock_osi.return_value = ("centos", "9") self.config.parser.update_runtime_variables() - self.assertEqual(mock_get.call_count, 3) - self.assertEqual(len(self.config.parser.modules), 4) - self.assertEqual(len(self.config.parser.variables), 2) - self.assertIn("TEST2", self.config.parser.variables) - self.assertIn("mod_another.c", self.config.parser.modules) + assert mock_get.call_count == 3 + assert len(self.config.parser.modules) == 4 + assert len(self.config.parser.variables) == 2 + assert "TEST2" in self.config.parser.variables + assert "mod_another.c" in self.config.parser.modules def test_get_virtual_hosts(self): """Make sure all vhosts are being properly found.""" vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 2) + assert len(vhs) == 2 found = 0 for vhost in vhs: @@ -200,7 +201,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): break else: raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 2) + assert found == 2 @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): @@ -216,26 +217,27 @@ class MultipleVhostsTestCentOS(util.ApacheTest): mock_osi.return_value = ("centos", "9") self.config.parser.update_runtime_variables() - self.assertIn("mock_define", self.config.parser.variables) - self.assertIn("mock_define_too", self.config.parser.variables) - self.assertIn("mock_value", self.config.parser.variables) - self.assertEqual("TRUE", self.config.parser.variables["mock_value"]) - self.assertIn("MOCK_NOSEP", self.config.parser.variables) - self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) + assert "mock_define" in self.config.parser.variables + assert "mock_define_too" in self.config.parser.variables + assert "mock_value" in self.config.parser.variables + assert "TRUE" == self.config.parser.variables["mock_value"] + assert "MOCK_NOSEP" in self.config.parser.variables + assert "NOSEP_VAL" == self.config.parser.variables["NOSEP_TWO"] @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_works(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, None] self.config.restart() - self.assertEqual(mock_run_script.call_count, 3) + assert mock_run_script.call_count == 3 @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_errors(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, errors.SubprocessError] - self.assertRaises(errors.MisconfigurationError, self.config.restart) + with pytest.raises(errors.MisconfigurationError): + self.config.restart() if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/complex_parsing_test.py b/certbot-apache/tests/complex_parsing_test.py index 973af302a..ef06d94d1 100644 --- a/certbot-apache/tests/complex_parsing_test.py +++ b/certbot-apache/tests/complex_parsing_test.py @@ -1,7 +1,10 @@ """Tests for certbot_apache._internal.parser.""" import shutil +import sys import unittest +import pytest + from certbot import errors from certbot.compat import os import util @@ -38,51 +41,49 @@ class ComplexParserTest(util.ParserTest): """Note: This may also fail do to Include conf-enabled/ syntax.""" matches = self.parser.find_dir("TestArgsDirective") - self.assertEqual(len(self.parser.filter_args_num(matches, 1)), 3) - self.assertEqual(len(self.parser.filter_args_num(matches, 2)), 2) - self.assertEqual(len(self.parser.filter_args_num(matches, 3)), 1) + assert len(self.parser.filter_args_num(matches, 1)) == 3 + assert len(self.parser.filter_args_num(matches, 2)) == 2 + assert len(self.parser.filter_args_num(matches, 3)) == 1 def test_basic_variable_parsing(self): matches = self.parser.find_dir("TestVariablePort") - self.assertEqual(len(matches), 1) - self.assertEqual(self.parser.get_arg(matches[0]), "1234") + assert len(matches) == 1 + assert self.parser.get_arg(matches[0]) == "1234" def test_basic_variable_parsing_quotes(self): matches = self.parser.find_dir("TestVariablePortStr") - self.assertEqual(len(matches), 1) - self.assertEqual(self.parser.get_arg(matches[0]), "1234") + assert len(matches) == 1 + assert self.parser.get_arg(matches[0]) == "1234" def test_invalid_variable_parsing(self): del self.parser.variables["tls_port"] matches = self.parser.find_dir("TestVariablePort") - self.assertRaises( - errors.PluginError, self.parser.get_arg, matches[0]) + with pytest.raises(errors.PluginError): + self.parser.get_arg(matches[0]) def test_basic_ifdefine(self): - self.assertEqual(len(self.parser.find_dir("VAR_DIRECTIVE")), 2) - self.assertEqual(len(self.parser.find_dir("INVALID_VAR_DIRECTIVE")), 0) + assert len(self.parser.find_dir("VAR_DIRECTIVE")) == 2 + assert len(self.parser.find_dir("INVALID_VAR_DIRECTIVE")) == 0 def test_basic_ifmodule(self): - self.assertEqual(len(self.parser.find_dir("MOD_DIRECTIVE")), 2) - self.assertEqual( - len(self.parser.find_dir("INVALID_MOD_DIRECTIVE")), 0) + assert len(self.parser.find_dir("MOD_DIRECTIVE")) == 2 + assert len(self.parser.find_dir("INVALID_MOD_DIRECTIVE")) == 0 def test_nested(self): - self.assertEqual(len(self.parser.find_dir("NESTED_DIRECTIVE")), 3) - self.assertEqual( - len(self.parser.find_dir("INVALID_NESTED_DIRECTIVE")), 0) + assert len(self.parser.find_dir("NESTED_DIRECTIVE")) == 3 + assert len(self.parser.find_dir("INVALID_NESTED_DIRECTIVE")) == 0 def test_load_modules(self): """If only first is found, there is bad variable parsing.""" - self.assertIn("status_module", self.parser.modules) - self.assertIn("mod_status.c", self.parser.modules) + assert "status_module" in self.parser.modules + assert "mod_status.c" in self.parser.modules # This is in an IfDefine - self.assertIn("ssl_module", self.parser.modules) - self.assertIn("mod_ssl.c", self.parser.modules) + assert "ssl_module" in self.parser.modules + assert "mod_ssl.c" in self.parser.modules def verify_fnmatch(self, arg, hit=True): """Test if Include was correctly parsed.""" @@ -90,9 +91,9 @@ class ComplexParserTest(util.ParserTest): self.parser.add_dir(parser.get_aug_path(self.parser.loc["default"]), "Include", [arg]) if hit: - self.assertTrue(self.parser.find_dir("FNMATCH_DIRECTIVE")) + assert self.parser.find_dir("FNMATCH_DIRECTIVE") else: - self.assertFalse(self.parser.find_dir("FNMATCH_DIRECTIVE")) + assert not self.parser.find_dir("FNMATCH_DIRECTIVE") # NOTE: Only run one test per function otherwise you will have # inf recursion @@ -124,4 +125,4 @@ class ComplexParserTest(util.ParserTest): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/configurator_reverter_test.py b/certbot-apache/tests/configurator_reverter_test.py index fe0dfb39d..21b1188e8 100644 --- a/certbot-apache/tests/configurator_reverter_test.py +++ b/certbot-apache/tests/configurator_reverter_test.py @@ -1,8 +1,11 @@ """Test for certbot_apache._internal.configurator implementations of reverter""" import shutil +import sys import unittest from unittest import mock +import pytest + from certbot import errors import util @@ -26,51 +29,54 @@ class ConfiguratorReverterTest(util.ApacheTest): def test_bad_save_checkpoint(self): self.config.reverter.add_to_checkpoint = mock.Mock(side_effect=errors.ReverterError) self.config.parser.add_dir(self.vh_truth[0].path, "Test", "bad_save_ckpt") - self.assertRaises(errors.PluginError, self.config.save) + with pytest.raises(errors.PluginError): + self.config.save() def test_bad_save_finalize_checkpoint(self): self.config.reverter.finalize_checkpoint = mock.Mock(side_effect=errors.ReverterError) self.config.parser.add_dir(self.vh_truth[0].path, "Test", "bad_save_ckpt") - self.assertRaises(errors.PluginError, self.config.save, "Title") + with pytest.raises(errors.PluginError): + self.config.save("Title") def test_finalize_save(self): mock_finalize = mock.Mock() self.config.reverter = mock_finalize self.config.save("Example Title") - self.assertTrue(mock_finalize.is_called) + assert mock_finalize.is_called def test_revert_challenge_config(self): mock_load = mock.Mock() self.config.parser.aug.load = mock_load self.config.revert_challenge_config() - self.assertEqual(mock_load.call_count, 1) + assert mock_load.call_count == 1 def test_revert_challenge_config_error(self): self.config.reverter.revert_temporary_config = mock.Mock( side_effect=errors.ReverterError) - self.assertRaises( - errors.PluginError, self.config.revert_challenge_config) + with pytest.raises(errors.PluginError): + self.config.revert_challenge_config() def test_rollback_checkpoints(self): mock_load = mock.Mock() self.config.parser.aug.load = mock_load self.config.rollback_checkpoints() - self.assertEqual(mock_load.call_count, 1) + assert mock_load.call_count == 1 def test_rollback_error(self): self.config.reverter.rollback_checkpoints = mock.Mock(side_effect=errors.ReverterError) - self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) + with pytest.raises(errors.PluginError): + self.config.rollback_checkpoints() def test_recovery_routine_reload(self): mock_load = mock.Mock() self.config.parser.aug.load = mock_load self.config.recovery_routine() - self.assertEqual(mock_load.call_count, 1) + assert mock_load.call_count == 1 if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/configurator_test.py b/certbot-apache/tests/configurator_test.py index 0978b302e..6b22ce8c7 100644 --- a/certbot-apache/tests/configurator_test.py +++ b/certbot-apache/tests/configurator_test.py @@ -3,10 +3,13 @@ import copy import shutil import socket +import sys import tempfile import unittest from unittest import mock +import pytest + from acme import challenges from certbot import achallenges from certbot import crypto_util @@ -51,8 +54,9 @@ class MultipleVhostsTest(util.ApacheTest): silly_path = {"PATH": "/tmp/nothingness2342"} mock_surgery.return_value = False with mock.patch.dict('os.environ', silly_path): - self.assertRaises(errors.NoInstallationError, self.config.prepare) - self.assertEqual(mock_surgery.call_count, 1) + with pytest.raises(errors.NoInstallationError): + self.config.prepare() + assert mock_surgery.call_count == 1 @mock.patch("certbot_apache._internal.parser.ApacheParser") @mock.patch("certbot_apache._internal.configurator.util.exe_exists") @@ -62,8 +66,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config.config_test = mock.Mock() self.config.get_version = mock.Mock(return_value=(1, 1)) - self.assertRaises( - errors.NotSupportedError, self.config.prepare) + with pytest.raises(errors.NotSupportedError): + self.config.prepare() def test_prepare_locked(self): server_root = self.config.conf("server-root") @@ -79,13 +83,14 @@ class MultipleVhostsTest(util.ApacheTest): self.config.prepare() except errors.PluginError as err: err_msg = str(err) - self.assertIn("lock", err_msg) - self.assertIn(self.config.conf("server-root"), err_msg) + assert "lock" in err_msg + assert self.config.conf("server-root") in err_msg else: # pragma: no cover self.fail("Exception wasn't raised!") def test_add_parser_arguments(self): # pylint: disable=no-self-use from certbot_apache._internal.configurator import ApacheConfigurator + # Weak test.. ApacheConfigurator.add_parser_arguments(mock.MagicMock()) @@ -110,10 +115,10 @@ class MultipleVhostsTest(util.ApacheTest): found.add(call[0][0]) # Make sure that all (and only) the expected values exist - self.assertEqual(len(mock_add.call_args_list), len(found)) + assert len(mock_add.call_args_list) == len(found) for e in exp: with self.subTest(e=e): - self.assertIn(e, found) + assert e in found del os.environ["CERTBOT_DOCS"] @@ -123,23 +128,23 @@ class MultipleVhostsTest(util.ApacheTest): cls.add_parser_arguments(mock.MagicMock()) def test_all_configurators_defaults_defined(self): - from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES from certbot_apache._internal.configurator import ApacheConfigurator + from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES parameters = set(ApacheConfigurator.OS_DEFAULTS.__dict__.keys()) for cls in OVERRIDE_CLASSES.values(): - self.assertIs(parameters.issubset(set(cls.OS_DEFAULTS.__dict__.keys())), True) + assert parameters.issubset(set(cls.OS_DEFAULTS.__dict__.keys())) is True def test_constant(self): - self.assertIn("debian_apache_2_4/multiple_vhosts/apache", self.config.options.server_root) + assert "debian_apache_2_4/multiple_vhosts/apache" in self.config.options.server_root @certbot_util.patch_display_util() def test_get_all_names(self, mock_getutility): mock_utility = mock_getutility() mock_utility.notification = mock.MagicMock(return_value=True) names = self.config.get_all_names() - self.assertEqual(names, {"certbot.demo", "ocspvhost.com", "encryption-example.demo", + assert names == {"certbot.demo", "ocspvhost.com", "encryption-example.demo", "nonsym.link", "vhost.in.rootconf", "www.certbot.demo", - "duplicate.example.com"}) + "duplicate.example.com"} @certbot_util.patch_display_util() @mock.patch("certbot_apache._internal.configurator.socket.gethostbyaddr") @@ -157,15 +162,15 @@ class MultipleVhostsTest(util.ApacheTest): self.config.vhosts.append(vhost) names = self.config.get_all_names() - self.assertEqual(len(names), 9) - self.assertIn("zombo.com", names) - self.assertIn("google.com", names) - self.assertIn("certbot.demo", names) + assert len(names) == 9 + assert "zombo.com" in names + assert "google.com" in names + assert "certbot.demo" in names def test_get_bad_path(self): - self.assertEqual(apache_util.get_file_path(None), None) - self.assertEqual(apache_util.get_file_path("nonexistent"), None) - self.assertEqual(self.config._create_vhost("nonexistent"), None) # pylint: disable=protected-access + assert apache_util.get_file_path(None) == None + assert apache_util.get_file_path("nonexistent") == None + assert self.config._create_vhost("nonexistent") == None # pylint: disable=protected-access def test_get_aug_internal_path(self): from certbot_apache._internal.apache_util import get_internal_aug_path @@ -175,8 +180,7 @@ class MultipleVhostsTest(util.ApacheTest): "IfModule/VirtualHost"] for i, internal_path in enumerate(internal_paths): - self.assertEqual( - get_internal_aug_path(self.vh_truth[i].path), internal_path) + assert get_internal_aug_path(self.vh_truth[i].path) == internal_path def test_bad_servername_alias(self): ssl_vh1 = obj.VirtualHost( @@ -184,19 +188,19 @@ class MultipleVhostsTest(util.ApacheTest): True, False) # pylint: disable=protected-access self.config._add_servernames(ssl_vh1) - self.assertIsNone(self.config._add_servername_alias("oy_vey", ssl_vh1)) + assert self.config._add_servername_alias("oy_vey", ssl_vh1) is None def test_add_servernames_alias(self): self.config.parser.add_dir( self.vh_truth[2].path, "ServerAlias", ["*.le.co"]) # pylint: disable=protected-access self.config._add_servernames(self.vh_truth[2]) - self.assertEqual(self.vh_truth[2].get_names(), {"*.le.co", "ip-172-30-0-17"}) + assert self.vh_truth[2].get_names() == {"*.le.co", "ip-172-30-0-17"} def test_get_virtual_hosts(self): """Make sure all vhosts are being properly found.""" vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 12) + assert len(vhs) == 12 found = 0 for vhost in vhs: @@ -207,7 +211,7 @@ class MultipleVhostsTest(util.ApacheTest): else: raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 12) + assert found == 12 # Handle case of non-debian layout get_virtual_hosts with mock.patch( @@ -215,19 +219,18 @@ class MultipleVhostsTest(util.ApacheTest): ) as mock_conf: mock_conf.return_value = False vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 12) + assert len(vhs) == 12 @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_none_avail(self, mock_select): mock_select.return_value = None - self.assertRaises( - errors.PluginError, self.config.choose_vhost, "none.com") + with pytest.raises(errors.PluginError): + self.config.choose_vhost("none.com") @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_select_vhost_ssl(self, mock_select): mock_select.return_value = self.vh_truth[1] - self.assertEqual( - self.vh_truth[1], self.config.choose_vhost("none.com")) + assert self.vh_truth[1] == self.config.choose_vhost("none.com") @mock.patch("certbot_apache._internal.display_ops.select_vhost") @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") @@ -236,12 +239,11 @@ class MultipleVhostsTest(util.ApacheTest): mock_conf.return_value = False chosen_vhost = self.config.choose_vhost("none.com") self.vh_truth[0].aliases.add("none.com") - self.assertEqual( - self.vh_truth[0].get_names(), chosen_vhost.get_names()) + assert self.vh_truth[0].get_names() == chosen_vhost.get_names() # Make sure we go from HTTP -> HTTPS - self.assertIs(self.vh_truth[0].ssl, False) - self.assertIs(chosen_vhost.ssl, True) + assert self.vh_truth[0].ssl is False + assert chosen_vhost.ssl is True @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._find_best_vhost") @mock.patch("certbot_apache._internal.parser.ApacheParser.add_dir") @@ -250,13 +252,13 @@ class MultipleVhostsTest(util.ApacheTest): ret_vh.enabled = False mock_find.return_value = self.vh_truth[8] self.config.choose_vhost("whatever.com") - self.assertIs(mock_add.called, True) + assert mock_add.called is True @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_select_vhost_with_temp(self, mock_select): mock_select.return_value = self.vh_truth[0] chosen_vhost = self.config.choose_vhost("none.com", create_if_no_ssl=False) - self.assertEqual(self.vh_truth[0], chosen_vhost) + assert self.vh_truth[0] == chosen_vhost @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select): @@ -266,14 +268,14 @@ class MultipleVhostsTest(util.ApacheTest): True, True) self.config.vhosts.append(conflicting_vhost) - self.assertRaises( - errors.PluginError, self.config.choose_vhost, "none.com") + with pytest.raises(errors.PluginError): + self.config.choose_vhost("none.com") def test_find_best_http_vhost_default(self): vh = obj.VirtualHost( "fp", "ap", {obj.Addr.fromstring("_default_:80")}, False, True) self.config.vhosts = [vh] - self.assertEqual(self.config.find_best_http_vhost("foo.bar", False), vh) + assert self.config.find_best_http_vhost("foo.bar", False) == vh def test_find_best_http_vhost_port(self): port = "8080" @@ -281,21 +283,21 @@ class MultipleVhostsTest(util.ApacheTest): "fp", "ap", {obj.Addr.fromstring("*:" + port)}, False, True, "encryption-example.demo") self.config.vhosts.append(vh) - self.assertEqual(self.config.find_best_http_vhost("foo.bar", False, port), vh) + assert self.config.find_best_http_vhost("foo.bar", False, port) == vh def test_findbest_continues_on_short_domain(self): # pylint: disable=protected-access - self.assertIsNone(self.config._find_best_vhost("purple.com")) + assert self.config._find_best_vhost("purple.com") is None def test_findbest_continues_on_long_domain(self): # pylint: disable=protected-access - self.assertIsNone(self.config._find_best_vhost("green.red.purple.com")) + assert self.config._find_best_vhost("green.red.purple.com") is None def test_find_best_vhost(self): # pylint: disable=protected-access - self.assertEqual(self.vh_truth[3], self.config._find_best_vhost("certbot.demo")) - self.assertEqual(self.vh_truth[0], self.config._find_best_vhost("encryption-example.demo")) - self.assertEqual(self.config._find_best_vhost("does-not-exist.com"), None) + assert self.vh_truth[3] == self.config._find_best_vhost("certbot.demo") + assert self.vh_truth[0] == self.config._find_best_vhost("encryption-example.demo") + assert self.config._find_best_vhost("does-not-exist.com") == None def test_find_best_vhost_variety(self): # pylint: disable=protected-access @@ -304,7 +306,7 @@ class MultipleVhostsTest(util.ApacheTest): obj.Addr(("zombo.com",))}, True, False) self.config.vhosts.append(ssl_vh) - self.assertEqual(self.config._find_best_vhost("zombo.com"), ssl_vh) + assert self.config._find_best_vhost("zombo.com") == ssl_vh def test_find_best_vhost_default(self): # pylint: disable=protected-access @@ -316,14 +318,13 @@ class MultipleVhostsTest(util.ApacheTest): "ocspvhost.com", "vhost.in.rootconf"] and "*.blue.purple.com" not in vh.aliases ] - self.assertEqual( - self.config._find_best_vhost("encryption-example.demo"), - self.vh_truth[2]) + assert self.config._find_best_vhost("encryption-example.demo") == \ + self.vh_truth[2] def test_non_default_vhosts(self): # pylint: disable=protected-access vhosts = self.config._non_default_vhosts(self.config.vhosts) - self.assertEqual(len(vhosts), 10) + assert len(vhosts) == 10 @mock.patch('certbot_apache._internal.configurator.display_util.notify') def test_deploy_cert_enable_new_vhost(self, unused_mock_notify): @@ -333,11 +334,11 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.modules["mod_ssl.c"] = None self.config.parser.modules["socache_shmcb_module"] = None - self.assertIs(ssl_vhost.enabled, False) + assert ssl_vhost.enabled is False self.config.deploy_cert( "encryption-example.demo", "example/cert.pem", "example/key.pem", "example/cert_chain.pem", "example/fullchain.pem") - self.assertIs(ssl_vhost.enabled, True) + assert ssl_vhost.enabled is True def test_no_duplicate_include(self): def mock_find_dir(directive, argument, _): @@ -354,7 +355,7 @@ class MultipleVhostsTest(util.ApacheTest): if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf: tried_to_add = True # Include should be added, find_dir is not patched, and returns falsy - self.assertIs(tried_to_add, True) + assert tried_to_add is True self.config.parser.find_dir = mock_find_dir mock_add.reset_mock() @@ -383,16 +384,12 @@ class MultipleVhostsTest(util.ApacheTest): f_args.append(self.config.parser.get_arg(d)) return f_args # Verify that the dummy directives do not exist - self.assertNotIn( - "insert_cert_file_path", find_args(vhostpath, "SSLCertificateFile")) - self.assertNotIn( - "insert_key_file_path", find_args(vhostpath, "SSLCertificateKeyFile")) + assert "insert_cert_file_path" not in find_args(vhostpath, "SSLCertificateFile") + assert "insert_key_file_path" not in find_args(vhostpath, "SSLCertificateKeyFile") orig_add_dummy(vhostpath) # Verify that the dummy directives exist - self.assertIn( - "insert_cert_file_path", find_args(vhostpath, "SSLCertificateFile")) - self.assertIn( - "insert_key_file_path", find_args(vhostpath, "SSLCertificateKeyFile")) + assert "insert_cert_file_path" in find_args(vhostpath, "SSLCertificateFile") + assert "insert_key_file_path" in find_args(vhostpath, "SSLCertificateKeyFile") # pylint: disable=protected-access self.config._add_dummy_ssl_directives = mock_add_dummy_ssl @@ -404,8 +401,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config.save() # Verify ssl_module was enabled. - self.assertIs(self.vh_truth[1].enabled, True) - self.assertIn("ssl_module", self.config.parser.modules) + assert self.vh_truth[1].enabled is True + assert "ssl_module" in self.config.parser.modules loc_cert = self.config.parser.find_dir( "sslcertificatefile", "example/cert.pem", self.vh_truth[1].path) @@ -416,28 +413,25 @@ class MultipleVhostsTest(util.ApacheTest): self.vh_truth[1].path) # Verify one directive was found in the correct file - self.assertEqual(len(loc_cert), 1) - self.assertEqual( - apache_util.get_file_path(loc_cert[0]), - self.vh_truth[1].filep) + assert len(loc_cert) == 1 + assert apache_util.get_file_path(loc_cert[0]) == \ + self.vh_truth[1].filep - self.assertEqual(len(loc_key), 1) - self.assertEqual( - apache_util.get_file_path(loc_key[0]), - self.vh_truth[1].filep) + assert len(loc_key) == 1 + assert apache_util.get_file_path(loc_key[0]) == \ + self.vh_truth[1].filep - self.assertEqual(len(loc_chain), 1) - self.assertEqual( - apache_util.get_file_path(loc_chain[0]), - self.vh_truth[1].filep) + assert len(loc_chain) == 1 + assert apache_util.get_file_path(loc_chain[0]) == \ + self.vh_truth[1].filep # One more time for chain directive setting self.config.deploy_cert( "random.demo", "two/cert.pem", "two/key.pem", "two/cert_chain.pem") - self.assertTrue(self.config.parser.find_dir( + assert self.config.parser.find_dir( "SSLCertificateChainFile", "two/cert_chain.pem", - self.vh_truth[1].path)) + self.vh_truth[1].path) def test_add_listen_80(self): mock_find = mock.Mock() @@ -446,10 +440,10 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.find_dir = mock_find self.config.parser.add_dir = mock_add_dir self.config.ensure_listen("80") - self.assertIs(mock_add_dir.called, True) - self.assertIs(mock_find.called, True) - self.assertEqual(mock_add_dir.call_args[0][1], "Listen") - self.assertEqual(mock_add_dir.call_args[0][2], "80") + assert mock_add_dir.called is True + assert mock_find.called is True + assert mock_add_dir.call_args[0][1] == "Listen" + assert mock_add_dir.call_args[0][2] == "80" def test_add_listen_80_named(self): mock_find = mock.Mock() @@ -463,7 +457,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.add_dir = mock_add_dir self.config.ensure_listen("80") - self.assertEqual(mock_add_dir.call_count, 0) + assert mock_add_dir.call_count == 0 # Reset return lists and inputs mock_add_dir.reset_mock() @@ -471,14 +465,14 @@ class MultipleVhostsTest(util.ApacheTest): # Test self.config.ensure_listen("8080") - self.assertEqual(mock_add_dir.call_count, 3) - self.assertIs(mock_add_dir.called, True) - self.assertEqual(mock_add_dir.call_args[0][1], "Listen") + assert mock_add_dir.call_count == 3 + assert mock_add_dir.called is True + assert mock_add_dir.call_args[0][1] == "Listen" call_found = False for mock_call in mock_add_dir.mock_calls: if mock_call[1][2] == ['1.2.3.4:8080']: call_found = True - self.assertIs(call_found, True) + assert call_found is True @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules") def test_prepare_server_https(self, mock_reset): @@ -494,18 +488,18 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.add_dir_to_ifmodssl = mock_add_dir self.config.prepare_server_https("443") # Changing the order these modules are enabled breaks the reverter - self.assertEqual(mock_enable.call_args_list[0][0][0], "socache_shmcb") - self.assertEqual(mock_enable.call_args[0][0], "ssl") - self.assertEqual(mock_enable.call_args[1], {"temp": False}) + assert mock_enable.call_args_list[0][0][0] == "socache_shmcb" + assert mock_enable.call_args[0][0] == "ssl" + assert mock_enable.call_args[1] == {"temp": False} self.config.prepare_server_https("8080", temp=True) # Changing the order these modules are enabled breaks the reverter - self.assertEqual(mock_enable.call_args_list[2][0][0], "socache_shmcb") - self.assertEqual(mock_enable.call_args[0][0], "ssl") + assert mock_enable.call_args_list[2][0][0] == "socache_shmcb" + assert mock_enable.call_args[0][0] == "ssl" # Enable mod is temporary - self.assertEqual(mock_enable.call_args[1], {"temp": True}) + assert mock_enable.call_args[1] == {"temp": True} - self.assertEqual(mock_add_dir.call_count, 2) + assert mock_add_dir.call_count == 2 @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules") def test_prepare_server_https_named_listen(self, mock_reset): @@ -524,7 +518,7 @@ class MultipleVhostsTest(util.ApacheTest): # Test Listen statements with specific ip listeed self.config.prepare_server_https("443") # Should be 0 as one interface already listens to 443 - self.assertEqual(mock_add_dir.call_count, 0) + assert mock_add_dir.call_count == 0 # Reset return lists and inputs mock_add_dir.reset_mock() @@ -532,13 +526,12 @@ class MultipleVhostsTest(util.ApacheTest): # Test self.config.prepare_server_https("8080", temp=True) - self.assertEqual(mock_add_dir.call_count, 3) + assert mock_add_dir.call_count == 3 call_args_list = [mock_add_dir.call_args_list[i][0][2] for i in range(3)] - self.assertEqual( - sorted(call_args_list), + assert sorted(call_args_list) == \ sorted([["1.2.3.4:8080", "https"], ["[::1]:8080", "https"], - ["1.1.1.1:8080", "https"]])) + ["1.1.1.1:8080", "https"]]) # mock_get.side_effect = ["1.2.3.4:80", "[::1]:80"] # mock_find.return_value = ["test1", "test2", "test3"] @@ -561,7 +554,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.enable_mod = mock_enable self.config.prepare_server_https("443") - self.assertEqual(mock_add_dir.call_count, 1) + assert mock_add_dir.call_count == 1 @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules") def test_prepare_server_https_mixed_listen(self, mock_reset): @@ -581,7 +574,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.prepare_server_https("443") # Should only be 2 here, as the third interface # already listens to the correct port - self.assertEqual(mock_add_dir.call_count, 0) + assert mock_add_dir.call_count == 0 def test_make_vhost_ssl_with_mock_span(self): # span excludes the closing tag in older versions @@ -601,32 +594,31 @@ class MultipleVhostsTest(util.ApacheTest): def test_make_vhost_ssl_nonsymlink(self): ssl_vhost_slink = self.config.make_vhost_ssl(self.vh_truth[8]) - self.assertIs(ssl_vhost_slink.ssl, True) - self.assertIs(ssl_vhost_slink.enabled, True) - self.assertEqual(ssl_vhost_slink.name, "nonsym.link") + assert ssl_vhost_slink.ssl is True + assert ssl_vhost_slink.enabled is True + assert ssl_vhost_slink.name == "nonsym.link" def test_make_vhost_ssl_nonexistent_vhost_path(self): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) - self.assertEqual(os.path.dirname(ssl_vhost.filep), - os.path.dirname(filesystem.realpath(self.vh_truth[1].filep))) + assert os.path.dirname(ssl_vhost.filep) == \ + os.path.dirname(filesystem.realpath(self.vh_truth[1].filep)) def test_make_vhost_ssl(self): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) - self.assertEqual( - ssl_vhost.filep, + assert ssl_vhost.filep == \ os.path.join(self.config_path, "sites-available", - "encryption-example-le-ssl.conf")) + "encryption-example-le-ssl.conf") - self.assertEqual(ssl_vhost.path, - "/files" + ssl_vhost.filep + "/IfModule/Virtualhost") - self.assertEqual(len(ssl_vhost.addrs), 1) - self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs) - self.assertEqual(ssl_vhost.name, "encryption-example.demo") - self.assertIs(ssl_vhost.ssl, True) - self.assertIs(ssl_vhost.enabled, False) + assert ssl_vhost.path == \ + "/files" + ssl_vhost.filep + "/IfModule/Virtualhost" + assert len(ssl_vhost.addrs) == 1 + assert {obj.Addr.fromstring("*:443")} == ssl_vhost.addrs + assert ssl_vhost.name == "encryption-example.demo" + assert ssl_vhost.ssl is True + assert ssl_vhost.enabled is False - self.assertEqual(len(self.config.vhosts), 13) + assert len(self.config.vhosts) == 13 def test_clean_vhost_ssl(self): # pylint: disable=protected-access @@ -649,12 +641,12 @@ class MultipleVhostsTest(util.ApacheTest): loc_cacert = self.config.parser.find_dir( 'SSLCACertificatePath', None, self.vh_truth[1].path, False) - self.assertEqual(len(loc_cert), 1) - self.assertEqual(len(loc_key), 1) + assert len(loc_cert) == 1 + assert len(loc_key) == 1 - self.assertEqual(len(loc_chain), 0) + assert len(loc_chain) == 0 - self.assertEqual(len(loc_cacert), 10) + assert len(loc_cacert) == 10 def test_deduplicate_directives(self): # pylint: disable=protected-access @@ -667,9 +659,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config._deduplicate_directives(self.vh_truth[1].path, [DIRECTIVE]) self.config.save() - self.assertEqual( - len(self.config.parser.find_dir( - DIRECTIVE, None, self.vh_truth[1].path, False)), 1) + assert len(self.config.parser.find_dir( + DIRECTIVE, None, self.vh_truth[1].path, False)) == 1 def test_remove_directives(self): # pylint: disable=protected-access @@ -684,9 +675,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config.save() for directive in DIRECTIVES: - self.assertEqual( - len(self.config.parser.find_dir( - directive, None, self.vh_truth[2].path, False)), 0) + assert len(self.config.parser.find_dir( + directive, None, self.vh_truth[2].path, False)) == 0 def test_make_vhost_ssl_bad_write(self): mock_open = mock.mock_open() @@ -694,13 +684,12 @@ class MultipleVhostsTest(util.ApacheTest): self.config.reverter.register_file_creation = mock.Mock() mock_open.side_effect = IOError with mock.patch("builtins.open", mock_open): - self.assertRaises( - errors.PluginError, - self.config.make_vhost_ssl, self.vh_truth[0]) + with pytest.raises(errors.PluginError): + self.config.make_vhost_ssl(self.vh_truth[0]) def test_get_ssl_vhost_path(self): # pylint: disable=protected-access - self.assertIs(self.config._get_ssl_vhost_path("example_path").endswith(".conf"), True) + assert self.config._get_ssl_vhost_path("example_path").endswith(".conf") is True @mock.patch("certbot_apache._internal.configurator.http_01.ApacheHttp01.perform") @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") @@ -714,10 +703,10 @@ class MultipleVhostsTest(util.ApacheTest): responses = self.config.perform(achalls) - self.assertEqual(mock_http_perform.call_count, 1) - self.assertEqual(responses, expected) + assert mock_http_perform.call_count == 1 + assert responses == expected - self.assertEqual(mock_restart.call_count, 1) + assert mock_restart.call_count == 1 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") @@ -731,9 +720,9 @@ class MultipleVhostsTest(util.ApacheTest): for i, achall in enumerate(achalls): self.config.cleanup([achall]) if i == len(achalls) - 1: - self.assertIs(mock_restart.called, True) + assert mock_restart.called is True else: - self.assertIs(mock_restart.called, False) + assert mock_restart.called is False @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") @@ -746,32 +735,35 @@ class MultipleVhostsTest(util.ApacheTest): self.config._chall_out.add(achall) # pylint: disable=protected-access self.config.cleanup([achalls[-1]]) - self.assertIs(mock_restart.called, False) + assert mock_restart.called is False self.config.cleanup(achalls) - self.assertIs(mock_restart.called, True) + assert mock_restart.called is True @mock.patch("certbot.util.run_script") def test_get_version(self, mock_script): mock_script.return_value = ( "Server Version: Apache/2.4.2 (Debian)", "") - self.assertEqual(self.config.get_version(), (2, 4, 2)) + assert self.config.get_version() == (2, 4, 2) mock_script.return_value = ( "Server Version: Apache/2 (Linux)", "") - self.assertEqual(self.config.get_version(), (2,)) + assert self.config.get_version() == (2,) mock_script.return_value = ( "Server Version: Apache (Debian)", "") - self.assertRaises(errors.PluginError, self.config.get_version) + with pytest.raises(errors.PluginError): + self.config.get_version() mock_script.return_value = ( "Server Version: Apache/2.3{0} Apache/2.4.7".format( os.linesep), "") - self.assertRaises(errors.PluginError, self.config.get_version) + with pytest.raises(errors.PluginError): + self.config.get_version() mock_script.side_effect = errors.SubprocessError("Can't find program") - self.assertRaises(errors.PluginError, self.config.get_version) + with pytest.raises(errors.PluginError): + self.config.get_version() @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_restart(self, _): @@ -781,7 +773,8 @@ class MultipleVhostsTest(util.ApacheTest): def test_restart_bad_process(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError] - self.assertRaises(errors.MisconfigurationError, self.config.restart) + with pytest.raises(errors.MisconfigurationError): + self.config.restart() @mock.patch("certbot.util.run_script") def test_config_test(self, _): @@ -791,25 +784,25 @@ class MultipleVhostsTest(util.ApacheTest): def test_config_test_bad_process(self, mock_run_script): mock_run_script.side_effect = errors.SubprocessError - self.assertRaises(errors.MisconfigurationError, - self.config.config_test) + with pytest.raises(errors.MisconfigurationError): + self.config.config_test() def test_more_info(self): - self.assertTrue(self.config.more_info()) + assert self.config.more_info() def test_get_chall_pref(self): - self.assertIsInstance(self.config.get_chall_pref(""), list) + assert isinstance(self.config.get_chall_pref(""), list) def test_install_ssl_options_conf(self): path = os.path.join(self.work_dir, "test_it") other_path = os.path.join(self.work_dir, "other_test_it") self.config.install_ssl_options_conf(path, other_path) - self.assertIs(os.path.isfile(path), True) - self.assertIs(os.path.isfile(other_path), True) + assert os.path.isfile(path) is True + assert os.path.isfile(other_path) is True # TEST ENHANCEMENTS def test_supported_enhancements(self): - self.assertIsInstance(self.config.supported_enhancements(), list) + assert isinstance(self.config.supported_enhancements(), list) def test_find_http_vhost_without_ancestor(self): # pylint: disable=protected-access @@ -817,8 +810,8 @@ class MultipleVhostsTest(util.ApacheTest): vhost.ssl = True vhost.ancestor = None res = self.config._get_http_vhost(vhost) - self.assertEqual(self.vh_truth[0].name, res.name) - self.assertEqual(self.vh_truth[0].aliases, res.aliases) + assert self.vh_truth[0].name == res.name + assert self.vh_truth[0].aliases == res.aliases @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._get_http_vhost") @mock.patch("certbot_apache._internal.display_ops.select_vhost") @@ -834,30 +827,28 @@ class MultipleVhostsTest(util.ApacheTest): mock_sel_vhost.return_value = None mock_get.return_value = None - self.assertRaises( - errors.PluginError, - self.config.enhance, "satoshi.com", "redirect") + with pytest.raises(errors.PluginError): + self.config.enhance("satoshi.com", "redirect") def test_enhance_unknown_enhancement(self): - self.assertRaises( - errors.PluginError, - self.config.enhance, "certbot.demo", "unknown_enhancement") + with pytest.raises(errors.PluginError): + self.config.enhance("certbot.demo", "unknown_enhancement") def test_enhance_no_ssl_vhost(self): with mock.patch("certbot_apache._internal.configurator.logger.error") as mock_log: - self.assertRaises(errors.PluginError, self.config.enhance, - "certbot.demo", "redirect") + with pytest.raises(errors.PluginError): + self.config.enhance("certbot.demo", "redirect") # Check that correct logger.warning was printed - self.assertIn("not able to find", mock_log.call_args[0][0]) - self.assertIn("\"redirect\"", mock_log.call_args[0][0]) + assert "not able to find" in mock_log.call_args[0][0] + assert "\"redirect\"" in mock_log.call_args[0][0] mock_log.reset_mock() - self.assertRaises(errors.PluginError, self.config.enhance, - "certbot.demo", "ensure-http-header", "Test") + with pytest.raises(errors.PluginError): + self.config.enhance("certbot.demo", "ensure-http-header", "Test") # Check that correct logger.warning was printed - self.assertIn("not able to find", mock_log.call_args[0][0]) - self.assertIn("Test", mock_log.call_args[0][0]) + assert "not able to find" in mock_log.call_args[0][0] + assert "Test" in mock_log.call_args[0][0] @mock.patch("certbot.util.exe_exists") def test_ocsp_stapling(self, mock_exe): @@ -877,14 +868,14 @@ class MultipleVhostsTest(util.ApacheTest): ssl_use_stapling_aug_path = self.config.parser.find_dir( "SSLUseStapling", "on", ssl_vhost.path) - self.assertEqual(len(ssl_use_stapling_aug_path), 1) + assert len(ssl_use_stapling_aug_path) == 1 ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep) stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache', "shmcb:/var/run/apache2/stapling_cache(128000)", ssl_vhost_aug_path) - self.assertEqual(len(stapling_cache_aug_path), 1) + assert len(stapling_cache_aug_path) == 1 @mock.patch("certbot.util.exe_exists") def test_ocsp_stapling_twice(self, mock_exe): @@ -904,13 +895,13 @@ class MultipleVhostsTest(util.ApacheTest): ssl_use_stapling_aug_path = self.config.parser.find_dir( "SSLUseStapling", "on", ssl_vhost.path) - self.assertEqual(len(ssl_use_stapling_aug_path), 1) + assert len(ssl_use_stapling_aug_path) == 1 ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep) stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache', "shmcb:/var/run/apache2/stapling_cache(128000)", ssl_vhost_aug_path) - self.assertEqual(len(stapling_cache_aug_path), 1) + assert len(stapling_cache_aug_path) == 1 def test_get_http_vhost_third_filter(self): ssl_vh = obj.VirtualHost( @@ -921,7 +912,7 @@ class MultipleVhostsTest(util.ApacheTest): # pylint: disable=protected-access http_vh = self.config._get_http_vhost(ssl_vh) - self.assertIs(http_vh.ssl, False) + assert http_vh.ssl is False @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") @@ -945,7 +936,7 @@ class MultipleVhostsTest(util.ApacheTest): "Header", None, ssl_vhost.path) # four args to HSTS header - self.assertEqual(len(hsts_header), 4) + assert len(hsts_header) == 4 def test_http_header_hsts_twice(self): self.config.parser.modules["mod_ssl.c"] = None @@ -957,9 +948,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config.enhance("encryption-example.demo", "ensure-http-header", "Strict-Transport-Security") - self.assertRaises( - errors.PluginEnhancementAlreadyPresent, - self.config.enhance, "encryption-example.demo", + with pytest.raises(errors.PluginEnhancementAlreadyPresent): + self.config.enhance("encryption-example.demo", "ensure-http-header", "Strict-Transport-Security") @mock.patch("certbot.util.run_script") @@ -976,7 +966,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.enhance("certbot.demo", "ensure-http-header", "Upgrade-Insecure-Requests") - self.assertIn("headers_module", self.config.parser.modules) + assert "headers_module" in self.config.parser.modules # Get the ssl vhost for certbot.demo ssl_vhost = self.config.assoc["certbot.demo"] @@ -987,7 +977,7 @@ class MultipleVhostsTest(util.ApacheTest): "Header", None, ssl_vhost.path) # four args to HSTS header - self.assertEqual(len(uir_header), 4) + assert len(uir_header) == 4 def test_http_header_uir_twice(self): self.config.parser.modules["mod_ssl.c"] = None @@ -999,9 +989,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config.enhance("encryption-example.demo", "ensure-http-header", "Upgrade-Insecure-Requests") - self.assertRaises( - errors.PluginEnhancementAlreadyPresent, - self.config.enhance, "encryption-example.demo", + with pytest.raises(errors.PluginEnhancementAlreadyPresent): + self.config.enhance("encryption-example.demo", "ensure-http-header", "Upgrade-Insecure-Requests") @mock.patch("certbot.util.run_script") @@ -1023,13 +1012,13 @@ class MultipleVhostsTest(util.ApacheTest): rw_rule = self.config.parser.find_dir( "RewriteRule", None, self.vh_truth[3].path) - self.assertEqual(len(rw_engine), 1) + assert len(rw_engine) == 1 # three args to rw_rule - self.assertEqual(len(rw_rule), 3) + assert len(rw_rule) == 3 # [:-3] to remove the vhost index number - self.assertIs(rw_engine[0].startswith(self.vh_truth[3].path[:-3]), True) - self.assertIs(rw_rule[0].startswith(self.vh_truth[3].path[:-3]), True) + assert rw_engine[0].startswith(self.vh_truth[3].path[:-3]) is True + assert rw_rule[0].startswith(self.vh_truth[3].path[:-3]) is True def test_rewrite_rule_exists(self): # Skip the enable mod @@ -1038,7 +1027,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.add_dir( self.vh_truth[3].path, "RewriteRule", ["Unknown"]) # pylint: disable=protected-access - self.assertIs(self.config._is_rewrite_exists(self.vh_truth[3]), True) + assert self.config._is_rewrite_exists(self.vh_truth[3]) is True def test_rewrite_engine_exists(self): # Skip the enable mod @@ -1047,7 +1036,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.add_dir( self.vh_truth[3].path, "RewriteEngine", "on") # pylint: disable=protected-access - self.assertTrue(self.config._is_rewrite_engine_on(self.vh_truth[3])) + assert self.config._is_rewrite_engine_on(self.vh_truth[3]) @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") @@ -1074,14 +1063,14 @@ class MultipleVhostsTest(util.ApacheTest): rw_rule = self.config.parser.find_dir( "RewriteRule", None, self.vh_truth[3].path) - self.assertEqual(len(rw_engine), 1) + assert len(rw_engine) == 1 # three args to rw_rule + 1 arg for the pre existing rewrite - self.assertEqual(len(rw_rule), 5) + assert len(rw_rule) == 5 # [:-3] to remove the vhost index number - self.assertIs(rw_engine[0].startswith(self.vh_truth[3].path[:-3]), True) - self.assertIs(rw_rule[0].startswith(self.vh_truth[3].path[:-3]), True) + assert rw_engine[0].startswith(self.vh_truth[3].path[:-3]) is True + assert rw_rule[0].startswith(self.vh_truth[3].path[:-3]) is True - self.assertIn("rewrite_module", self.config.parser.modules) + assert "rewrite_module" in self.config.parser.modules @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") @@ -1111,7 +1100,7 @@ class MultipleVhostsTest(util.ApacheTest): args_paths = self.config.parser.find_dir( "RewriteRule", None, http_vhost.path, False) arg_vals = [self.config.parser.aug.get(x) for x in args_paths] - self.assertEqual(arg_vals, constants.REWRITE_HTTPS_ARGS) + assert arg_vals == constants.REWRITE_HTTPS_ARGS def test_redirect_with_conflict(self): @@ -1123,8 +1112,8 @@ class MultipleVhostsTest(util.ApacheTest): # No names ^ this guy should conflict. # pylint: disable=protected-access - self.assertRaises( - errors.PluginError, self.config._enable_redirect, ssl_vh, "") + with pytest.raises(errors.PluginError): + self.config._enable_redirect(ssl_vh, "") def test_redirect_two_domains_one_vhost(self): # Skip the enable mod @@ -1139,7 +1128,7 @@ class MultipleVhostsTest(util.ApacheTest): "ApacheConfigurator._verify_no_certbot_redirect") with mock.patch(verify_no_redirect) as mock_verify: self.config.enhance("green.blue.purple.com", "redirect") - self.assertIs(mock_verify.called, False) + assert mock_verify.called is False def test_redirect_from_previous_run(self): # Skip the enable mod @@ -1151,9 +1140,8 @@ class MultipleVhostsTest(util.ApacheTest): # pylint: disable=protected-access self.config._enhanced_vhosts["redirect"].clear() - self.assertRaises( - errors.PluginEnhancementAlreadyPresent, - self.config.enhance, "green.blue.purple.com", "redirect") + with pytest.raises(errors.PluginEnhancementAlreadyPresent): + self.config.enhance("green.blue.purple.com", "redirect") def test_create_own_redirect(self): self.config.parser.modules["rewrite_module"] = None @@ -1164,7 +1152,7 @@ class MultipleVhostsTest(util.ApacheTest): # pylint: disable=protected-access self.config._enable_redirect(self.vh_truth[1], "") - self.assertEqual(len(self.config.vhosts), 13) + assert len(self.config.vhosts) == 13 def test_create_own_redirect_for_old_apache_version(self): self.config.parser.modules["rewrite_module"] = None @@ -1175,21 +1163,21 @@ class MultipleVhostsTest(util.ApacheTest): # pylint: disable=protected-access self.config._enable_redirect(self.vh_truth[1], "") - self.assertEqual(len(self.config.vhosts), 13) + assert len(self.config.vhosts) == 13 def test_sift_rewrite_rule(self): # pylint: disable=protected-access small_quoted_target = "RewriteRule ^ \"http://\"" - self.assertIs(self.config._sift_rewrite_rule(small_quoted_target), False) + assert self.config._sift_rewrite_rule(small_quoted_target) is False https_target = "RewriteRule ^ https://satoshi" - self.assertIs(self.config._sift_rewrite_rule(https_target), True) + assert self.config._sift_rewrite_rule(https_target) is True normal_target = "RewriteRule ^/(.*) http://www.a.com:1234/$1 [L,R]" - self.assertIs(self.config._sift_rewrite_rule(normal_target), False) + assert self.config._sift_rewrite_rule(normal_target) is False not_rewriterule = "NotRewriteRule ^ ..." - self.assertIs(self.config._sift_rewrite_rule(not_rewriterule), False) + assert self.config._sift_rewrite_rule(not_rewriterule) is False def get_key_and_achalls(self): """Return testing achallenges.""" @@ -1218,14 +1206,13 @@ class MultipleVhostsTest(util.ApacheTest): vhost = self.vh_truth[0] vhost.enabled = False vhost.filep = inc_path - self.assertEqual(self.config.parser.find_dir("Include", inc_path), []) - self.assertNotIn(os.path.dirname(inc_path), self.config.parser.existing_paths) + assert self.config.parser.find_dir("Include", inc_path) == [] + assert os.path.dirname(inc_path) not in self.config.parser.existing_paths self.config.enable_site(vhost) - self.assertGreaterEqual(len(self.config.parser.find_dir("Include", inc_path)), 1) - self.assertIn(os.path.dirname(inc_path), self.config.parser.existing_paths) - self.assertIn( - os.path.basename(inc_path), self.config.parser.existing_paths[ - os.path.dirname(inc_path)]) + assert len(self.config.parser.find_dir("Include", inc_path)) >= 1 + assert os.path.dirname(inc_path) in self.config.parser.existing_paths + assert os.path.basename(inc_path) in self.config.parser.existing_paths[ + os.path.dirname(inc_path)] @mock.patch('certbot_apache._internal.configurator.display_util.notify') def test_deploy_cert_not_parsed_path(self, unused_mock_notify): @@ -1247,7 +1234,7 @@ class MultipleVhostsTest(util.ApacheTest): "example/cert.pem", "example/key.pem", "example/cert_chain.pem") # Test that we actually called add_include - self.assertIs(mock_add.called, True) + assert mock_add.called is True shutil.rmtree(tmp_path) def test_deploy_cert_no_mod_ssl(self): @@ -1256,8 +1243,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.modules["socache_shmcb_module"] = None self.config.prepare_server_https = mock.Mock() - self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, - "encryption-example.demo", "example/cert.pem", "example/key.pem", + with pytest.raises(errors.MisconfigurationError): + self.config.deploy_cert("encryption-example.demo", "example/cert.pem", "example/key.pem", "example/cert_chain.pem", "example/fullchain.pem") @mock.patch("certbot_apache._internal.parser.ApacheParser.parsed_in_original") @@ -1266,12 +1253,11 @@ class MultipleVhostsTest(util.ApacheTest): ret_vh.enabled = True self.config.enable_site(ret_vh) # Make sure that we return early - self.assertIs(mock_parsed.called, False) + assert mock_parsed.called is False def test_enable_mod_unsupported(self): - self.assertRaises(errors.MisconfigurationError, - self.config.enable_mod, - "whatever") + with pytest.raises(errors.MisconfigurationError): + self.config.enable_mod("whatever") def test_choose_vhosts_wildcard(self): # pylint: disable=protected-access @@ -1281,15 +1267,15 @@ class MultipleVhostsTest(util.ApacheTest): vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", create_ssl=True) # Check that the dialog was called with one vh: certbot.demo - self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[3]) - self.assertEqual(len(mock_select_vhs.call_args_list), 1) + assert mock_select_vhs.call_args[0][0][0] == self.vh_truth[3] + assert len(mock_select_vhs.call_args_list) == 1 # And the actual returned values - self.assertEqual(len(vhs), 1) - self.assertEqual(vhs[0].name, "certbot.demo") - self.assertIs(vhs[0].ssl, True) + assert len(vhs) == 1 + assert vhs[0].name == "certbot.demo" + assert vhs[0].ssl is True - self.assertNotEqual(vhs[0], self.vh_truth[3]) + assert vhs[0] != self.vh_truth[3] @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") def test_choose_vhosts_wildcard_no_ssl(self, mock_makessl): @@ -1299,8 +1285,8 @@ class MultipleVhostsTest(util.ApacheTest): mock_select_vhs.return_value = [self.vh_truth[1]] vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", create_ssl=False) - self.assertIs(mock_makessl.called, False) - self.assertEqual(vhs[0], self.vh_truth[1]) + assert mock_makessl.called is False + assert vhs[0] == self.vh_truth[1] @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._vhosts_for_wildcard") @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") @@ -1313,15 +1299,15 @@ class MultipleVhostsTest(util.ApacheTest): mock_select_vhs.return_value = [self.vh_truth[7]] vhs = self.config._choose_vhosts_wildcard("whatever", create_ssl=True) - self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[7]) - self.assertEqual(len(mock_select_vhs.call_args_list), 1) + assert mock_select_vhs.call_args[0][0][0] == self.vh_truth[7] + assert len(mock_select_vhs.call_args_list) == 1 # Ensure that make_vhost_ssl was not called, vhost.ssl == true - self.assertIs(mock_makessl.called, False) + assert mock_makessl.called is False # And the actual returned values - self.assertEqual(len(vhs), 1) - self.assertIs(vhs[0].ssl, True) - self.assertEqual(vhs[0], self.vh_truth[7]) + assert len(vhs) == 1 + assert vhs[0].ssl is True + assert vhs[0] == self.vh_truth[7] @mock.patch('certbot_apache._internal.configurator.display_util.notify') def test_deploy_cert_wildcard(self, unused_mock_notify): @@ -1333,17 +1319,16 @@ class MultipleVhostsTest(util.ApacheTest): with mock.patch(mock_d) as mock_dep: self.config.deploy_cert("*.wildcard.example.org", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") - self.assertIs(mock_dep.called, True) - self.assertEqual(len(mock_dep.call_args_list), 1) - self.assertEqual(self.vh_truth[7], mock_dep.call_args_list[0][0][0]) + assert mock_dep.called is True + assert len(mock_dep.call_args_list) == 1 + assert self.vh_truth[7] == mock_dep.call_args_list[0][0][0] @mock.patch("certbot_apache._internal.display_ops.select_vhost_multiple") def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): # pylint: disable=protected-access mock_dialog.return_value = [] - self.assertRaises(errors.PluginError, - self.config.deploy_cert, - "*.wild.cat", "/tmp/path", "/tmp/path", + with pytest.raises(errors.PluginError): + self.config.deploy_cert("*.wild.cat", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") @@ -1355,7 +1340,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config._wildcard_vhosts["*.certbot.demo"] = [self.vh_truth[3]] self.config.enhance("*.certbot.demo", "ensure-http-header", "Upgrade-Insecure-Requests") - self.assertIs(mock_choose.called, False) + assert mock_choose.called is False @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") def test_enhance_wildcard_no_install(self, mock_choose): @@ -1365,22 +1350,21 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.modules["headers_module"] = None self.config.enhance("*.certbot.demo", "ensure-http-header", "Upgrade-Insecure-Requests") - self.assertIs(mock_choose.called, True) + assert mock_choose.called is True def test_add_vhost_id(self): for vh in [self.vh_truth[0], self.vh_truth[1], self.vh_truth[2]]: vh_id = self.config.add_vhost_id(vh) - self.assertEqual(vh, self.config.find_vhost_by_id(vh_id)) + assert vh == self.config.find_vhost_by_id(vh_id) def test_find_vhost_by_id_404(self): - self.assertRaises(errors.PluginError, - self.config.find_vhost_by_id, - "nonexistent") + with pytest.raises(errors.PluginError): + self.config.find_vhost_by_id("nonexistent") def test_add_vhost_id_already_exists(self): first_id = self.config.add_vhost_id(self.vh_truth[0]) second_id = self.config.add_vhost_id(self.vh_truth[0]) - self.assertEqual(first_id, second_id) + assert first_id == second_id def test_realpath_replaces_symlink(self): orig_match = self.config.parser.aug.match @@ -1399,11 +1383,11 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.parser_paths = ["/mocked/path"] self.config.parser.aug.match = mock_match vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 2) - self.assertEqual(vhs[0], self.vh_truth[1]) + assert len(vhs) == 2 + assert vhs[0] == self.vh_truth[1] # mock_vhost should have replaced the vh_truth[0], because its filepath # isn't a symlink - self.assertEqual(vhs[1], mock_vhost) + assert vhs[1] == mock_vhost class AugeasVhostsTest(util.ApacheTest): @@ -1427,25 +1411,25 @@ class AugeasVhostsTest(util.ApacheTest): self.config.parser.aug.match.side_effect = RuntimeError path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" chosen_vhost = self.config._create_vhost(path) - self.assertEqual(None, chosen_vhost) + assert None == chosen_vhost def test_choosevhost_works(self): path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" chosen_vhost = self.config._create_vhost(path) - self.assertTrue(chosen_vhost is None or chosen_vhost.path == path) + assert chosen_vhost is None or chosen_vhost.path == path @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._create_vhost") def test_get_vhost_continue(self, mock_vhost): mock_vhost.return_value = None vhs = self.config.get_virtual_hosts() - self.assertEqual([], vhs) + assert [] == vhs def test_choose_vhost_with_matching_wildcard(self): names = ( "an.example.net", "another.example.net", "an.other.example.net") for name in names: with self.subTest(name=name): - self.assertNotIn(name, self.config.choose_vhost(name).aliases) + assert name not in self.config.choose_vhost(name).aliases @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") def test_choose_vhost_without_matching_wildcard(self, mock_conflicts): @@ -1453,7 +1437,7 @@ class AugeasVhostsTest(util.ApacheTest): mock_path = "certbot_apache._internal.display_ops.select_vhost" with mock.patch(mock_path, lambda _, vhosts: vhosts[0]): for name in ("a.example.net", "other.example.net"): - self.assertIn(name, self.config.choose_vhost(name).aliases) + assert name in self.config.choose_vhost(name).aliases @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") def test_choose_vhost_wildcard_not_found(self, mock_conflicts): @@ -1467,7 +1451,7 @@ class AugeasVhostsTest(util.ApacheTest): for name in names: orig_cc = mock_select.call_count self.config.choose_vhost(name) - self.assertEqual(mock_select.call_count - orig_cc, 1) + assert mock_select.call_count - orig_cc == 1 def test_choose_vhost_wildcard_found(self): mock_path = "certbot_apache._internal.display_ops.select_vhost" @@ -1478,13 +1462,13 @@ class AugeasVhostsTest(util.ApacheTest): mock_select.return_value = self.config.vhosts[0] for name in names: self.config.choose_vhost(name) - self.assertEqual(mock_select.call_count, 0) + assert mock_select.call_count == 0 def test_augeas_span_error(self): broken_vhost = self.config.vhosts[0] broken_vhost.path = broken_vhost.path + "/nonexistent" - self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, - broken_vhost) + with pytest.raises(errors.PluginError): + self.config.make_vhost_ssl(broken_vhost) class MultiVhostsTest(util.ApacheTest): @@ -1506,39 +1490,38 @@ class MultiVhostsTest(util.ApacheTest): def test_make_vhost_ssl(self): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) - self.assertEqual( - ssl_vhost.filep, + assert ssl_vhost.filep == \ os.path.join(self.config_path, "sites-available", - "default-le-ssl.conf")) + "default-le-ssl.conf") - self.assertEqual(ssl_vhost.path, - "/files" + ssl_vhost.filep + "/IfModule/VirtualHost") - self.assertEqual(len(ssl_vhost.addrs), 1) - self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs) - self.assertEqual(ssl_vhost.name, "banana.vomit.com") - self.assertIs(ssl_vhost.ssl, True) - self.assertIs(ssl_vhost.enabled, False) + assert ssl_vhost.path == \ + "/files" + ssl_vhost.filep + "/IfModule/VirtualHost" + assert len(ssl_vhost.addrs) == 1 + assert {obj.Addr.fromstring("*:443")} == ssl_vhost.addrs + assert ssl_vhost.name == "banana.vomit.com" + assert ssl_vhost.ssl is True + assert ssl_vhost.enabled is False mock_path = "certbot_apache._internal.configurator.ApacheConfigurator._get_new_vh_path" with mock.patch(mock_path) as mock_getpath: mock_getpath.return_value = None - self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, - self.vh_truth[1]) + with pytest.raises(errors.PluginError): + self.config.make_vhost_ssl(self.vh_truth[1]) def test_get_new_path(self): with_index_1 = ["/path[1]/section[1]"] without_index = ["/path/section"] with_index_2 = ["/path[2]/section[2]"] - self.assertEqual(self.config._get_new_vh_path(without_index, - with_index_1), - None) - self.assertEqual(self.config._get_new_vh_path(without_index, - with_index_2), - with_index_2[0]) + assert self.config._get_new_vh_path(without_index, + with_index_1) == \ + None + assert self.config._get_new_vh_path(without_index, + with_index_2) == \ + with_index_2[0] both = with_index_1 + with_index_2 - self.assertEqual(self.config._get_new_vh_path(without_index, both), - with_index_2[0]) + assert self.config._get_new_vh_path(without_index, both) == \ + with_index_2[0] @mock.patch("certbot_apache._internal.configurator.display_util.notify") def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_notify): @@ -1546,7 +1529,7 @@ class MultiVhostsTest(util.ApacheTest): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[4]) - self.assertTrue(self.config.parser.find_dir("RewriteEngine", "on", ssl_vhost.path, False)) + assert self.config.parser.find_dir("RewriteEngine", "on", ssl_vhost.path, False) with open(ssl_vhost.filep) as the_file: conf_text = the_file.read() @@ -1554,10 +1537,10 @@ class MultiVhostsTest(util.ApacheTest): "\"https://new.example.com/docs/$1\" [R,L]") uncommented_rewrite_rule = ("RewriteRule \"^/docs/(.+)\" " "\"http://new.example.com/docs/$1\" [R,L]") - self.assertIn(commented_rewrite_rule, conf_text) - self.assertIn(uncommented_rewrite_rule, conf_text) - self.assertEqual(mock_notify.call_count, 1) - self.assertIn("Some rewrite rules", mock_notify.call_args[0][0]) + assert commented_rewrite_rule in conf_text + assert uncommented_rewrite_rule in conf_text + assert mock_notify.call_count == 1 + assert "Some rewrite rules" in mock_notify.call_args[0][0] @mock.patch("certbot_apache._internal.configurator.display_util.notify") def test_make_vhost_ssl_with_existing_rewrite_conds(self, mock_notify): @@ -1579,14 +1562,14 @@ class MultiVhostsTest(util.ApacheTest): "https://%{SERVER_NAME}%{REQUEST_URI} " "[L,NE,R=permanent]") - self.assertIn(not_commented_cond1, conf_line_set) - self.assertIn(not_commented_rewrite_rule, conf_line_set) + assert not_commented_cond1 in conf_line_set + assert not_commented_rewrite_rule in conf_line_set - self.assertIn(commented_cond1, conf_line_set) - self.assertIn(commented_cond2, conf_line_set) - self.assertIn(commented_rewrite_rule, conf_line_set) - self.assertEqual(mock_notify.call_count, 1) - self.assertIn("Some rewrite rules", mock_notify.call_args[0][0]) + assert commented_cond1 in conf_line_set + assert commented_cond2 in conf_line_set + assert commented_rewrite_rule in conf_line_set + assert mock_notify.call_count == 1 + assert "Some rewrite rules" in mock_notify.call_args[0][0] class InstallSslOptionsConfTest(util.ApacheTest): @@ -1606,15 +1589,15 @@ class InstallSslOptionsConfTest(util.ApacheTest): return crypto_util.sha256sum(self.config.pick_apache_config()) def _assert_current_file(self): - self.assertIs(os.path.isfile(self.config.mod_ssl_conf), True) - self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), - self._current_ssl_options_hash()) + assert os.path.isfile(self.config.mod_ssl_conf) is True + assert crypto_util.sha256sum(self.config.mod_ssl_conf) == \ + self._current_ssl_options_hash() def test_no_file(self): # prepare should have placed a file there self._assert_current_file() os.remove(self.config.mod_ssl_conf) - self.assertIs(os.path.isfile(self.config.mod_ssl_conf), False) + assert os.path.isfile(self.config.mod_ssl_conf) is False self._call() self._assert_current_file() @@ -1636,13 +1619,13 @@ class InstallSslOptionsConfTest(util.ApacheTest): mod_ssl_conf.write("a new line for the wrong hash\n") with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() - self.assertIs(mock_logger.warning.called, False) - self.assertIs(os.path.isfile(self.config.mod_ssl_conf), True) - self.assertEqual(crypto_util.sha256sum( - self.config.pick_apache_config()), - self._current_ssl_options_hash()) - self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), - self._current_ssl_options_hash()) + assert mock_logger.warning.called is False + assert os.path.isfile(self.config.mod_ssl_conf) is True + assert crypto_util.sha256sum( + self.config.pick_apache_config()) == \ + self._current_ssl_options_hash() + assert crypto_util.sha256sum(self.config.mod_ssl_conf) != \ + self._current_ssl_options_hash() def test_manually_modified_past_file_warns(self): with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: @@ -1651,16 +1634,16 @@ class InstallSslOptionsConfTest(util.ApacheTest): f.write("hashofanoldversion") with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() - self.assertEqual(mock_logger.warning.call_args[0][0], - "%s has been manually modified; updated file " - "saved to %s. We recommend updating %s for security purposes.") - self.assertEqual(crypto_util.sha256sum( - self.config.pick_apache_config()), - self._current_ssl_options_hash()) + assert mock_logger.warning.call_args[0][0] == \ + "%s has been manually modified; updated file " \ + "saved to %s. We recommend updating %s for security purposes." + assert crypto_util.sha256sum( + self.config.pick_apache_config()) == \ + self._current_ssl_options_hash() # only print warning once with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() - self.assertIs(mock_logger.warning.called, False) + assert mock_logger.warning.called is False def test_ssl_config_files_hash_in_all_hashes(self): """ @@ -1669,21 +1652,20 @@ class InstallSslOptionsConfTest(util.ApacheTest): file has been manually edited by the user, and will refuse to update it. This test ensures that all necessary hashes are present. """ - from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES import pkg_resources + from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES + tls_configs_dir = pkg_resources.resource_filename( "certbot_apache", os.path.join("_internal", "tls_configs")) all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir) if name.endswith('options-ssl-apache.conf')] - self.assertGreaterEqual(len(all_files), 1) + assert len(all_files) >= 1 for one_file in all_files: file_hash = crypto_util.sha256sum(one_file) - self.assertIn( - file_hash, ALL_SSL_OPTIONS_HASHES, - f"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " + assert file_hash in ALL_SSL_OPTIONS_HASHES, \ + f"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " \ f"hash of {one_file} when it is updated." - ) def test_openssl_version(self): self.config._openssl_version = None @@ -1703,7 +1685,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): with mock.patch("certbot_apache._internal.configurator." "ApacheConfigurator._open_module_file") as mock_omf: mock_omf.return_value = some_string_contents - self.assertEqual(self.config.openssl_version(), "1.0.2g") + assert self.config.openssl_version() == "1.0.2g" # ssl_module statically linked self.config._openssl_version = None @@ -1712,49 +1694,49 @@ class InstallSslOptionsConfTest(util.ApacheTest): with mock.patch("certbot_apache._internal.configurator." "ApacheConfigurator._open_module_file") as mock_omf: mock_omf.return_value = some_string_contents - self.assertEqual(self.config.openssl_version(), "1.0.2g") + assert self.config.openssl_version() == "1.0.2g" def test_current_version(self): self.config.version = (2, 4, 10) self.config._openssl_version = '1.0.2m' - self.assertIn('old', self.config.pick_apache_config()) + assert 'old' in self.config.pick_apache_config() self.config.version = (2, 4, 11) self.config._openssl_version = '1.0.2m' - self.assertIn('current', self.config.pick_apache_config()) + assert 'current' in self.config.pick_apache_config() self.config._openssl_version = '1.0.2a' - self.assertIn('old', self.config.pick_apache_config()) + assert 'old' in self.config.pick_apache_config() def test_openssl_version_warns(self): self.config._openssl_version = '1.0.2a' - self.assertEqual(self.config.openssl_version(), '1.0.2a') + assert self.config.openssl_version() == '1.0.2a' self.config._openssl_version = None with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: - self.assertEqual(self.config.openssl_version(), None) - self.assertIn("Could not find ssl_module", mock_log.call_args[0][0]) + assert self.config.openssl_version() == None + assert "Could not find ssl_module" in mock_log.call_args[0][0] # When no ssl_module is present at all self.config._openssl_version = None - self.assertNotIn("ssl_module", self.config.parser.modules) + assert "ssl_module" not in self.config.parser.modules with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: - self.assertEqual(self.config.openssl_version(), None) - self.assertIn("Could not find ssl_module", mock_log.call_args[0][0]) + assert self.config.openssl_version() == None + assert "Could not find ssl_module" in mock_log.call_args[0][0] # When ssl_module is statically linked but --apache-bin not provided self.config._openssl_version = None self.config.options.bin = None self.config.parser.modules['ssl_module'] = None with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: - self.assertEqual(self.config.openssl_version(), None) - self.assertIn("ssl_module is statically linked but", mock_log.call_args[0][0]) + assert self.config.openssl_version() == None + assert "ssl_module is statically linked but" in mock_log.call_args[0][0] self.config.parser.modules['ssl_module'] = "/fake/path" with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: # Check that correct logger.warning was printed - self.assertEqual(self.config.openssl_version(), None) - self.assertIn("Unable to read", mock_log.call_args[0][0]) + assert self.config.openssl_version() == None + assert "Unable to read" in mock_log.call_args[0][0] contents_missing_openssl = b"these contents won't match the regex" with mock.patch("certbot_apache._internal.configurator." @@ -1762,13 +1744,13 @@ class InstallSslOptionsConfTest(util.ApacheTest): mock_omf.return_value = contents_missing_openssl with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: # Check that correct logger.warning was printed - self.assertEqual(self.config.openssl_version(), None) - self.assertIn("Could not find OpenSSL", mock_log.call_args[0][0]) + assert self.config.openssl_version() == None + assert "Could not find OpenSSL" in mock_log.call_args[0][0] def test_open_module_file(self): mock_open = mock.mock_open(read_data="testing 12 3") with mock.patch("builtins.open", mock_open): - self.assertEqual(self.config._open_module_file("/nonsense/"), "testing 12 3") + assert self.config._open_module_file("/nonsense/") == "testing 12 3" if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/debian_test.py b/certbot-apache/tests/debian_test.py index facc65107..ae605264b 100644 --- a/certbot-apache/tests/debian_test.py +++ b/certbot-apache/tests/debian_test.py @@ -1,8 +1,11 @@ """Test for certbot_apache._internal.configurator for Debian overrides""" import shutil +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import os from certbot.tests import util as certbot_util @@ -14,8 +17,6 @@ import util class MultipleVhostsTestDebian(util.ApacheTest): """Multiple vhost tests for Debian family of distros""" - _multiprocess_can_split_ = True - def setUp(self): # pylint: disable=arguments-differ super().setUp() self.config = util.get_apache_configurator( @@ -41,7 +42,8 @@ class MultipleVhostsTestDebian(util.ApacheTest): def test_enable_mod_unsupported_dirs(self): shutil.rmtree(os.path.join(self.config.parser.root, "mods-enabled")) - self.assertRaises(errors.NotSupportedError, self.config.enable_mod, "ssl") + with pytest.raises(errors.NotSupportedError): + self.config.enable_mod("ssl") @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") @@ -53,29 +55,29 @@ class MultipleVhostsTestDebian(util.ApacheTest): mock_exe_exists.return_value = True self.config.enable_mod("ssl") - self.assertIn("ssl_module", self.config.parser.modules) - self.assertIn("mod_ssl.c", self.config.parser.modules) + assert "ssl_module" in self.config.parser.modules + assert "mod_ssl.c" in self.config.parser.modules - self.assertIs(mock_run_script.called, True) + assert mock_run_script.called is True def test_deploy_cert_enable_new_vhost(self): # Create ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) self.config.parser.modules["ssl_module"] = None self.config.parser.modules["mod_ssl.c"] = None - self.assertIs(ssl_vhost.enabled, False) + assert ssl_vhost.enabled is False with certbot_util.patch_display_util(): self.config.deploy_cert( "encryption-example.demo", "example/cert.pem", "example/key.pem", "example/cert_chain.pem", "example/fullchain.pem") - self.assertIs(ssl_vhost.enabled, True) + assert ssl_vhost.enabled is True # Make sure that we don't error out if symlink already exists ssl_vhost.enabled = False - self.assertIs(ssl_vhost.enabled, False) + assert ssl_vhost.enabled is False self.config.deploy_cert( "encryption-example.demo", "example/cert.pem", "example/key.pem", "example/cert_chain.pem", "example/fullchain.pem") - self.assertIs(ssl_vhost.enabled, True) + assert ssl_vhost.enabled is True def test_enable_site_failure(self): self.config.parser.root = "/tmp/nonexistent" @@ -83,10 +85,8 @@ class MultipleVhostsTestDebian(util.ApacheTest): mock_dir.return_value = True with mock.patch("certbot.compat.os.path.islink") as mock_link: mock_link.return_value = False - self.assertRaises( - errors.NotSupportedError, - self.config.enable_site, - obj.VirtualHost("asdf", "afsaf", set(), False, False)) + with pytest.raises(errors.NotSupportedError): + self.config.enable_site(obj.VirtualHost("asdf", "afsaf", set(), False, False)) def test_deploy_cert_newssl(self): self.config = util.get_apache_configurator( @@ -105,8 +105,8 @@ class MultipleVhostsTestDebian(util.ApacheTest): self.config.save() # Verify ssl_module was enabled. - self.assertIs(self.vh_truth[1].enabled, True) - self.assertIn("ssl_module", self.config.parser.modules) + assert self.vh_truth[1].enabled is True + assert "ssl_module" in self.config.parser.modules loc_cert = self.config.parser.find_dir( "sslcertificatefile", "example/fullchain.pem", @@ -115,15 +115,13 @@ class MultipleVhostsTestDebian(util.ApacheTest): "sslcertificateKeyfile", "example/key.pem", self.vh_truth[1].path) # Verify one directive was found in the correct file - self.assertEqual(len(loc_cert), 1) - self.assertEqual( - apache_util.get_file_path(loc_cert[0]), - self.vh_truth[1].filep) + assert len(loc_cert) == 1 + assert apache_util.get_file_path(loc_cert[0]) == \ + self.vh_truth[1].filep - self.assertEqual(len(loc_key), 1) - self.assertEqual( - apache_util.get_file_path(loc_key[0]), - self.vh_truth[1].filep) + assert len(loc_key) == 1 + assert apache_util.get_file_path(loc_key[0]) == \ + self.vh_truth[1].filep def test_deploy_cert_newssl_no_fullchain(self): self.config = util.get_apache_configurator( @@ -135,10 +133,10 @@ class MultipleVhostsTestDebian(util.ApacheTest): # Get the default 443 vhost self.config.assoc["random.demo"] = self.vh_truth[1] - self.assertRaises(errors.PluginError, - lambda: self.config.deploy_cert( + with pytest.raises(errors.PluginError): + self.config.deploy_cert( "random.demo", "example/cert.pem", - "example/key.pem")) + "example/key.pem") def test_deploy_cert_old_apache_no_chain(self): self.config = util.get_apache_configurator( @@ -150,10 +148,10 @@ class MultipleVhostsTestDebian(util.ApacheTest): # Get the default 443 vhost self.config.assoc["random.demo"] = self.vh_truth[1] - self.assertRaises(errors.PluginError, - lambda: self.config.deploy_cert( + with pytest.raises(errors.PluginError): + self.config.deploy_cert( "random.demo", "example/cert.pem", - "example/key.pem")) + "example/key.pem") @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") @@ -165,7 +163,7 @@ class MultipleVhostsTestDebian(util.ApacheTest): # This will create an ssl vhost for certbot.demo self.config.choose_vhost("certbot.demo") self.config.enhance("certbot.demo", "staple-ocsp") - self.assertIn("socache_shmcb_module", self.config.parser.modules) + assert "socache_shmcb_module" in self.config.parser.modules @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") @@ -178,7 +176,7 @@ class MultipleVhostsTestDebian(util.ApacheTest): self.config.choose_vhost("certbot.demo") self.config.enhance("certbot.demo", "ensure-http-header", "Strict-Transport-Security") - self.assertIn("headers_module", self.config.parser.modules) + assert "headers_module" in self.config.parser.modules @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") @@ -189,10 +187,10 @@ class MultipleVhostsTestDebian(util.ApacheTest): # This will create an ssl vhost for certbot.demo self.config.choose_vhost("certbot.demo") self.config.enhance("certbot.demo", "redirect") - self.assertIn("rewrite_module", self.config.parser.modules) + assert "rewrite_module" in self.config.parser.modules def test_enable_site_already_enabled(self): - self.assertIs(self.vh_truth[1].enabled, True) + assert self.vh_truth[1].enabled is True self.config.enable_site(self.vh_truth[1]) def test_enable_site_call_parent(self): @@ -202,13 +200,13 @@ class MultipleVhostsTestDebian(util.ApacheTest): vh = self.vh_truth[0] vh.enabled = False self.config.enable_site(vh) - self.assertIs(e_s.called, True) + assert e_s.called is True @mock.patch("certbot.util.exe_exists") def test_enable_mod_no_disable(self, mock_exe_exists): mock_exe_exists.return_value = False - self.assertRaises( - errors.MisconfigurationError, self.config.enable_mod, "ssl") + with pytest.raises(errors.MisconfigurationError): + self.config.enable_mod("ssl") if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/display_ops_test.py b/certbot-apache/tests/display_ops_test.py index 26927ffad..613fa42e9 100644 --- a/certbot-apache/tests/display_ops_test.py +++ b/certbot-apache/tests/display_ops_test.py @@ -1,7 +1,10 @@ """Test certbot_apache._internal.display_ops.""" +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot.display import util as display_util from certbot.tests import util as certbot_util @@ -19,7 +22,7 @@ class SelectVhostMultiTest(unittest.TestCase): self.base_dir, "debian_apache_2_4/multiple_vhosts") def test_select_no_input(self): - self.assertEqual(len(select_vhost_multiple([])), 0) + assert len(select_vhost_multiple([])) == 0 @certbot_util.patch_display_util() def test_select_correct(self, mock_util): @@ -29,15 +32,15 @@ class SelectVhostMultiTest(unittest.TestCase): vhs = select_vhost_multiple([self.vhosts[3], self.vhosts[2], self.vhosts[1]]) - self.assertIn(self.vhosts[2], vhs) - self.assertIn(self.vhosts[3], vhs) - self.assertNotIn(self.vhosts[1], vhs) + assert self.vhosts[2] in vhs + assert self.vhosts[3] in vhs + assert self.vhosts[1] not in vhs @certbot_util.patch_display_util() def test_select_cancel(self, mock_util): mock_util().checklist.return_value = (display_util.CANCEL, "whatever") vhs = select_vhost_multiple([self.vhosts[2], self.vhosts[3]]) - self.assertEqual(vhs, []) + assert vhs == [] class SelectVhostTest(unittest.TestCase): @@ -56,7 +59,7 @@ class SelectVhostTest(unittest.TestCase): @certbot_util.patch_display_util() def test_successful_choice(self, mock_util): mock_util().menu.return_value = (display_util.OK, 3) - self.assertEqual(self.vhosts[3], self._call(self.vhosts)) + assert self.vhosts[3] == self._call(self.vhosts) @certbot_util.patch_display_util() def test_noninteractive(self, mock_util): @@ -64,7 +67,7 @@ class SelectVhostTest(unittest.TestCase): try: self._call(self.vhosts) except errors.MissingCommandlineFlag as e: - self.assertIn("vhost ambiguity", str(e)) + assert "vhost ambiguity" in str(e) @certbot_util.patch_display_util() def test_more_info_cancel(self, mock_util): @@ -72,10 +75,10 @@ class SelectVhostTest(unittest.TestCase): (display_util.CANCEL, -1), ] - self.assertIsNone(self._call(self.vhosts)) + assert self._call(self.vhosts) is None def test_no_vhosts(self): - self.assertIsNone(self._call([])) + assert self._call([]) is None @mock.patch("certbot_apache._internal.display_ops.display_util") @mock.patch("certbot_apache._internal.display_ops.logger") @@ -84,7 +87,7 @@ class SelectVhostTest(unittest.TestCase): mock_display_util.menu.return_value = (display_util.OK, 0) self._call(self.vhosts) - self.assertEqual(mock_logger.debug.call_count, 1) + assert mock_logger.debug.call_count == 1 @certbot_util.patch_display_util() def test_multiple_names(self, mock_util): @@ -96,8 +99,8 @@ class SelectVhostTest(unittest.TestCase): False, False, "wildcard.com", {"*.wildcard.com"})) - self.assertEqual(self.vhosts[5], self._call(self.vhosts)) + assert self.vhosts[5] == self._call(self.vhosts) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/dualnode_test.py b/certbot-apache/tests/dualnode_test.py index a3e28d09e..2ce7fb6c7 100644 --- a/certbot-apache/tests/dualnode_test.py +++ b/certbot-apache/tests/dualnode_test.py @@ -5,6 +5,7 @@ from unittest import mock from certbot_apache._internal import assertions from certbot_apache._internal import augeasparser from certbot_apache._internal import dualparser +import pytest class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-methods @@ -49,20 +50,20 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- primary=self.block.secondary, secondary=self.block.primary) # Switched around - self.assertEqual(cnode.primary, self.comment.secondary) - self.assertEqual(cnode.secondary, self.comment.primary) - self.assertEqual(dnode.primary, self.directive.secondary) - self.assertEqual(dnode.secondary, self.directive.primary) - self.assertEqual(bnode.primary, self.block.secondary) - self.assertEqual(bnode.secondary, self.block.primary) + assert cnode.primary == self.comment.secondary + assert cnode.secondary == self.comment.primary + assert dnode.primary == self.directive.secondary + assert dnode.secondary == self.directive.primary + assert bnode.primary == self.block.secondary + assert bnode.secondary == self.block.primary def test_set_params(self): params = ("first", "second") self.directive.primary.set_parameters = mock.Mock() self.directive.secondary.set_parameters = mock.Mock() self.directive.set_parameters(params) - self.assertIs(self.directive.primary.set_parameters.called, True) - self.assertIs(self.directive.secondary.set_parameters.called, True) + assert self.directive.primary.set_parameters.called is True + assert self.directive.secondary.set_parameters.called is True def test_set_parameters(self): pparams = mock.MagicMock() @@ -72,8 +73,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.directive.primary.set_parameters = pparams self.directive.secondary.set_parameters = sparams self.directive.set_parameters(("param", "seq")) - self.assertIs(pparams.called, True) - self.assertIs(sparams.called, True) + assert pparams.called is True + assert sparams.called is True def test_delete_child(self): pdel = mock.MagicMock() @@ -81,8 +82,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.block.primary.delete_child = pdel self.block.secondary.delete_child = sdel self.block.delete_child(self.comment) - self.assertIs(pdel.called, True) - self.assertIs(sdel.called, True) + assert pdel.called is True + assert sdel.called is True def test_unsaved_files(self): puns = mock.MagicMock() @@ -92,13 +93,13 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.block.primary.unsaved_files = puns self.block.secondary.unsaved_files = suns self.block.unsaved_files() - self.assertIs(puns.called, True) - self.assertIs(suns.called, True) + assert puns.called is True + assert suns.called is True def test_getattr_equality(self): self.directive.primary.variableexception = "value" self.directive.secondary.variableexception = "not_value" - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): _ = self.directive.variableexception self.directive.primary.variable = "value" @@ -117,7 +118,7 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.comment.primary.dirty = False self.comment.secondary.dirty = True - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): assertions.assertEqual(self.comment.primary, self.comment.secondary) def test_parsernode_filepath_assert(self): @@ -127,7 +128,7 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.comment.primary.filepath = "first" self.comment.secondary.filepath = "second" - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): assertions.assertEqual(self.comment.primary, self.comment.secondary) def test_add_child_block(self): @@ -136,8 +137,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.block.primary.add_child_block = mock_first self.block.secondary.add_child_block = mock_second self.block.add_child_block("Block") - self.assertIs(mock_first.called, True) - self.assertIs(mock_second.called, True) + assert mock_first.called is True + assert mock_second.called is True def test_add_child_directive(self): mock_first = mock.MagicMock(return_value=self.directive.primary) @@ -145,8 +146,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.block.primary.add_child_directive = mock_first self.block.secondary.add_child_directive = mock_second self.block.add_child_directive("Directive") - self.assertIs(mock_first.called, True) - self.assertIs(mock_second.called, True) + assert mock_first.called is True + assert mock_second.called is True def test_add_child_comment(self): mock_first = mock.MagicMock(return_value=self.comment.primary) @@ -154,8 +155,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.block.primary.add_child_comment = mock_first self.block.secondary.add_child_comment = mock_second self.block.add_child_comment("Comment") - self.assertIs(mock_first.called, True) - self.assertIs(mock_second.called, True) + assert mock_first.called is True + assert mock_second.called is True def test_find_comments(self): pri_comments = [augeasparser.AugeasCommentNode(comment="some comment", @@ -179,9 +180,9 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- # Check that every comment response is represented in the list of # DualParserNode instances. for p in p_dcoms: - self.assertIn(p, p_coms) + assert p in p_coms for s in s_dcoms: - self.assertIn(s, s_coms) + assert s in s_coms def test_find_blocks_first_passing(self): youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing", @@ -203,8 +204,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- assertions.assertEqual(block.primary, block.secondary) except AssertionError: # pragma: no cover self.fail("Assertion should have passed") - self.assertIs(assertions.isPassDirective(block.primary), True) - self.assertIs(assertions.isPassDirective(block.secondary), False) + assert assertions.isPassDirective(block.primary) is True + assert assertions.isPassDirective(block.secondary) is False def test_find_blocks_second_passing(self): youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing", @@ -226,8 +227,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- assertions.assertEqual(block.primary, block.secondary) except AssertionError: # pragma: no cover self.fail("Assertion should have passed") - self.assertIs(assertions.isPassDirective(block.primary), False) - self.assertIs(assertions.isPassDirective(block.secondary), True) + assert assertions.isPassDirective(block.primary) is False + assert assertions.isPassDirective(block.secondary) is True def test_find_dirs_first_passing(self): notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing", @@ -249,8 +250,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- assertions.assertEqual(directive.primary, directive.secondary) except AssertionError: # pragma: no cover self.fail("Assertion should have passed") - self.assertIs(assertions.isPassDirective(directive.primary), True) - self.assertIs(assertions.isPassDirective(directive.secondary), False) + assert assertions.isPassDirective(directive.primary) is True + assert assertions.isPassDirective(directive.secondary) is False def test_find_dirs_second_passing(self): notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing", @@ -272,8 +273,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- assertions.assertEqual(directive.primary, directive.secondary) except AssertionError: # pragma: no cover self.fail("Assertion should have passed") - self.assertIs(assertions.isPassDirective(directive.primary), False) - self.assertIs(assertions.isPassDirective(directive.secondary), True) + assert assertions.isPassDirective(directive.primary) is False + assert assertions.isPassDirective(directive.secondary) is True def test_find_coms_first_passing(self): notpassing = [augeasparser.AugeasCommentNode(comment="notpassing", @@ -295,8 +296,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- assertions.assertEqual(comment.primary, comment.secondary) except AssertionError: # pragma: no cover self.fail("Assertion should have passed") - self.assertIs(assertions.isPassComment(comment.primary), True) - self.assertIs(assertions.isPassComment(comment.secondary), False) + assert assertions.isPassComment(comment.primary) is True + assert assertions.isPassComment(comment.secondary) is False def test_find_coms_second_passing(self): notpassing = [augeasparser.AugeasCommentNode(comment="notpassing", @@ -318,8 +319,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- assertions.assertEqual(comment.primary, comment.secondary) except AssertionError: # pragma: no cover self.fail("Assertion should have passed") - self.assertIs(assertions.isPassComment(comment.primary), False) - self.assertIs(assertions.isPassComment(comment.secondary), True) + assert assertions.isPassComment(comment.primary) is False + assert assertions.isPassComment(comment.secondary) is True def test_find_blocks_no_pass_equal(self): notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing", @@ -338,8 +339,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- blocks = self.block.find_blocks("anything") for block in blocks: with self.subTest(block=block): - self.assertEqual(block.primary, block.secondary) - self.assertIsNot(block.primary, block.secondary) + assert block.primary == block.secondary + assert block.primary is not block.secondary def test_find_dirs_no_pass_equal(self): notpassing1 = [augeasparser.AugeasDirectiveNode(name="notpassing", @@ -358,8 +359,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- directives = self.block.find_directives("anything") for directive in directives: with self.subTest(directive=directive): - self.assertEqual(directive.primary, directive.secondary) - self.assertIsNot(directive.primary, directive.secondary) + assert directive.primary == directive.secondary + assert directive.primary is not directive.secondary def test_find_comments_no_pass_equal(self): notpassing1 = [augeasparser.AugeasCommentNode(comment="notpassing", @@ -378,8 +379,8 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- comments = self.block.find_comments("anything") for comment in comments: with self.subTest(comment=comment): - self.assertEqual(comment.primary, comment.secondary) - self.assertIsNot(comment.primary, comment.secondary) + assert comment.primary == comment.secondary + assert comment.primary is not comment.secondary def test_find_blocks_no_pass_notequal(self): notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing", @@ -395,7 +396,7 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.block.primary.find_blocks = find_blocks_primary self.block.secondary.find_blocks = find_blocks_secondary - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): _ = self.block.find_blocks("anything") def test_parsernode_notequal(self): @@ -411,9 +412,9 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- ancestor=self.block, filepath="/path/to/whatever", metadata=self.metadata) - self.assertNotEqual(self.block, ne_block) - self.assertNotEqual(self.directive, ne_directive) - self.assertNotEqual(self.comment, ne_comment) + assert self.block != ne_block + assert self.directive != ne_directive + assert self.comment != ne_comment def test_parsed_paths(self): mock_p = mock.MagicMock(return_value=['/path/file.conf', @@ -423,15 +424,15 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.block.primary.parsed_paths = mock_p self.block.secondary.parsed_paths = mock_s self.block.parsed_paths() - self.assertIs(mock_p.called, True) - self.assertIs(mock_s.called, True) + assert mock_p.called is True + assert mock_s.called is True def test_parsed_paths_error(self): mock_p = mock.MagicMock(return_value=['/path/file.conf']) mock_s = mock.MagicMock(return_value=['/path/*.conf', '/another/path']) self.block.primary.parsed_paths = mock_p self.block.secondary.parsed_paths = mock_s - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.block.parsed_paths() def test_find_ancestors(self): @@ -440,5 +441,5 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public- self.block.primary.find_ancestors = primarymock self.block.secondary.find_ancestors = secondarymock self.block.find_ancestors("anything") - self.assertIs(primarymock.called, True) - self.assertIs(secondarymock.called, True) + assert primarymock.called is True + assert secondarymock.called is True diff --git a/certbot-apache/tests/entrypoint_test.py b/certbot-apache/tests/entrypoint_test.py index 0b9644f09..ca4a22400 100644 --- a/certbot-apache/tests/entrypoint_test.py +++ b/certbot-apache/tests/entrypoint_test.py @@ -1,46 +1,42 @@ """Test for certbot_apache._internal.entrypoint for override class resolution""" -import unittest +import sys from unittest import mock +import pytest + from certbot_apache._internal import configurator from certbot_apache._internal import entrypoint -class EntryPointTest(unittest.TestCase): - """Entrypoint tests""" +def test_get_configurator(): + with mock.patch("certbot.util.get_os_info") as mock_info: + for distro in entrypoint.OVERRIDE_CLASSES: + return_value = (distro, "whatever") + if distro == 'fedora_old': + return_value = ('fedora', '28') + elif distro == 'fedora': + return_value = ('fedora', '29') + mock_info.return_value = return_value + assert entrypoint.get_configurator() == \ + entrypoint.OVERRIDE_CLASSES[distro] - _multiprocess_can_split_ = True +def test_nonexistent_like(): + with mock.patch("certbot.util.get_os_info") as mock_info: + mock_info.return_value = ("nonexistent", "irrelevant") + with mock.patch("certbot.util.get_systemd_os_like") as mock_like: + for like in entrypoint.OVERRIDE_CLASSES: + mock_like.return_value = [like] + assert entrypoint.get_configurator() == \ + entrypoint.OVERRIDE_CLASSES[like] - def test_get_configurator(self): - - with mock.patch("certbot.util.get_os_info") as mock_info: - for distro in entrypoint.OVERRIDE_CLASSES: - return_value = (distro, "whatever") - if distro == 'fedora_old': - return_value = ('fedora', '28') - elif distro == 'fedora': - return_value = ('fedora', '29') - mock_info.return_value = return_value - self.assertEqual(entrypoint.get_configurator(), - entrypoint.OVERRIDE_CLASSES[distro]) - - def test_nonexistent_like(self): - with mock.patch("certbot.util.get_os_info") as mock_info: - mock_info.return_value = ("nonexistent", "irrelevant") - with mock.patch("certbot.util.get_systemd_os_like") as mock_like: - for like in entrypoint.OVERRIDE_CLASSES: - mock_like.return_value = [like] - self.assertEqual(entrypoint.get_configurator(), - entrypoint.OVERRIDE_CLASSES[like]) - - def test_nonexistent_generic(self): - with mock.patch("certbot.util.get_os_info") as mock_info: - mock_info.return_value = ("nonexistent", "irrelevant") - with mock.patch("certbot.util.get_systemd_os_like") as mock_like: - mock_like.return_value = ["unknown"] - self.assertEqual(entrypoint.get_configurator(), - configurator.ApacheConfigurator) +def test_nonexistent_generic(): + with mock.patch("certbot.util.get_os_info") as mock_info: + mock_info.return_value = ("nonexistent", "irrelevant") + with mock.patch("certbot.util.get_systemd_os_like") as mock_like: + mock_like.return_value = ["unknown"] + assert entrypoint.get_configurator() == \ + configurator.ApacheConfigurator if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/fedora_test.py b/certbot-apache/tests/fedora_test.py index 4ff704aaf..a313feb00 100644 --- a/certbot-apache/tests/fedora_test.py +++ b/certbot-apache/tests/fedora_test.py @@ -1,7 +1,10 @@ """Test for certbot_apache._internal.configurator for Fedora 29+ overrides""" +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import filesystem from certbot.compat import os @@ -52,7 +55,7 @@ class FedoraRestartTest(util.ApacheTest): self.temp_dir, "centos7_apache/apache") def _run_fedora_test(self): - self.assertIsInstance(self.config, override_fedora.FedoraConfigurator) + assert isinstance(self.config, override_fedora.FedoraConfigurator) self.config.config_test() def test_fedora_restart_error(self): @@ -62,8 +65,8 @@ class FedoraRestartTest(util.ApacheTest): mock_test.side_effect = [errors.MisconfigurationError, ''] with mock.patch("certbot.util.run_script") as mock_run: mock_run.side_effect = errors.SubprocessError - self.assertRaises(errors.MisconfigurationError, - self._run_fedora_test) + with pytest.raises(errors.MisconfigurationError): + self._run_fedora_test() def test_fedora_restart(self): c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" @@ -72,16 +75,14 @@ class FedoraRestartTest(util.ApacheTest): # First call raises error, second doesn't mock_test.side_effect = [errors.MisconfigurationError, ''] self._run_fedora_test() - self.assertEqual(mock_test.call_count, 2) - self.assertEqual(mock_run.call_args[0][0], - ['systemctl', 'restart', 'httpd']) + assert mock_test.call_count == 2 + assert mock_run.call_args[0][0] == \ + ['systemctl', 'restart', 'httpd'] class MultipleVhostsTestFedora(util.ApacheTest): """Multiple vhost tests for CentOS / RHEL family of distros""" - _multiprocess_can_split_ = True - def setUp(self): # pylint: disable=arguments-differ test_dir = "centos7_apache/apache" config_root = "centos7_apache/apache/httpd" @@ -97,7 +98,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): self.temp_dir, "centos7_apache/apache") def test_get_parser(self): - self.assertIsInstance(self.config.parser, override_fedora.FedoraParser) + assert isinstance(self.config.parser, override_fedora.FedoraParser) @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): @@ -127,22 +128,23 @@ class MultipleVhostsTestFedora(util.ApacheTest): mock_osi.return_value = ("fedora", "29") self.config.parser.update_runtime_variables() - self.assertEqual(mock_get.call_count, 3) - self.assertEqual(len(self.config.parser.modules), 4) - self.assertEqual(len(self.config.parser.variables), 2) - self.assertIn("TEST2", self.config.parser.variables) - self.assertIn("mod_another.c", self.config.parser.modules) + assert mock_get.call_count == 3 + assert len(self.config.parser.modules) == 4 + assert len(self.config.parser.variables) == 2 + assert "TEST2" in self.config.parser.variables + assert "mod_another.c" in self.config.parser.modules @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_get_version(self, mock_run_script): mock_run_script.return_value = ('', None) - self.assertRaises(errors.PluginError, self.config.get_version) - self.assertEqual(mock_run_script.call_args[0][0][0], 'httpd') + with pytest.raises(errors.PluginError): + self.config.get_version() + assert mock_run_script.call_args[0][0][0] == 'httpd' def test_get_virtual_hosts(self): """Make sure all vhosts are being properly found.""" vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 2) + assert len(vhs) == 2 found = 0 for vhost in vhs: @@ -152,7 +154,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): break else: raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 2) + assert found == 2 @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): @@ -168,26 +170,27 @@ class MultipleVhostsTestFedora(util.ApacheTest): mock_osi.return_value = ("fedora", "29") self.config.parser.update_runtime_variables() - self.assertIn("mock_define", self.config.parser.variables) - self.assertIn("mock_define_too", self.config.parser.variables) - self.assertIn("mock_value", self.config.parser.variables) - self.assertEqual("TRUE", self.config.parser.variables["mock_value"]) - self.assertIn("MOCK_NOSEP", self.config.parser.variables) - self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) + assert "mock_define" in self.config.parser.variables + assert "mock_define_too" in self.config.parser.variables + assert "mock_value" in self.config.parser.variables + assert "TRUE" == self.config.parser.variables["mock_value"] + assert "MOCK_NOSEP" in self.config.parser.variables + assert "NOSEP_VAL" == self.config.parser.variables["NOSEP_TWO"] @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_works(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, None] self.config.restart() - self.assertEqual(mock_run_script.call_count, 3) + assert mock_run_script.call_count == 3 @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_errors(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, errors.SubprocessError] - self.assertRaises(errors.MisconfigurationError, self.config.restart) + with pytest.raises(errors.MisconfigurationError): + self.config.restart() if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/gentoo_test.py b/certbot-apache/tests/gentoo_test.py index 4df46e70f..0774a6e8b 100644 --- a/certbot-apache/tests/gentoo_test.py +++ b/certbot-apache/tests/gentoo_test.py @@ -1,7 +1,10 @@ """Test for certbot_apache._internal.configurator for Gentoo overrides""" +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import filesystem from certbot.compat import os @@ -40,8 +43,6 @@ def get_vh_truth(temp_dir, config_name): class MultipleVhostsTestGentoo(util.ApacheTest): """Multiple vhost tests for non-debian distro""" - _multiprocess_can_split_ = True - def setUp(self): # pylint: disable=arguments-differ test_dir = "gentoo_apache/apache" config_root = "gentoo_apache/apache/apache2" @@ -59,12 +60,12 @@ class MultipleVhostsTestGentoo(util.ApacheTest): self.temp_dir, "gentoo_apache/apache") def test_get_parser(self): - self.assertIsInstance(self.config.parser, override_gentoo.GentooParser) + assert isinstance(self.config.parser, override_gentoo.GentooParser) def test_get_virtual_hosts(self): """Make sure all vhosts are being properly found.""" vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 3) + assert len(vhs) == 3 found = 0 for vhost in vhs: @@ -74,7 +75,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): break else: raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 3) + assert found == 3 def test_get_sysconfig_vars(self): """Make sure we read the Gentoo APACHE2_OPTS variable correctly""" @@ -86,7 +87,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): self.config.parser.update_runtime_variables() for define in defines: - self.assertIn(define, self.config.parser.variables) + assert define in self.config.parser.variables @mock.patch("certbot_apache._internal.apache_util.parse_from_subprocess") def test_no_binary_configdump(self, mock_subprocess): @@ -96,11 +97,11 @@ class MultipleVhostsTestGentoo(util.ApacheTest): with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): self.config.parser.update_runtime_variables() self.config.parser.reset_modules() - self.assertIs(mock_subprocess.called, False) + assert mock_subprocess.called is False self.config.parser.update_runtime_variables() self.config.parser.reset_modules() - self.assertIs(mock_subprocess.called, True) + assert mock_subprocess.called is True @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): @@ -122,15 +123,15 @@ class MultipleVhostsTestGentoo(util.ApacheTest): mock_osi.return_value = ("gentoo", "123") self.config.parser.update_runtime_variables() - self.assertEqual(mock_get.call_count, 1) - self.assertEqual(len(self.config.parser.modules), 4) - self.assertIn("mod_another.c", self.config.parser.modules) + assert mock_get.call_count == 1 + assert len(self.config.parser.modules) == 4 + assert "mod_another.c" in self.config.parser.modules @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_works(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, None] self.config.restart() - self.assertEqual(mock_run_script.call_count, 3) + assert mock_run_script.call_count == 3 if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/http_01_test.py b/certbot-apache/tests/http_01_test.py index fe5b69b33..b73be6f4f 100644 --- a/certbot-apache/tests/http_01_test.py +++ b/certbot-apache/tests/http_01_test.py @@ -1,9 +1,12 @@ """Test for certbot_apache._internal.http_01.""" -import unittest import errno +import sys from typing import List +import unittest from unittest import mock +import pytest + from acme import challenges from certbot import achallenges from certbot import errors @@ -47,7 +50,7 @@ class ApacheHttp01Test(util.ApacheTest): self.http = ApacheHttp01(self.config) def test_empty_perform(self): - self.assertEqual(len(self.http.perform()), 0) + assert len(self.http.perform()) == 0 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") def test_enable_modules_apache_2_4(self, mock_enmod): @@ -55,7 +58,7 @@ class ApacheHttp01Test(util.ApacheTest): del self.config.parser.modules["mod_authz_host.c"] enmod_calls = self.common_enable_modules_test(mock_enmod) - self.assertEqual(enmod_calls[0][0][0], "authz_core") + assert enmod_calls[0][0][0] == "authz_core" def common_enable_modules_test(self, mock_enmod): """Tests enabling mod_rewrite and other modules.""" @@ -64,7 +67,7 @@ class ApacheHttp01Test(util.ApacheTest): self.http.prepare_http01_modules() - self.assertIs(mock_enmod.called, True) + assert mock_enmod.called is True calls = mock_enmod.call_args_list other_calls = [] for call in calls: @@ -72,7 +75,7 @@ class ApacheHttp01Test(util.ApacheTest): other_calls.append(call) # If these lists are equal, we never enabled mod_rewrite - self.assertNotEqual(calls, other_calls) + assert calls != other_calls return other_calls def test_same_vhost(self): @@ -103,7 +106,7 @@ class ApacheHttp01Test(util.ApacheTest): def test_configure_multiple_vhosts(self): vhosts = [v for v in self.config.vhosts if "duplicate.example.com" in v.get_names()] - self.assertEqual(len(vhosts), 2) + assert len(vhosts) == 2 achalls = [ achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.chall_to_challb( @@ -128,7 +131,8 @@ class ApacheHttp01Test(util.ApacheTest): for achall in self.achalls: self.http.add_chall(achall) self.config.config.http01_port = 12345 - self.assertRaises(errors.PluginError, self.http.perform) + with pytest.raises(errors.PluginError): + self.http.perform() def test_perform_1_achall_apache_2_4(self): self.combinations_perform_test(num_achalls=1, minor_version=4) @@ -152,7 +156,7 @@ class ApacheHttp01Test(util.ApacheTest): matches = self.config.parser.find_dir( "Include", vhosts[0].filep, get_aug_path(self.config.parser.loc["default"])) - self.assertEqual(len(matches), 1) + assert len(matches) == 1 def combinations_perform_test(self, num_achalls, minor_version): """Test perform with the given achall count and Apache version.""" @@ -164,16 +168,16 @@ class ApacheHttp01Test(util.ApacheTest): def common_perform_test(self, achalls, vhosts): """Tests perform with the given achalls.""" challenge_dir = self.http.challenge_dir - self.assertIs(os.path.exists(challenge_dir), False) + assert os.path.exists(challenge_dir) is False for achall in achalls: self.http.add_chall(achall) expected_response = [ achall.response(self.account_key) for achall in achalls] - self.assertEqual(self.http.perform(), expected_response) + assert self.http.perform() == expected_response - self.assertIs(os.path.isdir(self.http.challenge_dir), True) - self.assertIs(filesystem.has_min_permissions(self.http.challenge_dir, 0o755), True) + assert os.path.isdir(self.http.challenge_dir) is True + assert filesystem.has_min_permissions(self.http.challenge_dir, 0o755) is True self._test_challenge_conf() for achall in achalls: @@ -183,19 +187,20 @@ class ApacheHttp01Test(util.ApacheTest): matches = self.config.parser.find_dir("Include", self.http.challenge_conf_pre, vhost.path) - self.assertEqual(len(matches), 1) + assert len(matches) == 1 matches = self.config.parser.find_dir("Include", self.http.challenge_conf_post, vhost.path) - self.assertEqual(len(matches), 1) + assert len(matches) == 1 - self.assertIs(os.path.exists(challenge_dir), True) + assert os.path.exists(challenge_dir) is True @mock.patch("certbot_apache._internal.http_01.filesystem.makedirs") def test_failed_makedirs(self, mock_makedirs): mock_makedirs.side_effect = OSError(errno.EACCES, "msg") self.http.add_chall(self.achalls[0]) - self.assertRaises(errors.PluginError, self.http.perform) + with pytest.raises(errors.PluginError): + self.http.perform() def _test_challenge_conf(self): with open(self.http.challenge_conf_pre) as f: @@ -204,20 +209,20 @@ class ApacheHttp01Test(util.ApacheTest): with open(self.http.challenge_conf_post) as f: post_conf_contents = f.read() - self.assertIn("RewriteEngine on", pre_conf_contents) - self.assertIn("RewriteRule", pre_conf_contents) + assert "RewriteEngine on" in pre_conf_contents + assert "RewriteRule" in pre_conf_contents - self.assertIn(self.http.challenge_dir, post_conf_contents) - self.assertIn("Require all granted", post_conf_contents) + assert self.http.challenge_dir in post_conf_contents + assert "Require all granted" in post_conf_contents def _test_challenge_file(self, achall): name = os.path.join(self.http.challenge_dir, achall.chall.encode("token")) validation = achall.validation(self.account_key) - self.assertIs(filesystem.has_min_permissions(name, 0o644), True) + assert filesystem.has_min_permissions(name, 0o644) is True with open(name, 'rb') as f: - self.assertEqual(f.read(), validation.encode()) + assert f.read() == validation.encode() if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/obj_test.py b/certbot-apache/tests/obj_test.py index 411ec21e9..16a71ba62 100644 --- a/certbot-apache/tests/obj_test.py +++ b/certbot-apache/tests/obj_test.py @@ -1,6 +1,9 @@ """Tests for certbot_apache._internal.obj.""" +import sys import unittest +import pytest + class VirtualHostTest(unittest.TestCase): """Test the VirtualHost class.""" @@ -23,18 +26,18 @@ class VirtualHostTest(unittest.TestCase): "fp", "vhp", {self.addr2}, False, False, "localhost") def test_repr(self): - self.assertEqual(repr(self.addr2), - "certbot_apache._internal.obj.Addr(('127.0.0.1', '443'))") + assert repr(self.addr2) == \ + "certbot_apache._internal.obj.Addr(('127.0.0.1', '443'))" def test_eq(self): - self.assertEqual(self.vhost1b, self.vhost1) - self.assertNotEqual(self.vhost1, self.vhost2) - self.assertEqual(str(self.vhost1b), str(self.vhost1)) - self.assertNotEqual(self.vhost1b, 1234) + assert self.vhost1b == self.vhost1 + assert self.vhost1 != self.vhost2 + assert str(self.vhost1b) == str(self.vhost1) + assert self.vhost1b != 1234 def test_ne(self): - self.assertNotEqual(self.vhost1, self.vhost2) - self.assertEqual(self.vhost1, self.vhost1b) + assert self.vhost1 != self.vhost2 + assert self.vhost1 == self.vhost1b def test_conflicts(self): from certbot_apache._internal.obj import Addr @@ -44,14 +47,14 @@ class VirtualHostTest(unittest.TestCase): "fp", "vhp", {Addr.fromstring("*:443"), Addr.fromstring("1.2.3.4:443")}, False, False) - self.assertIs(complex_vh.conflicts([self.addr1]), True) - self.assertIs(complex_vh.conflicts([self.addr2]), True) - self.assertIs(complex_vh.conflicts([self.addr_default]), False) + assert complex_vh.conflicts([self.addr1]) is True + assert complex_vh.conflicts([self.addr2]) is True + assert complex_vh.conflicts([self.addr_default]) is False - self.assertIs(self.vhost1.conflicts([self.addr2]), True) - self.assertIs(self.vhost1.conflicts([self.addr_default]), False) + assert self.vhost1.conflicts([self.addr2]) is True + assert self.vhost1.conflicts([self.addr_default]) is False - self.assertIs(self.vhost2.conflicts([self.addr1, self.addr_default]), False) + assert self.vhost2.conflicts([self.addr1, self.addr_default]) is False def test_same_server(self): from certbot_apache._internal.obj import VirtualHost @@ -66,12 +69,12 @@ class VirtualHostTest(unittest.TestCase): "fp", "vhp", {self.addr2, self.addr_default}, False, False, None) - self.assertIs(self.vhost1.same_server(self.vhost2), True) - self.assertIs(no_name1.same_server(no_name2), True) + assert self.vhost1.same_server(self.vhost2) is True + assert no_name1.same_server(no_name2) is True - self.assertIs(self.vhost1.same_server(no_name1), False) - self.assertIs(no_name1.same_server(no_name3), False) - self.assertIs(no_name1.same_server(no_name4), False) + assert self.vhost1.same_server(no_name1) is False + assert no_name1.same_server(no_name3) is False + assert no_name1.same_server(no_name4) is False class AddrTest(unittest.TestCase): @@ -87,54 +90,51 @@ class AddrTest(unittest.TestCase): self.addr_default = Addr.fromstring("_default_:443") def test_wildcard(self): - self.assertIs(self.addr.is_wildcard(), False) - self.assertIs(self.addr1.is_wildcard(), True) - self.assertIs(self.addr2.is_wildcard(), True) + assert self.addr.is_wildcard() is False + assert self.addr1.is_wildcard() is True + assert self.addr2.is_wildcard() is True def test_get_sni_addr(self): from certbot_apache._internal.obj import Addr - self.assertEqual( - self.addr.get_sni_addr("443"), Addr.fromstring("*:443")) - self.assertEqual( - self.addr.get_sni_addr("225"), Addr.fromstring("*:225")) - self.assertEqual( - self.addr1.get_sni_addr("443"), Addr.fromstring("127.0.0.1")) + assert self.addr.get_sni_addr("443") == Addr.fromstring("*:443") + assert self.addr.get_sni_addr("225") == Addr.fromstring("*:225") + assert self.addr1.get_sni_addr("443") == Addr.fromstring("127.0.0.1") def test_conflicts(self): # Note: Defined IP is more important than defined port in match - self.assertIs(self.addr.conflicts(self.addr1), True) - self.assertIs(self.addr.conflicts(self.addr2), True) - self.assertIs(self.addr.conflicts(self.addr_defined), True) - self.assertIs(self.addr.conflicts(self.addr_default), False) + assert self.addr.conflicts(self.addr1) is True + assert self.addr.conflicts(self.addr2) is True + assert self.addr.conflicts(self.addr_defined) is True + assert self.addr.conflicts(self.addr_default) is False - self.assertIs(self.addr1.conflicts(self.addr), False) - self.assertIs(self.addr1.conflicts(self.addr_defined), True) - self.assertIs(self.addr1.conflicts(self.addr_default), False) + assert self.addr1.conflicts(self.addr) is False + assert self.addr1.conflicts(self.addr_defined) is True + assert self.addr1.conflicts(self.addr_default) is False - self.assertIs(self.addr_defined.conflicts(self.addr1), False) - self.assertIs(self.addr_defined.conflicts(self.addr2), False) - self.assertIs(self.addr_defined.conflicts(self.addr), False) - self.assertIs(self.addr_defined.conflicts(self.addr_default), False) + assert self.addr_defined.conflicts(self.addr1) is False + assert self.addr_defined.conflicts(self.addr2) is False + assert self.addr_defined.conflicts(self.addr) is False + assert self.addr_defined.conflicts(self.addr_default) is False - self.assertIs(self.addr_default.conflicts(self.addr), True) - self.assertIs(self.addr_default.conflicts(self.addr1), True) - self.assertIs(self.addr_default.conflicts(self.addr_defined), True) + assert self.addr_default.conflicts(self.addr) is True + assert self.addr_default.conflicts(self.addr1) is True + assert self.addr_default.conflicts(self.addr_defined) is True # Self test - self.assertIs(self.addr.conflicts(self.addr), True) - self.assertIs(self.addr1.conflicts(self.addr1), True) + assert self.addr.conflicts(self.addr) is True + assert self.addr1.conflicts(self.addr1) is True # This is a tricky one... - self.assertIs(self.addr1.conflicts(self.addr2), True) + assert self.addr1.conflicts(self.addr2) is True def test_equal(self): - self.assertEqual(self.addr1, self.addr2) - self.assertNotEqual(self.addr, self.addr1) - self.assertNotEqual(self.addr, 123) + assert self.addr1 == self.addr2 + assert self.addr != self.addr1 + assert self.addr != 123 def test_not_equal(self): - self.assertEqual(self.addr1, self.addr2) - self.assertNotEqual(self.addr, self.addr1) + assert self.addr1 == self.addr2 + assert self.addr != self.addr1 if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/parser_test.py b/certbot-apache/tests/parser_test.py index 89633ae47..b4d13c78e 100644 --- a/certbot-apache/tests/parser_test.py +++ b/certbot-apache/tests/parser_test.py @@ -1,8 +1,11 @@ """Tests for certbot_apache._internal.parser.""" import shutil +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import os import util @@ -22,14 +25,15 @@ class BasicParserTest(util.ParserTest): def test_bad_parse(self): self.parser.parse_file(os.path.join(self.parser.root, "conf-available", "bad_conf_file.conf")) - self.assertRaises( - errors.PluginError, self.parser.check_parsing_errors, "httpd.aug") + with pytest.raises(errors.PluginError): + self.parser.check_parsing_errors("httpd.aug") def test_bad_save(self): mock_save = mock.Mock() mock_save.side_effect = IOError self.parser.aug.save = mock_save - self.assertRaises(errors.PluginError, self.parser.unsaved_files) + with pytest.raises(errors.PluginError): + self.parser.unsaved_files() @mock.patch("certbot_apache._internal.parser.logger") def test_bad_save_errors(self, mock_logger): @@ -37,7 +41,8 @@ class BasicParserTest(util.ParserTest): self.parser.aug.set("/augeas/load/Httpd/incl[last()]", nx_path) self.parser.add_dir(f"/files{nx_path}", "AddDirective", "test") - self.assertRaises(IOError, self.parser.save, {}) + with pytest.raises(IOError): + self.parser.save({}) mock_logger.error.assert_called_with( 'Unable to save files: %s.%s', '/non/existent/path.conf', mock.ANY) mock_logger.debug.assert_called_with( @@ -48,16 +53,16 @@ class BasicParserTest(util.ParserTest): mock_match = mock.Mock(return_value=["something"]) self.parser.aug.match = mock_match # pylint: disable=protected-access - self.assertEqual(self.parser.check_aug_version(), - ["something"]) + assert self.parser.check_aug_version() == \ + ["something"] self.parser.aug.match.side_effect = RuntimeError - self.assertIs(self.parser.check_aug_version(), False) + assert self.parser.check_aug_version() is False def test_find_config_root_no_root(self): # pylint: disable=protected-access os.remove(self.parser.loc["root"]) - self.assertRaises( - errors.NoInstallationError, self.parser._find_config_root) + with pytest.raises(errors.NoInstallationError): + self.parser._find_config_root() def test_parse_file(self): """Test parse_file. @@ -75,26 +80,26 @@ class BasicParserTest(util.ParserTest): matches = self.parser.aug.match( "/augeas/load/Httpd/incl [. ='%s']" % file_path) - self.assertTrue(matches) + assert matches def test_find_dir(self): test = self.parser.find_dir("Listen", "80") # This will only look in enabled hosts test2 = self.parser.find_dir("documentroot") - self.assertEqual(len(test), 1) - self.assertEqual(len(test2), 8) + assert len(test) == 1 + assert len(test2) == 8 def test_add_dir(self): aug_default = "/files" + self.parser.loc["default"] self.parser.add_dir(aug_default, "AddDirective", "test") - self.assertTrue(self.parser.find_dir("AddDirective", "test", aug_default)) + assert self.parser.find_dir("AddDirective", "test", aug_default) self.parser.add_dir(aug_default, "AddList", ["1", "2", "3", "4"]) matches = self.parser.find_dir("AddList", None, aug_default) for i, match in enumerate(matches): - self.assertEqual(self.parser.aug.get(match), str(i + 1)) + assert self.parser.aug.get(match) == str(i + 1) def test_add_dir_beginning(self): aug_default = "/files" + self.parser.loc["default"] @@ -102,24 +107,22 @@ class BasicParserTest(util.ParserTest): "AddDirectiveBeginning", "testBegin") - self.assertTrue(self.parser.find_dir("AddDirectiveBeginning", "testBegin", aug_default)) + assert self.parser.find_dir("AddDirectiveBeginning", "testBegin", aug_default) - self.assertEqual(self.parser.aug.get(aug_default+"/directive[1]"), "AddDirectiveBeginning") + assert self.parser.aug.get(aug_default+"/directive[1]") == "AddDirectiveBeginning" self.parser.add_dir_beginning(aug_default, "AddList", ["1", "2", "3", "4"]) matches = self.parser.find_dir("AddList", None, aug_default) for i, match in enumerate(matches): - self.assertEqual(self.parser.aug.get(match), str(i + 1)) + assert self.parser.aug.get(match) == str(i + 1) for name in ("empty.conf", "no-directives.conf"): conf = "/files" + os.path.join(self.parser.root, "sites-available", name) self.parser.add_dir_beginning(conf, "AddDirectiveBeginning", "testBegin") - self.assertGreater( - len(self.parser.find_dir("AddDirectiveBeginning", "testBegin", conf)), + assert len(self.parser.find_dir("AddDirectiveBeginning", "testBegin", conf)) > \ 0 - ) def test_empty_arg(self): - self.assertIsNone(self.parser.get_arg("/files/whatever/nonexistent")) + assert self.parser.get_arg("/files/whatever/nonexistent") is None def test_add_dir_to_ifmodssl(self): """test add_dir_to_ifmodssl. @@ -128,6 +131,7 @@ class BasicParserTest(util.ParserTest): """ from certbot_apache._internal.parser import get_aug_path + # This makes sure that find_dir will work self.parser.modules["mod_ssl.c"] = "/fake/path" @@ -137,11 +141,12 @@ class BasicParserTest(util.ParserTest): matches = self.parser.find_dir("FakeDirective", "123") - self.assertEqual(len(matches), 1) - self.assertIn("IfModule", matches[0]) + assert len(matches) == 1 + assert "IfModule" in matches[0] def test_add_dir_to_ifmodssl_multiple(self): from certbot_apache._internal.parser import get_aug_path + # This makes sure that find_dir will work self.parser.modules["mod_ssl.c"] = "/fake/path" @@ -151,12 +156,12 @@ class BasicParserTest(util.ParserTest): matches = self.parser.find_dir("FakeDirective") - self.assertEqual(len(matches), 3) - self.assertIn("IfModule", matches[0]) + assert len(matches) == 3 + assert "IfModule" in matches[0] def test_get_aug_path(self): from certbot_apache._internal.parser import get_aug_path - self.assertEqual("/files/etc/apache", get_aug_path("/etc/apache")) + assert "/files/etc/apache" == get_aug_path("/etc/apache") def test_set_locations(self): with mock.patch("certbot_apache._internal.parser.os.path") as mock_path: @@ -166,8 +171,8 @@ class BasicParserTest(util.ParserTest): # pylint: disable=protected-access results = self.parser._set_locations() - self.assertEqual(results["default"], results["listen"]) - self.assertEqual(results["default"], results["name"]) + assert results["default"] == results["listen"] + assert results["default"] == results["name"] @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") @mock.patch("certbot_apache._internal.parser.ApacheParser.get_arg") @@ -177,7 +182,7 @@ class BasicParserTest(util.ParserTest): with mock.patch("certbot_apache._internal.parser.logger") as mock_logger: self.parser.parse_modules() # Make sure that we got None return value and logged the file - self.assertIs(mock_logger.debug.called, True) + assert mock_logger.debug.called is True @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") @@ -284,11 +289,11 @@ class BasicParserTest(util.ParserTest): with mock.patch( "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: self.parser.update_runtime_variables() - self.assertEqual(self.parser.variables, expected_vars) - self.assertEqual(len(self.parser.modules), 58) + assert self.parser.variables == expected_vars + assert len(self.parser.modules) == 58 # None of the includes in inc_val should be in parsed paths. # Make sure we tried to include them all. - self.assertEqual(mock_parse.call_count, 25) + assert mock_parse.call_count == 25 @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") @@ -308,17 +313,16 @@ class BasicParserTest(util.ParserTest): "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: self.parser.update_runtime_variables() # No matching modules should have been found - self.assertEqual(len(self.parser.modules), 0) + assert len(self.parser.modules) == 0 # Only one of the three includes do not exist in already parsed # path derived from root configuration Include statements - self.assertEqual(mock_parse.call_count, 1) + assert mock_parse.call_count == 1 @mock.patch("certbot_apache._internal.apache_util.subprocess.run") def test_update_runtime_vars_bad_ctl(self, mock_run): mock_run.side_effect = OSError - self.assertRaises( - errors.MisconfigurationError, - self.parser.update_runtime_variables) + with pytest.raises(errors.MisconfigurationError): + self.parser.update_runtime_variables() @mock.patch("certbot_apache._internal.apache_util.subprocess.run") def test_update_runtime_vars_bad_exit(self, mock_run): @@ -326,16 +330,15 @@ class BasicParserTest(util.ParserTest): mock_proc.stdout = "" mock_proc.stderr = "" mock_proc.returncode = -1 - self.assertRaises( - errors.MisconfigurationError, - self.parser.update_runtime_variables) + with pytest.raises(errors.MisconfigurationError): + self.parser.update_runtime_variables() def test_add_comment(self): from certbot_apache._internal.parser import get_aug_path self.parser.add_comment(get_aug_path(self.parser.loc["name"]), "123456") comm = self.parser.find_comments("123456") - self.assertEqual(len(comm), 1) - self.assertIn(self.parser.loc["name"], comm[0]) + assert len(comm) == 1 + assert self.parser.loc["name"] in comm[0] class ParserInitTest(util.ApacheTest): @@ -352,18 +355,16 @@ class ParserInitTest(util.ApacheTest): from certbot_apache._internal.parser import ApacheParser mock_init_augeas.side_effect = errors.NoInstallationError self.config.config_test = mock.Mock() - self.assertRaises( - errors.NoInstallationError, ApacheParser, - os.path.relpath(self.config_path), self.config, + with pytest.raises(errors.NoInstallationError): + ApacheParser(os.path.relpath(self.config_path), self.config, "/dummy/vhostpath", version=(2, 4, 22)) def test_init_old_aug(self): from certbot_apache._internal.parser import ApacheParser with mock.patch("certbot_apache._internal.parser.ApacheParser.check_aug_version") as mock_c: mock_c.return_value = False - self.assertRaises( - errors.NotSupportedError, - ApacheParser, os.path.relpath(self.config_path), self.config, + with pytest.raises(errors.NotSupportedError): + ApacheParser(os.path.relpath(self.config_path), self.config, "/dummy/vhostpath", version=(2, 4, 22)) def test_root_normalized(self): @@ -377,7 +378,7 @@ class ParserInitTest(util.ApacheTest): parser = ApacheParser(path, self.config, "/dummy/vhostpath") - self.assertEqual(parser.root, self.config_path) + assert parser.root == self.config_path def test_root_absolute(self): from certbot_apache._internal.parser import ApacheParser @@ -386,7 +387,7 @@ class ParserInitTest(util.ApacheTest): parser = ApacheParser( os.path.relpath(self.config_path), self.config, "/dummy/vhostpath") - self.assertEqual(parser.root, self.config_path) + assert parser.root == self.config_path def test_root_no_trailing_slash(self): from certbot_apache._internal.parser import ApacheParser @@ -394,8 +395,8 @@ class ParserInitTest(util.ApacheTest): "update_runtime_variables"): parser = ApacheParser( self.config_path + os.path.sep, self.config, "/dummy/vhostpath") - self.assertEqual(parser.root, self.config_path) + assert parser.root == self.config_path if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/parsernode_configurator_test.py b/certbot-apache/tests/parsernode_configurator_test.py index 6c153acc4..b9f74dfbb 100644 --- a/certbot-apache/tests/parsernode_configurator_test.py +++ b/certbot-apache/tests/parsernode_configurator_test.py @@ -1,7 +1,10 @@ """Tests for ApacheConfigurator for AugeasParserNode classes""" +import sys import unittest from unittest import mock +import pytest + import util try: @@ -28,16 +31,16 @@ class ConfiguratorParserNodeTest(util.ApacheTest): # pylint: disable=too-many-p self.config.USE_PARSERNODE = True vhosts = self.config.get_virtual_hosts() # Legacy get_virtual_hosts() do not set the node - self.assertIsNotNone(vhosts[0].node) + assert vhosts[0].node is not None def test_parsernode_get_vhosts_mismatch(self): vhosts = self.config.get_virtual_hosts_v2() # One of the returned VirtualHost objects differs vhosts[0].name = "IdidntExpectThat" self.config.get_virtual_hosts_v2 = mock.MagicMock(return_value=vhosts) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): _ = self.config.get_virtual_hosts() if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/parsernode_test.py b/certbot-apache/tests/parsernode_test.py index 4ea5f8415..d85e8c753 100644 --- a/certbot-apache/tests/parsernode_test.py +++ b/certbot-apache/tests/parsernode_test.py @@ -1,6 +1,8 @@ """ Tests for ParserNode interface """ -import unittest +import sys + +import pytest from certbot_apache._internal import interfaces from certbot_apache._internal import parsernode_util as util @@ -101,28 +103,26 @@ interfaces.CommentNode.register(DummyCommentNode) interfaces.DirectiveNode.register(DummyDirectiveNode) interfaces.BlockNode.register(DummyBlockNode) -class ParserNodeTest(unittest.TestCase): +def test_dummy(): """Dummy placeholder test case for ParserNode interfaces""" - - def test_dummy(self): - dummyblock = DummyBlockNode( - name="None", - parameters=(), - ancestor=None, - dirty=False, - filepath="/some/random/path" - ) - dummydirective = DummyDirectiveNode( - name="Name", - ancestor=None, - filepath="/another/path" - ) - dummycomment = DummyCommentNode( - comment="Comment", - ancestor=dummyblock, - filepath="/some/file" - ) + dummyblock = DummyBlockNode( + name="None", + parameters=(), + ancestor=None, + dirty=False, + filepath="/some/random/path" + ) + dummydirective = DummyDirectiveNode( + name="Name", + ancestor=None, + filepath="/another/path" + ) + dummycomment = DummyCommentNode( + comment="Comment", + ancestor=dummyblock, + filepath="/some/file" + ) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/parsernode_util_test.py b/certbot-apache/tests/parsernode_util_test.py index 715388da5..c046b1ec7 100644 --- a/certbot-apache/tests/parsernode_util_test.py +++ b/certbot-apache/tests/parsernode_util_test.py @@ -1,115 +1,120 @@ """ Tests for ParserNode utils """ -import unittest +import sys + +import pytest from certbot_apache._internal import parsernode_util as util -class ParserNodeUtilTest(unittest.TestCase): - """Tests for ParserNode utils""" +def _setup_parsernode(): + """ Sets up kwargs dict for ParserNode """ + return { + "ancestor": None, + "dirty": False, + "filepath": "/tmp", + } - def _setup_parsernode(self): - """ Sets up kwargs dict for ParserNode """ - return { - "ancestor": None, - "dirty": False, - "filepath": "/tmp", - } +def _setup_commentnode(): + """ Sets up kwargs dict for CommentNode """ - def _setup_commentnode(self): - """ Sets up kwargs dict for CommentNode """ + pn = _setup_parsernode() + pn["comment"] = "x" + return pn - pn = self._setup_parsernode() - pn["comment"] = "x" - return pn +def _setup_directivenode(): + """ Sets up kwargs dict for DirectiveNode """ - def _setup_directivenode(self): - """ Sets up kwargs dict for DirectiveNode """ + pn = _setup_parsernode() + pn["name"] = "Name" + pn["parameters"] = ("first",) + pn["enabled"] = True + return pn - pn = self._setup_parsernode() - pn["name"] = "Name" - pn["parameters"] = ("first",) - pn["enabled"] = True - return pn +def test_unknown_parameter(): + params = _setup_parsernode() + params["unknown"] = "unknown" + with pytest.raises(TypeError): + util.parsernode_kwargs(params) - def test_unknown_parameter(self): - params = self._setup_parsernode() - params["unknown"] = "unknown" - self.assertRaises(TypeError, util.parsernode_kwargs, params) - - params = self._setup_commentnode() - params["unknown"] = "unknown" - self.assertRaises(TypeError, util.commentnode_kwargs, params) - - params = self._setup_directivenode() - params["unknown"] = "unknown" - self.assertRaises(TypeError, util.directivenode_kwargs, params) - - def test_parsernode(self): - params = self._setup_parsernode() - ctrl = self._setup_parsernode() - - ancestor, dirty, filepath, metadata = util.parsernode_kwargs(params) - self.assertEqual(ancestor, ctrl["ancestor"]) - self.assertEqual(dirty, ctrl["dirty"]) - self.assertEqual(filepath, ctrl["filepath"]) - self.assertEqual(metadata, {}) - - def test_parsernode_from_metadata(self): - params = self._setup_parsernode() - params.pop("filepath") - md = {"some": "value"} - params["metadata"] = md - - # Just testing that error from missing required parameters is not raised - _, _, _, metadata = util.parsernode_kwargs(params) - self.assertEqual(metadata, md) - - def test_commentnode(self): - params = self._setup_commentnode() - ctrl = self._setup_commentnode() - - comment, _ = util.commentnode_kwargs(params) - self.assertEqual(comment, ctrl["comment"]) - - def test_commentnode_from_metadata(self): - params = self._setup_commentnode() - params.pop("comment") - params["metadata"] = {} - - # Just testing that error from missing required parameters is not raised + params = _setup_commentnode() + params["unknown"] = "unknown" + with pytest.raises(TypeError): util.commentnode_kwargs(params) - def test_directivenode(self): - params = self._setup_directivenode() - ctrl = self._setup_directivenode() - - name, parameters, enabled, _ = util.directivenode_kwargs(params) - self.assertEqual(name, ctrl["name"]) - self.assertEqual(parameters, ctrl["parameters"]) - self.assertEqual(enabled, ctrl["enabled"]) - - def test_directivenode_from_metadata(self): - params = self._setup_directivenode() - params.pop("filepath") - params.pop("name") - params["metadata"] = {"irrelevant": "value"} - - # Just testing that error from missing required parameters is not raised + params = _setup_directivenode() + params["unknown"] = "unknown" + with pytest.raises(TypeError): util.directivenode_kwargs(params) - def test_missing_required(self): - c_params = self._setup_commentnode() - c_params.pop("comment") - self.assertRaises(TypeError, util.commentnode_kwargs, c_params) +def test_parsernode(): + params = _setup_parsernode() + ctrl = _setup_parsernode() - d_params = self._setup_directivenode() - d_params.pop("ancestor") - self.assertRaises(TypeError, util.directivenode_kwargs, d_params) + ancestor, dirty, filepath, metadata = util.parsernode_kwargs(params) + assert ancestor == ctrl["ancestor"] + assert dirty == ctrl["dirty"] + assert filepath == ctrl["filepath"] + assert metadata == {} - p_params = self._setup_parsernode() - p_params.pop("filepath") - self.assertRaises(TypeError, util.parsernode_kwargs, p_params) +def test_parsernode_from_metadata(): + params = _setup_parsernode() + params.pop("filepath") + md = {"some": "value"} + params["metadata"] = md + + # Just testing that error from missing required parameters is not raised + _, _, _, metadata = util.parsernode_kwargs(params) + assert metadata == md + +def test_commentnode(): + params = _setup_commentnode() + ctrl = _setup_commentnode() + + comment, _ = util.commentnode_kwargs(params) + assert comment == ctrl["comment"] + +def test_commentnode_from_metadata(): + params = _setup_commentnode() + params.pop("comment") + params["metadata"] = {} + + # Just testing that error from missing required parameters is not raised + util.commentnode_kwargs(params) + +def test_directivenode(): + params = _setup_directivenode() + ctrl = _setup_directivenode() + + name, parameters, enabled, _ = util.directivenode_kwargs(params) + assert name == ctrl["name"] + assert parameters == ctrl["parameters"] + assert enabled == ctrl["enabled"] + +def test_directivenode_from_metadata(): + params = _setup_directivenode() + params.pop("filepath") + params.pop("name") + params["metadata"] = {"irrelevant": "value"} + + # Just testing that error from missing required parameters is not raised + util.directivenode_kwargs(params) + +def test_missing_required(): + c_params = _setup_commentnode() + c_params.pop("comment") + with pytest.raises(TypeError): + util.commentnode_kwargs(c_params) + + d_params = _setup_directivenode() + d_params.pop("ancestor") + with pytest.raises(TypeError): + util.directivenode_kwargs(d_params) + + p_params = _setup_parsernode() + p_params.pop("filepath") + with pytest.raises(TypeError): + util.parsernode_kwargs(p_params) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-apache/tests/util.py b/certbot-apache/tests/util.py index 7cea90f25..57c374f07 100644 --- a/certbot-apache/tests/util.py +++ b/certbot-apache/tests/util.py @@ -1,10 +1,10 @@ """Common utilities for certbot_apache.""" import shutil import unittest +from unittest import mock import augeas import josepy as jose -from unittest import mock from certbot.compat import os from certbot.plugins import common diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py index a1e814405..62d99fb0e 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py @@ -5,7 +5,8 @@ from typing import Optional from typing import Type from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey, EllipticCurve +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey from cryptography.hazmat.primitives.serialization import load_pem_private_key @@ -13,8 +14,8 @@ try: import grp POSIX_MODE = True except ImportError: - import win32security import ntsecuritycon + import win32security POSIX_MODE = False EVERYBODY_SID = 'S-1-1-0' @@ -22,13 +23,13 @@ SYSTEM_SID = 'S-1-5-18' ADMINS_SID = 'S-1-5-32-544' -def assert_elliptic_key(key: str, curve: Type[EllipticCurve]) -> None: +def assert_elliptic_key(key_path: str, curve: Type[EllipticCurve]) -> None: """ Asserts that the key at the given path is an EC key using the given curve. - :param key: path to key + :param key_path: path to key :param EllipticCurve curve: name of the expected elliptic curve """ - with open(key, 'rb') as file: + with open(key_path, 'rb') as file: privkey1 = file.read() key = load_pem_private_key(data=privkey1, password=None, backend=default_backend()) @@ -37,13 +38,13 @@ def assert_elliptic_key(key: str, curve: Type[EllipticCurve]) -> None: assert isinstance(key.curve, curve), f"should have curve {curve} but was {key.curve}" -def assert_rsa_key(key: str, key_size: Optional[int] = None) -> None: +def assert_rsa_key(key_path: str, key_size: Optional[int] = None) -> None: """ Asserts that the key at the given path is an RSA key. - :param str key: path to key + :param str key_path: path to key :param int key_size: if provided, assert that the RSA key is of this size """ - with open(key, 'rb') as file: + with open(key_path, 'rb') as file: privkey1 = file.read() key = load_pem_private_key(data=privkey1, password=None, backend=default_backend()) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 65eca976d..6df79c7f9 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -6,8 +6,8 @@ import re import shutil import subprocess import time -from typing import Iterable from typing import Generator +from typing import Iterable from typing import Tuple from typing import Type @@ -18,7 +18,6 @@ from cryptography.hazmat.primitives.asymmetric.ec import SECP521R1 from cryptography.x509 import NameOID import pytest -from certbot_integration_tests.certbot_tests.context import IntegrationTestsContext from certbot_integration_tests.certbot_tests.assertions import assert_cert_count_for_lineage from certbot_integration_tests.certbot_tests.assertions import assert_elliptic_key from certbot_integration_tests.certbot_tests.assertions import assert_equals_group_owner @@ -31,6 +30,7 @@ from certbot_integration_tests.certbot_tests.assertions import assert_saved_rene from certbot_integration_tests.certbot_tests.assertions import assert_world_no_permissions from certbot_integration_tests.certbot_tests.assertions import assert_world_read_permissions from certbot_integration_tests.certbot_tests.assertions import EVERYBODY_SID +from certbot_integration_tests.certbot_tests.context import IntegrationTestsContext from certbot_integration_tests.utils import misc @@ -118,7 +118,7 @@ def test_http_01(context: IntegrationTestsContext) -> None: def test_manual_http_auth(context: IntegrationTestsContext) -> None: """Test the HTTP-01 challenge using manual plugin.""" with misc.create_http_server(context.http_01_port) as webroot,\ - misc.manual_http_hooks(webroot, context.http_01_port) as scripts: + misc.manual_http_hooks(webroot) as scripts: certname = context.get_domain() context.certbot([ @@ -248,8 +248,9 @@ def test_renew_files_propagate_permissions(context: IntegrationTestsContext) -> if os.name != 'nt': os.chmod(privkey1, 0o444) else: - import win32security # pylint: disable=import-error import ntsecuritycon # pylint: disable=import-error + import win32security # pylint: disable=import-error + # Get the current DACL of the private key security = win32security.GetFileSecurity(privkey1, win32security.DACL_SECURITY_INFORMATION) dacl = security.GetSecurityDescriptorDacl() @@ -329,7 +330,6 @@ def test_renew_with_changed_private_key_complexity(context: IntegrationTestsCont context.certbot(['renew', '--rsa-key-size', '2048']) - assert_cert_count_for_lineage(context.config_dir, certname, 3) key3 = join(context.config_dir, 'archive', certname, 'privkey3.pem') assert_rsa_key(key3, 2048) @@ -437,38 +437,37 @@ def test_reuse_key(context: IntegrationTestsContext) -> None: with open(join(context.config_dir, 'archive/{0}/privkey1.pem').format(certname), 'r') as file: privkey1 = file.read() + with open(join(context.config_dir, 'archive/{0}/cert1.pem').format(certname), 'r') as file: + cert1 = file.read() with open(join(context.config_dir, 'archive/{0}/privkey2.pem').format(certname), 'r') as file: privkey2 = file.read() + with open(join(context.config_dir, 'archive/{0}/cert2.pem').format(certname), 'r') as file: + cert2 = file.read() assert privkey1 == privkey2 context.certbot(['--cert-name', certname, '--domains', certname, '--force-renewal']) with open(join(context.config_dir, 'archive/{0}/privkey3.pem').format(certname), 'r') as file: privkey3 = file.read() + with open(join(context.config_dir, 'archive/{0}/cert3.pem').format(certname), 'r') as file: + cert3 = file.read() assert privkey2 != privkey3 context.certbot(['--cert-name', certname, '--domains', certname, '--reuse-key','--force-renewal']) - context.certbot(['renew', '--cert-name', certname, '--no-reuse-key', '--force-renewal']) - context.certbot(['renew', '--cert-name', certname, '--force-renewal']) - with open(join(context.config_dir, 'archive/{0}/privkey4.pem').format(certname), 'r') as file: privkey4 = file.read() + context.certbot(['renew', '--cert-name', certname, '--no-reuse-key', '--force-renewal']) with open(join(context.config_dir, 'archive/{0}/privkey5.pem').format(certname), 'r') as file: privkey5 = file.read() + context.certbot(['renew', '--cert-name', certname, '--force-renewal']) with open(join(context.config_dir, 'archive/{0}/privkey6.pem').format(certname), 'r') as file: privkey6 = file.read() + assert privkey3 == privkey4 assert privkey4 != privkey5 assert privkey5 != privkey6 - with open(join(context.config_dir, 'archive/{0}/cert1.pem').format(certname), 'r') as file: - cert1 = file.read() - with open(join(context.config_dir, 'archive/{0}/cert2.pem').format(certname), 'r') as file: - cert2 = file.read() - with open(join(context.config_dir, 'archive/{0}/cert3.pem').format(certname), 'r') as file: - cert3 = file.read() - assert len({cert1, cert2, cert3}) == 3 @@ -615,7 +614,6 @@ def test_renew_with_ec_keys(context: IntegrationTestsContext) -> None: # to the lineage key type, Certbot should keep the lineage key type. The curve will still # change to the default value, in order to stay consistent with the behavior of certonly. context.certbot(['certonly', '--force-renewal', '-d', certname]) - assert_cert_count_for_lineage(context.config_dir, certname, 3) key3 = join(context.config_dir, 'archive', certname, 'privkey3.pem') assert 200 < os.stat(key3).st_size < 250 # ec keys of 256 bits are ~225 bytes assert_elliptic_key(key3, SECP256R1) @@ -629,14 +627,12 @@ def test_renew_with_ec_keys(context: IntegrationTestsContext) -> None: context.certbot(['certonly', '--force-renewal', '-d', certname, '--key-type', 'rsa', '--cert-name', certname]) - assert_cert_count_for_lineage(context.config_dir, certname, 4) key4 = join(context.config_dir, 'archive', certname, 'privkey4.pem') assert_rsa_key(key4) # We expect that the previous behavior of requiring both --cert-name and # --key-type to be set to not apply to the renew subcommand. context.certbot(['renew', '--force-renewal', '--key-type', 'ecdsa']) - assert_cert_count_for_lineage(context.config_dir, certname, 5) key5 = join(context.config_dir, 'archive', certname, 'privkey5.pem') assert 200 < os.stat(key5).st_size < 250 # ec keys of 256 bits are ~225 bytes assert_elliptic_key(key5, SECP256R1) @@ -813,6 +809,25 @@ def test_revoke_multiple_lineages(context: IntegrationTestsContext) -> None: assert 'Not deleting revoked certificates due to overlapping archive dirs' in f.read() +def test_reconfigure(context: IntegrationTestsContext) -> None: + """Test the reconfigure verb""" + certname = context.get_domain() + context.certbot(['-d', certname]) + conf_path = join(context.config_dir, 'renewal', '{}.conf'.format(certname)) + + with misc.create_http_server(context.http_01_port) as webroot: + context.certbot(['reconfigure', '--cert-name', certname, + '-a', 'webroot', '--webroot-path', webroot]) + with open(conf_path, 'r') as f: + file_contents = f.read() + # Check changed value + assert 'authenticator = webroot' in file_contents, \ + 'Expected authenticator to be changed to webroot in renewal config' + # Check added value + assert f'webroot_path = {webroot}' in file_contents, \ + 'Expected new webroot path to be added to renewal config' + + def test_wildcard_certificates(context: IntegrationTestsContext) -> None: """Test wildcard certificate issuance.""" certname = context.get_domain('wild') @@ -902,7 +917,7 @@ def test_preferred_chain(context: IntegrationTestsContext) -> None: except NotImplementedError: pytest.skip('This ACME server does not support alternative issuers.') - names = [i.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value \ + names = [str(i.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value) \ for i in issuers] domain = context.get_domain('preferred-chain') @@ -915,9 +930,9 @@ def test_preferred_chain(context: IntegrationTestsContext) -> None: context.certbot(args) dumped = misc.read_certificate(cert_path) - assert 'Issuer: CN={}'.format(expected) in dumped, \ - 'Expected chain issuer to be {} when preferring {}'.format(expected, requested) + assert f'Issuer: CN={expected}'in dumped, \ + f'Expected chain issuer to be {expected} when preferring {requested}' with open(conf_path, 'r') as f: - assert 'preferred_chain = {}'.format(requested) in f.read(), \ + assert f'preferred_chain = {requested}' in f.read(), \ 'Expected preferred_chain to be set in renewal config' diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index ecd7fe778..b75c412d5 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -44,7 +44,7 @@ class ACMEServer: """ def __init__(self, acme_server: str, nodes: List[str], http_proxy: bool = True, stdout: bool = False, dns_server: Optional[str] = None, - http_01_port: int = DEFAULT_HTTP_01_PORT) -> None: + http_01_port: Optional[int] = None) -> None: """ Create an ACMEServer instance. :param str acme_server: the type of acme server used (boulder-v2 or pebble) @@ -63,12 +63,14 @@ class ACMEServer: self._processes: List[subprocess.Popen] = [] self._stdout = sys.stdout if stdout else open(os.devnull, 'w') # pylint: disable=consider-using-with self._dns_server = dns_server - self._http_01_port = http_01_port self._preterminate_cmds_args: List[Tuple[Tuple[Any, ...], Dict[str, Any]]] = [] - if http_01_port != DEFAULT_HTTP_01_PORT: - if self._acme_type != 'pebble' or self._proxy: - raise ValueError('setting http_01_port is not currently supported ' - 'with boulder or the HTTP proxy') + self._http_01_port = BOULDER_HTTP_01_PORT if self._acme_type == 'boulder' \ + else DEFAULT_HTTP_01_PORT + if http_01_port: + if (self._acme_type == 'pebble' and self._proxy) or self._acme_type == 'boulder': + raise ValueError('Setting http_01_port is not currently supported when ' + 'using Boulder or the HTTP proxy') + self._http_01_port = http_01_port def start(self) -> None: """Start the test stack""" @@ -236,11 +238,11 @@ class ACMEServer: def _prepare_http_proxy(self) -> None: """Configure and launch an HTTP proxy""" - print('=> Configuring the HTTP proxy...') + print(f'=> Configuring the HTTP proxy on port {self._http_01_port}...') http_port_map = cast(Dict[str, int], self.acme_xdist['http_port']) mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port) for node, port in http_port_map.items()} - command = [sys.executable, proxy.__file__, str(DEFAULT_HTTP_01_PORT), json.dumps(mapping)] + command = [sys.executable, proxy.__file__, str(self._http_01_port), json.dumps(mapping)] self._launch_process(command) print('=> Finished configuring the HTTP proxy.') diff --git a/certbot-ci/certbot_integration_tests/utils/certbot_call.py b/certbot-ci/certbot_integration_tests/utils/certbot_call.py index 429cf413e..bcec38be2 100755 --- a/certbot-ci/certbot_integration_tests/utils/certbot_call.py +++ b/certbot-ci/certbot_integration_tests/utils/certbot_call.py @@ -2,7 +2,6 @@ """Module to call certbot in test mode""" import os -import pkg_resources import subprocess import sys from typing import Dict @@ -10,6 +9,8 @@ from typing import List from typing import Mapping from typing import Tuple +import pkg_resources + import certbot_integration_tests # pylint: disable=wildcard-import,unused-wildcard-import from certbot_integration_tests.utils.constants import * diff --git a/certbot-ci/certbot_integration_tests/utils/constants.py b/certbot-ci/certbot_integration_tests/utils/constants.py index ce0cd91d5..5aabe379a 100644 --- a/certbot-ci/certbot_integration_tests/utils/constants.py +++ b/certbot-ci/certbot_integration_tests/utils/constants.py @@ -1,5 +1,6 @@ """Some useful constants to use throughout certbot-ci integration tests""" DEFAULT_HTTP_01_PORT = 5002 +BOULDER_HTTP_01_PORT = 80 TLS_ALPN_01_PORT = 5001 CHALLTESTSRV_PORT = 8055 BOULDER_V2_CHALLTESTSRV_URL = f'http://10.77.77.77:{CHALLTESTSRV_PORT}' diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index f21f7492a..22159c1fa 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -15,20 +15,20 @@ import sys import tempfile import threading import time -import warnings from typing import Generator from typing import Iterable from typing import List from typing import Optional from typing import Tuple +import warnings from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import Encoding from cryptography.hazmat.primitives.serialization import NoEncryption from cryptography.hazmat.primitives.serialization import PrivateFormat -from cryptography.x509 import load_pem_x509_certificate from cryptography.x509 import Certificate +from cryptography.x509 import load_pem_x509_certificate from OpenSSL import crypto import pkg_resources import requests @@ -158,14 +158,12 @@ set -e @contextlib.contextmanager -def manual_http_hooks(http_server_root: str, - http_port: int) -> Generator[Tuple[str, str], None, None]: +def manual_http_hooks(http_server_root: str) -> Generator[Tuple[str, str], None, None]: """ Generate suitable http-01 hooks command for test purpose in the given HTTP server webroot directory. These hooks command use temporary python scripts that are deleted upon context exit. :param str http_server_root: path to the HTTP server configured to serve http-01 challenges - :param int http_port: HTTP port that the HTTP server listen on :return (str, str): a tuple containing the authentication hook and cleanup hook commands """ tempdir = tempfile.mkdtemp() @@ -175,24 +173,12 @@ def manual_http_hooks(http_server_root: str, file_h.write('''\ #!/usr/bin/env python import os -import requests -import time -import sys challenge_dir = os.path.join('{0}', '.well-known', 'acme-challenge') os.makedirs(challenge_dir) challenge_file = os.path.join(challenge_dir, os.environ.get('CERTBOT_TOKEN')) with open(challenge_file, 'w') as file_h: file_h.write(os.environ.get('CERTBOT_VALIDATION')) -url = 'http://localhost:{1}/.well-known/acme-challenge/' + os.environ.get('CERTBOT_TOKEN') -for _ in range(0, 10): - time.sleep(1) - try: - if request.get(url).status_code == 200: - sys.exit(0) - except requests.exceptions.ConnectionError: - pass -raise ValueError('Error, url did not respond after 10 attempts: {{0}}'.format(url)) -'''.format(http_server_root.replace('\\', '\\\\'), http_port)) +'''.format(http_server_root.replace('\\', '\\\\'))) os.chmod(auth_script_path, 0o755) cleanup_script_path = os.path.join(tempdir, 'cleanup.py') @@ -230,11 +216,7 @@ def generate_csr(domains: Iterable[str], key_path: str, csr_path: str, # Ignore a warning on some old versions of cryptography warnings.simplefilter('ignore', category=PendingDeprecationWarning) _key = ec.generate_private_key(ec.SECP384R1(), default_backend()) - # This type ignore directive is required due to an outdated version of types-cryptography. - # It can be removed once package types-pyOpenSSL depends on cryptography instead of - # types-cryptography and so types-cryptography is not installed anymore. - # See https://github.com/python/typeshed/issues/5618 - _bytes = _key.private_bytes(encoding=Encoding.PEM, # type: ignore + _bytes = _key.private_bytes(encoding=Encoding.PEM, format=PrivateFormat.TraditionalOpenSSL, encryption_algorithm=NoEncryption()) key = crypto.load_privatekey(crypto.FILETYPE_PEM, _bytes) diff --git a/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py b/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py index e9c8acd49..1749b03da 100755 --- a/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py +++ b/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py @@ -6,11 +6,15 @@ to serve a mock OCSP responder during integration tests against Pebble. import datetime import http.server as BaseHTTPServer import re +from typing import cast +from typing import Union from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey from cryptography.x509 import ocsp from dateutil import parser import requests @@ -25,7 +29,9 @@ class _ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_POST(self) -> None: request = requests.get(PEBBLE_MANAGEMENT_URL + '/intermediate-keys/0', verify=False, timeout=10) - issuer_key = serialization.load_pem_private_key(request.content, None, default_backend()) + issuer_key = cast( + Union[RSAPrivateKey, EllipticCurvePrivateKey], + serialization.load_pem_private_key(request.content, None, default_backend())) request = requests.get(PEBBLE_MANAGEMENT_URL + '/intermediates/0', verify=False, timeout=10) diff --git a/certbot-ci/windows_installer_integration_tests/test_main.py b/certbot-ci/windows_installer_integration_tests/test_main.py index ad1622bde..635335522 100644 --- a/certbot-ci/windows_installer_integration_tests/test_main.py +++ b/certbot-ci/windows_installer_integration_tests/test_main.py @@ -3,13 +3,12 @@ import os import re import subprocess import time -import unittest from typing import Any import pytest -@unittest.skipIf(os.name != 'nt', reason='Windows installer tests must be run on Windows.') +@pytest.mark.skipif(os.name != 'nt', reason='Windows installer tests must be run on Windows.') def test_it(request: pytest.FixtureRequest) -> None: try: subprocess.check_call(['certbot', '--version']) diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py index fda51f3a0..ca8a02b18 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py @@ -7,15 +7,14 @@ from typing import Set from typing import Tuple from unittest import mock +from certbot import configuration +from certbot import errors as le_errors +from certbot import util as certbot_util from certbot_apache._internal import entrypoint from certbot_compatibility_test import errors from certbot_compatibility_test import util from certbot_compatibility_test.configurators import common as configurators_common -from certbot import configuration -from certbot import errors as le_errors -from certbot import util as certbot_util - class Proxy(configurators_common.Proxy): """A common base for Apache test configurators""" diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py index a115e8419..44a1cec01 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py @@ -8,21 +8,20 @@ import tempfile from typing import Iterable from typing import List from typing import Optional -from typing import Union from typing import overload from typing import Set from typing import Tuple from typing import Type - -from certbot_compatibility_test import errors -from certbot_compatibility_test import interfaces -from certbot_compatibility_test import util +from typing import Union from acme import challenges from acme.challenges import Challenge from certbot._internal import constants -from certbot.plugins import common from certbot.achallenges import AnnotatedChallenge +from certbot.plugins import common +from certbot_compatibility_test import errors +from certbot_compatibility_test import interfaces +from certbot_compatibility_test import util logger = logging.getLogger(__name__) diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index 1282923bf..6f2b9c1ae 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -5,14 +5,13 @@ import subprocess from typing import Set from typing import Tuple +from certbot import configuration from certbot_compatibility_test import errors from certbot_compatibility_test import util from certbot_compatibility_test.configurators import common as configurators_common from certbot_nginx._internal import configurator from certbot_nginx._internal import constants -from certbot import configuration - class Proxy(configurators_common.Proxy): """A common base for Nginx test configurators""" diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py index 6a29e74f5..2baa207f6 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py +++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py @@ -18,12 +18,6 @@ from typing import Optional from typing import Tuple from typing import Type -from certbot_compatibility_test import errors -from certbot_compatibility_test import util -from certbot_compatibility_test import validator -from certbot_compatibility_test.configurators import common -from certbot_compatibility_test.configurators.apache import common as a_common -from certbot_compatibility_test.configurators.nginx import common as n_common from OpenSSL import crypto from urllib3.util import connection @@ -34,6 +28,12 @@ from certbot import achallenges from certbot import errors as le_errors from certbot._internal.display import obj as display_obj from certbot.tests import acme_util +from certbot_compatibility_test import errors +from certbot_compatibility_test import util +from certbot_compatibility_test import validator +from certbot_compatibility_test.configurators import common +from certbot_compatibility_test.configurators.apache import common as a_common +from certbot_compatibility_test.configurators.nginx import common as n_common DESCRIPTION = """ Tests Certbot plugins against different server configurations. It is diff --git a/certbot-compatibility-test/certbot_compatibility_test/util.py b/certbot-compatibility-test/certbot_compatibility_test/util.py index 2a69d3835..4fe2e417d 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/util.py +++ b/certbot-compatibility-test/certbot_compatibility_test/util.py @@ -6,11 +6,11 @@ import re import shutil import tarfile -from certbot_compatibility_test import errors import josepy as jose from certbot._internal import constants from certbot.tests import util as test_util +from certbot_compatibility_test import errors _KEY_BASE = "rsa2048_key.pem" KEY_PATH = test_util.vector_path(_KEY_BASE) diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py deleted file mode 100644 index 1a2d4dfb5..000000000 --- a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py +++ /dev/null @@ -1,129 +0,0 @@ -"""Tests for certbot_compatibility_test.validator.""" -from typing import cast -from typing import Mapping -from typing import Optional -import unittest -from unittest import mock - -from certbot_compatibility_test import validator -from OpenSSL import crypto -import requests - -from acme import errors as acme_errors - - -class ValidatorTest(unittest.TestCase): - def setUp(self) -> None: - self.validator = validator.Validator() - - @mock.patch( - "certbot_compatibility_test.validator.crypto_util.probe_sni") - def test_certificate_success(self, mock_probe_sni: mock.MagicMock) -> None: - cert = crypto.X509() - mock_probe_sni.return_value = cert - self.assertTrue(self.validator.certificate( - cert, "test.com", "127.0.0.1")) - - @mock.patch( - "certbot_compatibility_test.validator.crypto_util.probe_sni") - def test_certificate_error(self, mock_probe_sni: mock.MagicMock) -> None: - cert = crypto.X509() - mock_probe_sni.side_effect = [acme_errors.Error] - self.assertFalse(self.validator.certificate( - cert, "test.com", "127.0.0.1")) - - @mock.patch( - "certbot_compatibility_test.validator.crypto_util.probe_sni") - def test_certificate_failure(self, mock_probe_sni: mock.MagicMock) -> None: - cert = crypto.X509() - cert.set_serial_number(1337) - mock_probe_sni.return_value = crypto.X509() - self.assertFalse(self.validator.certificate( - cert, "test.com", "127.0.0.1")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_successful_redirect(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - 301, {"location": "https://test.com"}) - self.assertTrue(self.validator.redirect("test.com")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_redirect_with_headers(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - 301, {"location": "https://test.com"}) - self.assertTrue(self.validator.redirect( - "test.com", headers={"Host": "test.com"})) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_redirect_missing_location(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response(301) - self.assertFalse(self.validator.redirect("test.com")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_redirect_wrong_status_code(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - 201, {"location": "https://test.com"}) - self.assertFalse(self.validator.redirect("test.com")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_redirect_wrong_redirect_code(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - 303, {"location": "https://test.com"}) - self.assertFalse(self.validator.redirect("test.com")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_hsts_empty(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - headers={"strict-transport-security": ""}) - self.assertFalse(self.validator.hsts("test.com")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_hsts_malformed(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - headers={"strict-transport-security": "sdfal"}) - self.assertFalse(self.validator.hsts("test.com")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_hsts_bad_max_age(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - headers={"strict-transport-security": "max-age=not-an-int"}) - self.assertFalse(self.validator.hsts("test.com")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_hsts_expire(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - headers={"strict-transport-security": "max-age=3600"}) - self.assertFalse(self.validator.hsts("test.com")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_hsts(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - headers={"strict-transport-security": "max-age=31536000"}) - self.assertTrue(self.validator.hsts("test.com")) - - @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_hsts_include_subdomains(self, mock_get_request: mock.MagicMock) -> None: - mock_get_request.return_value = create_response( - headers={"strict-transport-security": - "max-age=31536000;includeSubDomains"}) - self.assertTrue(self.validator.hsts("test.com")) - - def test_ocsp_stapling(self) -> None: - self.assertRaises( - NotImplementedError, self.validator.ocsp_stapling, "test.com") - - -def create_response(status_code: int = 200, - headers: Optional[Mapping[str, str]] = None) -> requests.Response: - """Creates a requests.Response object for testing""" - response = requests.Response() - response.status_code = status_code - - if headers: - response.headers = cast(requests.models.CaseInsensitiveDict, headers) - - return response - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 2b8226d6e..90f7ecd2d 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -1,7 +1,7 @@ from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 2891423c0..df074b8f4 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'cloudflare>=1.5.1', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-cloudflare', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py index cd73adc8f..83441a36c 100644 --- a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py +++ b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py @@ -1,9 +1,11 @@ """Tests for certbot_dns_cloudflare._internal.dns_cloudflare.""" +import sys import unittest from unittest import mock import CloudFlare +import pytest from certbot import errors from certbot.compat import os @@ -43,7 +45,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic self.auth.perform([self.achall]) expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) + assert expected == self.mock_client.mock_calls def test_cleanup(self): # _attempt_cleanup | pylint: disable=protected-access @@ -51,7 +53,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic self.auth.cleanup([self.achall]) expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) + assert expected == self.mock_client.mock_calls @test_util.patch_display_util() def test_api_token(self, unused_mock_get_utility): @@ -60,43 +62,37 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic self.auth.perform([self.achall]) expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) + assert expected == self.mock_client.mock_calls def test_no_creds(self): dns_test_common.write({}, self.config.cloudflare_credentials) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) def test_missing_email_or_key(self): dns_test_common.write({"cloudflare_api_key": API_KEY}, self.config.cloudflare_credentials) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) dns_test_common.write({"cloudflare_email": EMAIL}, self.config.cloudflare_credentials) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) def test_email_or_key_with_token(self): dns_test_common.write({"cloudflare_api_token": API_TOKEN, "cloudflare_email": EMAIL}, self.config.cloudflare_credentials) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) dns_test_common.write({"cloudflare_api_token": API_TOKEN, "cloudflare_api_key": API_KEY}, self.config.cloudflare_credentials) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) dns_test_common.write({"cloudflare_api_token": API_TOKEN, "cloudflare_email": EMAIL, "cloudflare_api_key": API_KEY}, self.config.cloudflare_credentials) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) class CloudflareClientTest(unittest.TestCase): @@ -124,61 +120,47 @@ class CloudflareClientTest(unittest.TestCase): post_data = self.cf.zones.dns_records.post.call_args[1]['data'] - self.assertEqual('TXT', post_data['type']) - self.assertEqual(self.record_name, post_data['name']) - self.assertEqual(self.record_content, post_data['content']) - self.assertEqual(self.record_ttl, post_data['ttl']) + assert 'TXT' == post_data['type'] + assert self.record_name == post_data['name'] + assert self.record_content == post_data['content'] + assert self.record_ttl == post_data['ttl'] def test_add_txt_record_error(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] self.cf.zones.dns_records.post.side_effect = CloudFlare.exceptions.CloudFlareAPIError(1009, '', '') - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) def test_add_txt_record_error_during_zone_lookup(self): self.cf.zones.get.side_effect = API_ERROR - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) def test_add_txt_record_zone_not_found(self): self.cf.zones.get.return_value = [] - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) def test_add_txt_record_bad_creds(self): self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(6003, '', '') - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9103, '', '') - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9109, '', '') - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(0, 'com.cloudflare.api.account.zone.list', '') - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) def test_del_txt_record(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] @@ -190,13 +172,13 @@ class CloudflareClientTest(unittest.TestCase): mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY), mock.call.zones.dns_records.delete(self.zone_id, self.record_id)] - self.assertEqual(expected, self.cf.mock_calls) + assert expected == self.cf.mock_calls get_data = self.cf.zones.dns_records.get.call_args[1]['params'] - self.assertEqual('TXT', get_data['type']) - self.assertEqual(self.record_name, get_data['name']) - self.assertEqual(self.record_content, get_data['content']) + assert 'TXT' == get_data['type'] + assert self.record_name == get_data['name'] + assert self.record_content == get_data['content'] def test_del_txt_record_error_during_zone_lookup(self): self.cf.zones.get.side_effect = API_ERROR @@ -213,7 +195,7 @@ class CloudflareClientTest(unittest.TestCase): mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY), mock.call.zones.dns_records.delete(self.zone_id, self.record_id)] - self.assertEqual(expected, self.cf.mock_calls) + assert expected == self.cf.mock_calls def test_del_txt_record_error_during_get(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] @@ -223,7 +205,7 @@ class CloudflareClientTest(unittest.TestCase): expected = [mock.call.zones.get(params=mock.ANY), mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY)] - self.assertEqual(expected, self.cf.mock_calls) + assert expected == self.cf.mock_calls def test_del_txt_record_no_record(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] @@ -233,7 +215,7 @@ class CloudflareClientTest(unittest.TestCase): expected = [mock.call.zones.get(params=mock.ANY), mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY)] - self.assertEqual(expected, self.cf.mock_calls) + assert expected == self.cf.mock_calls def test_del_txt_record_no_zone(self): self.cf.zones.get.return_value = [{'id': None}] @@ -241,8 +223,8 @@ class CloudflareClientTest(unittest.TestCase): self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) expected = [mock.call.zones.get(params=mock.ANY)] - self.assertEqual(expected, self.cf.mock_calls) + assert expected == self.cf.mock_calls if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index de8a3e98f..c1f637a73 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'python-digitalocean>=1.11', # 1.15.0 or newer is recommended for TTL support @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-digitalocean', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-digitalocean/tests/dns_digitalocean_test.py b/certbot-dns-digitalocean/tests/dns_digitalocean_test.py index 8fdee38f3..ab4579b76 100644 --- a/certbot-dns-digitalocean/tests/dns_digitalocean_test.py +++ b/certbot-dns-digitalocean/tests/dns_digitalocean_test.py @@ -1,9 +1,11 @@ """Tests for certbot_dns_digitalocean._internal.dns_digitalocean.""" +import sys import unittest from unittest import mock import digitalocean +import pytest from certbot import errors from certbot.compat import os @@ -39,7 +41,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic self.auth.perform([self.achall]) expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, 30)] - self.assertEqual(expected, self.mock_client.mock_calls) + assert expected == self.mock_client.mock_calls def test_cleanup(self): # _attempt_cleanup | pylint: disable=protected-access @@ -47,7 +49,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic self.auth.cleanup([self.achall]) expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) + assert expected == self.mock_client.mock_calls class DigitalOceanClientTest(unittest.TestCase): @@ -88,16 +90,14 @@ class DigitalOceanClientTest(unittest.TestCase): def test_add_txt_record_fail_to_find_domain(self): self.manager.get_all_domains.return_value = [] - self.assertRaises(errors.PluginError, - self.digitalocean_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.digitalocean_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) def test_add_txt_record_error_finding_domain(self): self.manager.get_all_domains.side_effect = API_ERROR - self.assertRaises(errors.PluginError, - self.digitalocean_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.digitalocean_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) def test_add_txt_record_error_creating_record(self): domain_mock = mock.MagicMock() @@ -106,9 +106,8 @@ class DigitalOceanClientTest(unittest.TestCase): self.manager.get_all_domains.return_value = [domain_mock] - self.assertRaises(errors.PluginError, - self.digitalocean_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + self.digitalocean_client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) def test_del_txt_record(self): first_record_mock = mock.MagicMock() @@ -136,10 +135,10 @@ class DigitalOceanClientTest(unittest.TestCase): self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - self.assertTrue(correct_record_mock.destroy.called) + assert correct_record_mock.destroy.called - self.assertFalse(first_record_mock.destroy.call_args_list) - self.assertFalse(last_record_mock.destroy.call_args_list) + assert not first_record_mock.destroy.call_args_list + assert not last_record_mock.destroy.call_args_list def test_del_txt_record_error_finding_domain(self): self.manager.get_all_domains.side_effect = API_ERROR @@ -172,4 +171,4 @@ class DigitalOceanClientTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 1395fd1ef..e17de6876 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ # This version of lexicon is required to address the problem described in @@ -32,6 +32,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-dnsimple', version=version, @@ -67,6 +71,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-dnsimple/tests/dns_dnsimple_test.py b/certbot-dns-dnsimple/tests/dns_dnsimple_test.py index 0e28f43b2..31a91be0f 100644 --- a/certbot-dns-dnsimple/tests/dns_dnsimple_test.py +++ b/certbot-dns-dnsimple/tests/dns_dnsimple_test.py @@ -1,8 +1,10 @@ """Tests for certbot_dns_dnsimple._internal.dns_dnsimple.""" +import sys import unittest from unittest import mock +import pytest from requests.exceptions import HTTPError from certbot.compat import os @@ -48,4 +50,4 @@ class DNSimpleLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseL if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index a12950fa1..e1482678c 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'dns-lexicon>=3.2.1', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-dnsmadeeasy', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py b/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py index 46f5895a8..2295e83cf 100644 --- a/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py +++ b/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py @@ -1,8 +1,10 @@ """Tests for certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy.""" +import sys import unittest from unittest import mock +import pytest from requests.exceptions import HTTPError from certbot.compat import os @@ -53,4 +55,4 @@ class DNSMadeEasyLexiconClientTest(unittest.TestCase, if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 88f282fcf..773480210 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'dns-lexicon>=3.2.1', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-gehirn', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-gehirn/tests/dns_gehirn_test.py b/certbot-dns-gehirn/tests/dns_gehirn_test.py index b982e3e1b..b15e36cab 100644 --- a/certbot-dns-gehirn/tests/dns_gehirn_test.py +++ b/certbot-dns-gehirn/tests/dns_gehirn_test.py @@ -1,8 +1,10 @@ """Tests for certbot_dns_gehirn._internal.dns_gehirn.""" +import sys import unittest from unittest import mock +import pytest from requests.exceptions import HTTPError from certbot.compat import os @@ -52,4 +54,4 @@ class GehirnLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-google/certbot_dns_google/__init__.py b/certbot-dns-google/certbot_dns_google/__init__.py index 2cac34652..19f81c0c6 100644 --- a/certbot-dns-google/certbot_dns_google/__init__.py +++ b/certbot-dns-google/certbot_dns_google/__init__.py @@ -38,6 +38,19 @@ for an account with the following permissions: * ``dns.resourceRecordSets.list`` * ``dns.resourceRecordSets.update`` +(The closest role is `dns.admin `_). + +If the above permissions are assigned at the `resource level `_, the same user must +have, at the PROJECT level, the following permissions: + +* ``dns.managedZones.get`` +* ``dns.managedZones.list`` + +(The closest role is `dns.reader `_). + Google provides instructions for `creating a service account `_ and `information about the required permissions =1.5.5', @@ -33,6 +33,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-google', version=version, @@ -68,6 +72,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-google/tests/dns_google_test.py b/certbot-dns-google/tests/dns_google_test.py index 27e8b1a65..22c221714 100644 --- a/certbot-dns-google/tests/dns_google_test.py +++ b/certbot-dns-google/tests/dns_google_test.py @@ -1,13 +1,14 @@ """Tests for certbot_dns_google._internal.dns_google.""" +import sys import unittest +from unittest import mock from googleapiclient import discovery from googleapiclient.errors import Error from googleapiclient.http import HttpMock from httplib2 import ServerNotFoundError - -from unittest import mock +import pytest from certbot import errors from certbot.compat import os @@ -46,7 +47,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic self.auth.perform([self.achall]) expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) + assert expected == self.mock_client.mock_calls def test_cleanup(self): # _attempt_cleanup | pylint: disable=protected-access @@ -54,13 +55,14 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic self.auth.cleanup([self.achall]) expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) + assert expected == self.mock_client.mock_calls @mock.patch('httplib2.Http.request', side_effect=ServerNotFoundError) @test_util.patch_display_util() def test_without_auth(self, unused_mock_get_utility, unused_mock): self.config.google_credentials = None - self.assertRaises(PluginError, self.auth.perform, [self.achall]) + with pytest.raises(PluginError): + self.auth.perform([self.achall]) class GoogleClientTest(unittest.TestCase): @@ -111,19 +113,17 @@ class GoogleClientTest(unittest.TestCase): unused_discovery_mock): from certbot_dns_google._internal.dns_google import _GoogleClient _GoogleClient(None) - self.assertFalse(credential_mock.called) - self.assertTrue(get_project_id_mock.called) + assert not credential_mock.called + assert get_project_id_mock.called @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') def test_client_bad_credentials_file(self, credential_mock): credential_mock.side_effect = ValueError('Some exception buried in oauth2client') - with self.assertRaises(errors.PluginError) as cm: + with pytest.raises(errors.PluginError) as exc_info: self._setUp_client_with_mock([]) - self.assertEqual( - str(cm.exception), - "Error parsing credentials file '/not/a/real/path.json': " + assert str(exc_info.value) == \ + "Error parsing credentials file '/not/a/real/path.json': " \ "Some exception buried in oauth2client" - ) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -132,7 +132,7 @@ class GoogleClientTest(unittest.TestCase): def test_add_txt_record(self, get_project_id_mock, credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) credential_mock.assert_called_once_with('/not/a/real/path.json', mock.ANY) - self.assertFalse(get_project_id_mock.called) + assert not get_project_id_mock.called client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @@ -182,10 +182,10 @@ class GoogleClientTest(unittest.TestCase): with mock.patch(mock_get_rrs) as mock_rrs: mock_rrs.return_value = {"rrdatas": ["sample-txt-contents"], "ttl": self.record_ttl} client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) - self.assertIs(changes.create.called, True) + assert changes.create.called is True deletions = changes.create.call_args_list[0][1]["body"]["deletions"][0] - self.assertIn("sample-txt-contents", deletions["rrdatas"]) - self.assertEqual(self.record_ttl, deletions["ttl"]) + assert "sample-txt-contents" in deletions["rrdatas"] + assert self.record_ttl == deletions["ttl"] @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -199,10 +199,10 @@ class GoogleClientTest(unittest.TestCase): custom_ttl = 300 mock_rrs.return_value = {"rrdatas": ["sample-txt-contents"], "ttl": custom_ttl} client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) - self.assertIs(changes.create.called, True) + assert changes.create.called is True deletions = changes.create.call_args_list[0][1]["body"]["deletions"][0] - self.assertIn("sample-txt-contents", deletions["rrdatas"]) - self.assertEqual(custom_ttl, deletions["ttl"]) #otherwise HTTP 412 + assert "sample-txt-contents" in deletions["rrdatas"] + assert custom_ttl == deletions["ttl"] #otherwise HTTP 412 @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -212,7 +212,7 @@ class GoogleClientTest(unittest.TestCase): [{'managedZones': [{'id': self.zone}]}]) client.add_txt_record(DOMAIN, "_acme-challenge.example.org", "example-txt-contents", self.record_ttl) - self.assertIs(changes.create.called, False) + assert changes.create.called is False @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -220,8 +220,8 @@ class GoogleClientTest(unittest.TestCase): def test_add_txt_record_error_during_zone_lookup(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock(API_ERROR) - self.assertRaises(errors.PluginError, client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -230,8 +230,8 @@ class GoogleClientTest(unittest.TestCase): client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, {'managedZones': []}]) - self.assertRaises(errors.PluginError, client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -240,8 +240,8 @@ class GoogleClientTest(unittest.TestCase): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) changes.create.side_effect = API_ERROR - self.assertRaises(errors.PluginError, client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) + with pytest.raises(errors.PluginError): + client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -345,8 +345,8 @@ class GoogleClientTest(unittest.TestCase): [{'managedZones': [{'id': self.zone}]}]) # Record name mocked in setUp found = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") - self.assertEqual(found["rrdatas"], ["\"example-txt-contents\""]) - self.assertEqual(found["ttl"], 60) + assert found["rrdatas"] == ["\"example-txt-contents\""] + assert found["ttl"] == 60 @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -355,7 +355,7 @@ class GoogleClientTest(unittest.TestCase): client, unused_changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone}]}]) not_found = client.get_existing_txt_rrset(self.zone, "nonexistent.tld") - self.assertIsNone(not_found) + assert not_found is None @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -365,7 +365,7 @@ class GoogleClientTest(unittest.TestCase): [{'managedZones': [{'id': self.zone}]}], API_ERROR) # Record name mocked in setUp found = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") - self.assertIsNone(found) + assert found is None @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') @mock.patch('certbot_dns_google._internal.dns_google.open', @@ -374,7 +374,7 @@ class GoogleClientTest(unittest.TestCase): client, unused_changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone}]}], API_ERROR) rrset = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") - self.assertFalse(rrset) + assert not rrset def test_get_project_id(self): from certbot_dns_google._internal.dns_google import _GoogleClient @@ -384,21 +384,23 @@ class GoogleClientTest(unittest.TestCase): with mock.patch('httplib2.Http.request', return_value=(response, 'test-test-1')): project_id = _GoogleClient.get_project_id() - self.assertEqual(project_id, 'test-test-1') + assert project_id == 'test-test-1' with mock.patch('httplib2.Http.request', return_value=(response, b'test-test-1')): project_id = _GoogleClient.get_project_id() - self.assertEqual(project_id, 'test-test-1') + assert project_id == 'test-test-1' failed_response = DummyResponse() failed_response.status = 404 with mock.patch('httplib2.Http.request', return_value=(failed_response, "some detailed http error response")): - self.assertRaises(ValueError, _GoogleClient.get_project_id) + with pytest.raises(ValueError): + _GoogleClient.get_project_id() with mock.patch('httplib2.Http.request', side_effect=ServerNotFoundError): - self.assertRaises(ServerNotFoundError, _GoogleClient.get_project_id) + with pytest.raises(ServerNotFoundError): + _GoogleClient.get_project_id() class DummyResponse: @@ -411,4 +413,4 @@ class DummyResponse: if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 36bee97e8..e77f17968 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'dns-lexicon>=3.2.1', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-linode', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-linode/tests/dns_linode_test.py b/certbot-dns-linode/tests/dns_linode_test.py index c227ef4b5..28e3f5265 100644 --- a/certbot-dns-linode/tests/dns_linode_test.py +++ b/certbot-dns-linode/tests/dns_linode_test.py @@ -1,8 +1,11 @@ """Tests for certbot_dns_linode._internal.dns_linode.""" +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import os from certbot.plugins import dns_test_common @@ -43,7 +46,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, auth = Authenticator(config, "linode") auth._setup_credentials() client = auth._get_linode_client() - self.assertEqual(3, client.api_version) + assert 3 == client.api_version # pylint: disable=protected-access def test_api_version_4_detection(self): @@ -55,7 +58,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, auth = Authenticator(config, "linode") auth._setup_credentials() client = auth._get_linode_client() - self.assertEqual(4, client.api_version) + assert 4 == client.api_version # pylint: disable=protected-access def test_api_version_3_detection_empty_version(self): @@ -67,7 +70,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, auth = Authenticator(config, "linode") auth._setup_credentials() client = auth._get_linode_client() - self.assertEqual(3, client.api_version) + assert 3 == client.api_version # pylint: disable=protected-access def test_api_version_4_detection_empty_version(self): @@ -79,7 +82,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, auth = Authenticator(config, "linode") auth._setup_credentials() client = auth._get_linode_client() - self.assertEqual(4, client.api_version) + assert 4 == client.api_version # pylint: disable=protected-access def test_api_version_3_manual(self): @@ -91,7 +94,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, auth = Authenticator(config, "linode") auth._setup_credentials() client = auth._get_linode_client() - self.assertEqual(3, client.api_version) + assert 3 == client.api_version # pylint: disable=protected-access def test_api_version_4_manual(self): @@ -103,7 +106,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, auth = Authenticator(config, "linode") auth._setup_credentials() client = auth._get_linode_client() - self.assertEqual(4, client.api_version) + assert 4 == client.api_version # pylint: disable=protected-access def test_api_version_error(self): @@ -114,7 +117,8 @@ class AuthenticatorTest(test_util.TempDirTestCase, linode_propagation_seconds=0) auth = Authenticator(config, "linode") auth._setup_credentials() - self.assertRaises(errors.PluginError, auth._get_linode_client) + with pytest.raises(errors.PluginError): + auth._get_linode_client() class LinodeLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): @@ -144,4 +148,4 @@ class Linode4LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLe if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index c6b889548..5abeaea3b 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'dns-lexicon>=3.2.1', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-luadns', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-luadns/tests/dns_luadns_test.py b/certbot-dns-luadns/tests/dns_luadns_test.py index 3c1ac6841..2cade9f9a 100644 --- a/certbot-dns-luadns/tests/dns_luadns_test.py +++ b/certbot-dns-luadns/tests/dns_luadns_test.py @@ -1,8 +1,10 @@ """Tests for certbot_dns_luadns._internal.dns_luadns.""" +import sys import unittest from unittest import mock +import pytest from requests.exceptions import HTTPError from certbot.compat import os @@ -49,4 +51,4 @@ class LuaDNSLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index cad383c2b..04eb47cd3 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'dns-lexicon>=3.2.1', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-nsone', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-nsone/tests/dns_nsone_test.py b/certbot-dns-nsone/tests/dns_nsone_test.py index 13ea09b3d..f4da5b4cc 100644 --- a/certbot-dns-nsone/tests/dns_nsone_test.py +++ b/certbot-dns-nsone/tests/dns_nsone_test.py @@ -1,8 +1,10 @@ """Tests for certbot_dns_nsone._internal.dns_nsone.""" +import sys import unittest from unittest import mock +import pytest from requests.exceptions import HTTPError from certbot.compat import os @@ -49,4 +51,4 @@ class NS1LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexico if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index a8a83018b..8f7bbcfd8 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'dns-lexicon>=3.2.1', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-ovh', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-ovh/tests/dns_ovh_test.py b/certbot-dns-ovh/tests/dns_ovh_test.py index 7eb767b70..a83ec8470 100644 --- a/certbot-dns-ovh/tests/dns_ovh_test.py +++ b/certbot-dns-ovh/tests/dns_ovh_test.py @@ -1,8 +1,10 @@ """Tests for certbot_dns_ovh._internal.dns_ovh.""" +import sys import unittest from unittest import mock +import pytest from requests.exceptions import HTTPError from certbot.compat import os @@ -59,4 +61,4 @@ class OVHLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexico if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py index 2c52486e2..d48307750 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py @@ -138,7 +138,7 @@ class _RFC2136Client: except Exception as e: raise errors.PluginError('Encountered error adding TXT record: {0}' .format(e)) - rcode = response.rcode() # type: ignore[attr-defined] + rcode = response.rcode() if rcode == dns.rcode.NOERROR: logger.debug('Successfully added TXT record %s', record_name) @@ -173,7 +173,7 @@ class _RFC2136Client: except Exception as e: raise errors.PluginError('Encountered error deleting TXT record: {0}' .format(e)) - rcode = response.rcode() # type: ignore[attr-defined] + rcode = response.rcode() if rcode == dns.rcode.NOERROR: logger.debug('Successfully deleted TXT record %s', record_name) @@ -217,7 +217,7 @@ class _RFC2136Client: # Turn off Recursion Desired bit in query request.flags ^= dns.flags.RD # Use our TSIG keyring - request.use_tsig(self.keyring, algorithm=self.algorithm) # type: ignore[attr-defined] + request.use_tsig(self.keyring, algorithm=self.algorithm) try: try: @@ -225,11 +225,11 @@ class _RFC2136Client: except (OSError, dns.exception.Timeout) as e: logger.debug('TCP query failed, fallback to UDP: %s', e) response = dns.query.udp(request, self.server, self._default_timeout, self.port) - rcode = response.rcode() # type: ignore[attr-defined] + rcode = response.rcode() # Authoritative Answer bit should be set if (rcode == dns.rcode.NOERROR - and response.get_rrset(response.answer, # type: ignore[attr-defined] + and response.get_rrset(response.answer, domain, dns.rdataclass.IN, dns.rdatatype.SOA) and response.flags & dns.flags.AA): logger.debug('Received authoritative SOA response for %s', domain_name) diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index c3fdeb864..57f1466e2 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'dnspython>=1.15.0', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-rfc2136', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-rfc2136/tests/dns_rfc2136_test.py b/certbot-dns-rfc2136/tests/dns_rfc2136_test.py index 1f91d3cb6..3a82f1b65 100644 --- a/certbot-dns-rfc2136/tests/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/tests/dns_rfc2136_test.py @@ -1,11 +1,13 @@ """Tests for certbot_dns_rfc2136._internal.dns_rfc2136.""" +import sys import unittest from unittest import mock import dns.flags import dns.rcode import dns.tsig +import pytest from certbot import errors from certbot.compat import os @@ -45,7 +47,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic self.auth.perform([self.achall]) expected = [mock.call.add_txt_record('_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) + assert expected == self.mock_client.mock_calls def test_cleanup(self): # _attempt_cleanup | pylint: disable=protected-access @@ -53,16 +55,15 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic self.auth.cleanup([self.achall]) expected = [mock.call.del_txt_record('_acme-challenge.'+DOMAIN, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) + assert expected == self.mock_client.mock_calls def test_invalid_algorithm_raises(self): config = VALID_CONFIG.copy() config["rfc2136_algorithm"] = "INVALID" dns_test_common.write(config, self.config.rfc2136_credentials) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) @test_util.patch_display_util() def test_valid_algorithm_passes(self, unused_mock_get_utility): @@ -77,9 +78,8 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic config["rfc2136_server"] = "example.com" dns_test_common.write(config, self.config.rfc2136_credentials) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) @test_util.patch_display_util() def test_valid_server_passes(self, unused_mock_get_utility): @@ -111,7 +111,7 @@ class RFC2136ClientTest(unittest.TestCase): self.rfc2136_client.add_txt_record("bar", "baz", 42) query_mock.assert_called_with(mock.ANY, SERVER, TIMEOUT, PORT) - self.assertIn('bar. 42 IN TXT "baz"', str(query_mock.call_args[0][0])) + assert 'bar. 42 IN TXT "baz"' in str(query_mock.call_args[0][0]) @mock.patch("dns.query.tcp") def test_add_txt_record_wraps_errors(self, query_mock): @@ -119,10 +119,8 @@ class RFC2136ClientTest(unittest.TestCase): # _find_domain | pylint: disable=protected-access self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - self.assertRaises( - errors.PluginError, - self.rfc2136_client.add_txt_record, - "bar", "baz", 42) + with pytest.raises(errors.PluginError): + self.rfc2136_client.add_txt_record("bar", "baz", 42) @mock.patch("dns.query.tcp") def test_add_txt_record_server_error(self, query_mock): @@ -130,10 +128,8 @@ class RFC2136ClientTest(unittest.TestCase): # _find_domain | pylint: disable=protected-access self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - self.assertRaises( - errors.PluginError, - self.rfc2136_client.add_txt_record, - "bar", "baz", 42) + with pytest.raises(errors.PluginError): + self.rfc2136_client.add_txt_record("bar", "baz", 42) @mock.patch("dns.query.tcp") def test_del_txt_record(self, query_mock): @@ -144,7 +140,7 @@ class RFC2136ClientTest(unittest.TestCase): self.rfc2136_client.del_txt_record("bar", "baz") query_mock.assert_called_with(mock.ANY, SERVER, TIMEOUT, PORT) - self.assertIn('bar. 0 NONE TXT "baz"', str(query_mock.call_args[0][0])) + assert 'bar. 0 NONE TXT "baz"' in str(query_mock.call_args[0][0]) @mock.patch("dns.query.tcp") def test_del_txt_record_wraps_errors(self, query_mock): @@ -152,10 +148,8 @@ class RFC2136ClientTest(unittest.TestCase): # _find_domain | pylint: disable=protected-access self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - self.assertRaises( - errors.PluginError, - self.rfc2136_client.del_txt_record, - "bar", "baz") + with pytest.raises(errors.PluginError): + self.rfc2136_client.del_txt_record("bar", "baz") @mock.patch("dns.query.tcp") def test_del_txt_record_server_error(self, query_mock): @@ -163,10 +157,8 @@ class RFC2136ClientTest(unittest.TestCase): # _find_domain | pylint: disable=protected-access self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - self.assertRaises( - errors.PluginError, - self.rfc2136_client.del_txt_record, - "bar", "baz") + with pytest.raises(errors.PluginError): + self.rfc2136_client.del_txt_record("bar", "baz") def test_find_domain(self): # _query_soa | pylint: disable=protected-access @@ -175,17 +167,14 @@ class RFC2136ClientTest(unittest.TestCase): # _find_domain | pylint: disable=protected-access domain = self.rfc2136_client._find_domain('foo.bar.'+DOMAIN) - self.assertEqual(domain, DOMAIN) + assert domain == DOMAIN def test_find_domain_wraps_errors(self): # _query_soa | pylint: disable=protected-access self.rfc2136_client._query_soa = mock.MagicMock(return_value=False) - self.assertRaises( - errors.PluginError, - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain, - 'foo.bar.'+DOMAIN) + with pytest.raises(errors.PluginError): + self.rfc2136_client._find_domain('foo.bar.'+DOMAIN) @mock.patch("dns.query.tcp") def test_query_soa_found(self, query_mock): @@ -196,7 +185,7 @@ class RFC2136ClientTest(unittest.TestCase): result = self.rfc2136_client._query_soa(DOMAIN) query_mock.assert_called_with(mock.ANY, SERVER, TIMEOUT, PORT) - self.assertTrue(result) + assert result @mock.patch("dns.query.tcp") def test_query_soa_not_found(self, query_mock): @@ -206,17 +195,14 @@ class RFC2136ClientTest(unittest.TestCase): result = self.rfc2136_client._query_soa(DOMAIN) query_mock.assert_called_with(mock.ANY, SERVER, TIMEOUT, PORT) - self.assertFalse(result) + assert not result @mock.patch("dns.query.tcp") def test_query_soa_wraps_errors(self, query_mock): query_mock.side_effect = Exception - self.assertRaises( - errors.PluginError, - # _query_soa | pylint: disable=protected-access - self.rfc2136_client._query_soa, - DOMAIN) + with pytest.raises(errors.PluginError): + self.rfc2136_client._query_soa(DOMAIN) @mock.patch("dns.query.udp") @mock.patch("dns.query.tcp") @@ -230,8 +216,8 @@ class RFC2136ClientTest(unittest.TestCase): tcp_mock.assert_called_with(mock.ANY, SERVER, TIMEOUT, PORT) udp_mock.assert_called_with(mock.ANY, SERVER, TIMEOUT, PORT) - self.assertTrue(result) + assert result if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 651413516..0d9ece96e 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'boto3>=1.15.15', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-route53', version=version, @@ -65,6 +69,7 @@ setup( keywords=['certbot', 'route53', 'aws'], extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-route53/tests/dns_route53_test.py b/certbot-dns-route53/tests/dns_route53_test.py index bdc70e048..6a68b0b1e 100644 --- a/certbot-dns-route53/tests/dns_route53_test.py +++ b/certbot-dns-route53/tests/dns_route53_test.py @@ -1,10 +1,12 @@ """Tests for certbot_dns_route53._internal.dns_route53.Authenticator""" +import sys import unittest from unittest import mock from botocore.exceptions import ClientError from botocore.exceptions import NoCredentialsError +import pytest from certbot import errors from certbot.compat import os @@ -42,22 +44,20 @@ class AuthenticatorTest(unittest.TestCase, dns_test_common.BaseAuthenticatorTest self.auth._change_txt_record.assert_called_once_with("UPSERT", '_acme-challenge.' + DOMAIN, mock.ANY) - self.assertEqual(self.auth._wait_for_change.call_count, 1) + assert self.auth._wait_for_change.call_count == 1 def test_perform_no_credentials_error(self): self.auth._change_txt_record = mock.MagicMock(side_effect=NoCredentialsError) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) def test_perform_client_error(self): self.auth._change_txt_record = mock.MagicMock( side_effect=ClientError({"Error": {"Code": "foo"}}, "bar")) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) def test_cleanup(self): self.auth._attempt_cleanup = True @@ -149,7 +149,7 @@ class ClientTest(unittest.TestCase): ] result = self.client._find_zone_id_for_domain("foo.example.com") - self.assertEqual(result, "EXAMPLE") + assert result == "EXAMPLE" def test_find_zone_id_for_domain_pagination(self): self.client.r53.get_paginator = mock.MagicMock() @@ -169,15 +169,14 @@ class ClientTest(unittest.TestCase): ] result = self.client._find_zone_id_for_domain("foo.example.com") - self.assertEqual(result, "FOO") + assert result == "FOO" def test_find_zone_id_for_domain_no_results(self): self.client.r53.get_paginator = mock.MagicMock() self.client.r53.get_paginator().paginate.return_value = [] - self.assertRaises(errors.PluginError, - self.client._find_zone_id_for_domain, - "foo.example.com") + with pytest.raises(errors.PluginError): + self.client._find_zone_id_for_domain("foo.example.com") def test_find_zone_id_for_domain_no_correct_results(self): self.client.r53.get_paginator = mock.MagicMock() @@ -190,9 +189,8 @@ class ClientTest(unittest.TestCase): }, ] - self.assertRaises(errors.PluginError, - self.client._find_zone_id_for_domain, - "foo.example.com") + with pytest.raises(errors.PluginError): + self.client._find_zone_id_for_domain("foo.example.com") def test_change_txt_record(self): self.client._find_zone_id_for_domain = mock.MagicMock() @@ -202,7 +200,7 @@ class ClientTest(unittest.TestCase): self.client._change_txt_record("FOO", DOMAIN, "foo") call_count = self.client.r53.change_resource_record_sets.call_count - self.assertEqual(call_count, 1) + assert call_count == 1 def test_change_txt_record_delete(self): self.client._find_zone_id_for_domain = mock.MagicMock() @@ -216,13 +214,12 @@ class ClientTest(unittest.TestCase): self.client._change_txt_record("DELETE", DOMAIN, validation) call_count = self.client.r53.change_resource_record_sets.call_count - self.assertEqual(call_count, 1) + assert call_count == 1 call_args = self.client.r53.change_resource_record_sets.call_args_list[0][1] call_args_batch = call_args["ChangeBatch"]["Changes"][0] - self.assertEqual(call_args_batch["Action"], "DELETE") - self.assertEqual( - call_args_batch["ResourceRecordSet"]["ResourceRecords"], - [validation_record]) + assert call_args_batch["Action"] == "DELETE" + assert call_args_batch["ResourceRecordSet"]["ResourceRecords"] == \ + [validation_record] def test_change_txt_record_multirecord(self): self.client._find_zone_id_for_domain = mock.MagicMock() @@ -239,12 +236,11 @@ class ClientTest(unittest.TestCase): call_count = self.client.r53.change_resource_record_sets.call_count call_args = self.client.r53.change_resource_record_sets.call_args_list[0][1] call_args_batch = call_args["ChangeBatch"]["Changes"][0] - self.assertEqual(call_args_batch["Action"], "UPSERT") - self.assertEqual( - call_args_batch["ResourceRecordSet"]["ResourceRecords"], - [{"Value": "\"pre-existing-value-two\""}]) + assert call_args_batch["Action"] == "UPSERT" + assert call_args_batch["ResourceRecordSet"]["ResourceRecords"] == \ + [{"Value": "\"pre-existing-value-two\""}] - self.assertEqual(call_count, 1) + assert call_count == 1 def test_wait_for_change(self): self.client.r53.get_change = mock.MagicMock( @@ -253,8 +249,8 @@ class ClientTest(unittest.TestCase): self.client._wait_for_change(1) - self.assertTrue(self.client.r53.get_change.called) + assert self.client.r53.get_change.called if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 235ab8d10..272ae827e 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ 'dns-lexicon>=3.2.1', @@ -30,6 +30,10 @@ docs_extras = [ 'sphinx_rtd_theme', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-dns-sakuracloud', version=version, @@ -65,6 +69,7 @@ setup( install_requires=install_requires, extras_require={ 'docs': docs_extras, + 'test': test_extras, }, entry_points={ 'certbot.plugins': [ diff --git a/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py b/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py index a1abf7b78..2e8fccb55 100644 --- a/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py +++ b/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py @@ -1,8 +1,10 @@ """Tests for certbot_dns_sakuracloud._internal.dns_sakuracloud.""" +import sys import unittest from unittest import mock +import pytest from requests.exceptions import HTTPError from certbot.compat import os @@ -53,4 +55,4 @@ class SakuraCloudLexiconClientTest(unittest.TestCase, if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index b91b11a53..ec2806ce3 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -20,12 +20,6 @@ from typing import Tuple from typing import Type from typing import Union -from certbot_nginx._internal import constants -from certbot_nginx._internal import display_ops -from certbot_nginx._internal import http_01 -from certbot_nginx._internal import nginxparser -from certbot_nginx._internal import obj -from certbot_nginx._internal import parser import OpenSSL import pkg_resources @@ -38,6 +32,12 @@ from certbot import util from certbot.compat import os from certbot.display import util as display_util from certbot.plugins import common +from certbot_nginx._internal import constants +from certbot_nginx._internal import display_ops +from certbot_nginx._internal import http_01 +from certbot_nginx._internal import nginxparser +from certbot_nginx._internal import obj +from certbot_nginx._internal import parser NAME_RANK = 0 START_WILDCARD_RANK = 1 @@ -693,6 +693,7 @@ class NginxConfigurator(common.Configurator): le_key = crypto_util.generate_key( key_size=1024, key_dir=tmp_dir, keyname="key.pem", strict_permissions=self.config.strict_permissions) + assert le_key.file is not None key = OpenSSL.crypto.load_privatekey( OpenSSL.crypto.FILETYPE_PEM, le_key.pem) cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()]) @@ -1060,8 +1061,8 @@ class NginxConfigurator(common.Configurator): product_name, product_version = version_matches[0] if product_name != 'nginx': - logger.warning("nginx derivative %s is not officially supported by " - "Certbot.", product_name) + logger.warning("NGINX derivative %s is not officially supported by" + " certbot", product_name) nginx_version = tuple(int(i) for i in product_version.split(".")) diff --git a/certbot-nginx/certbot_nginx/_internal/display_ops.py b/certbot-nginx/certbot_nginx/_internal/display_ops.py index 89483c94a..3a0fa67ff 100644 --- a/certbot-nginx/certbot_nginx/_internal/display_ops.py +++ b/certbot-nginx/certbot_nginx/_internal/display_ops.py @@ -4,9 +4,8 @@ from typing import Iterable from typing import List from typing import Optional -from certbot_nginx._internal.obj import VirtualHost - from certbot.display import util as display_util +from certbot_nginx._internal.obj import VirtualHost logger = logging.getLogger(__name__) diff --git a/certbot-nginx/certbot_nginx/_internal/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py index 9b086d429..8a6afb0ea 100644 --- a/certbot-nginx/certbot_nginx/_internal/http_01.py +++ b/certbot-nginx/certbot_nginx/_internal/http_01.py @@ -7,15 +7,14 @@ from typing import List from typing import Optional from typing import TYPE_CHECKING -from certbot_nginx._internal import nginxparser -from certbot_nginx._internal.obj import Addr - from acme import challenges from acme.challenges import KeyAuthorizationChallengeResponse from certbot import errors from certbot.achallenges import KeyAuthorizationAnnotatedChallenge from certbot.compat import os from certbot.plugins import common +from certbot_nginx._internal import nginxparser +from certbot_nginx._internal.obj import Addr if TYPE_CHECKING: from certbot_nginx._internal.configurator import NginxConfigurator diff --git a/certbot-nginx/certbot_nginx/_internal/nginxparser.py b/certbot-nginx/certbot_nginx/_internal/nginxparser.py index 99955447a..1c74cd367 100644 --- a/certbot-nginx/certbot_nginx/_internal/nginxparser.py +++ b/certbot-nginx/certbot_nginx/_internal/nginxparser.py @@ -33,18 +33,6 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) -class UnsupportedDirectiveException(RuntimeError): - """Exception when encountering an nginx directive which is not supported - by this parser.""" - - directive_name: str - line_no: int - - def __init__(self, directive_name: str, line_no: int) -> None: - self.directive_name = directive_name - self.line_no = line_no - - class RawNginxParser: # pylint: disable=pointless-statement """A class that parses nginx configuration with pyparsing.""" @@ -86,7 +74,6 @@ class RawNginxParser: def __init__(self, source: str) -> None: self.source = source - self.whitespace_token_group.addParseAction(self._check_disallowed_directive) def parse(self) -> ParseResults: """Returns the parsed tree.""" @@ -96,12 +83,6 @@ class RawNginxParser: """Returns the parsed tree as a list.""" return self.parse().asList() - def _check_disallowed_directive(self, _source: str, line: int, results: ParseResults) -> None: - # *_by_lua_block might be first or second result, due to optional leading whitespace - toks = [t for t in results[0:2] if isinstance(t, str) and t.endswith("_by_lua_block")] - if toks: - raise UnsupportedDirectiveException(toks[0], line) - class RawNginxDumper: """A class that dumps nginx configuration from the provided tree.""" diff --git a/certbot-nginx/certbot_nginx/_internal/parser.py b/certbot-nginx/certbot_nginx/_internal/parser.py index bc1643426..9d24ce242 100644 --- a/certbot-nginx/certbot_nginx/_internal/parser.py +++ b/certbot-nginx/certbot_nginx/_internal/parser.py @@ -18,13 +18,13 @@ from typing import Set from typing import Tuple from typing import Union -from certbot_nginx._internal import nginxparser -from certbot_nginx._internal import obj -from certbot_nginx._internal.nginxparser import UnspacedList import pyparsing from certbot import errors from certbot.compat import os +from certbot_nginx._internal import nginxparser +from certbot_nginx._internal import obj +from certbot_nginx._internal.nginxparser import UnspacedList logger = logging.getLogger(__name__) @@ -223,12 +223,6 @@ class NginxParser: "supported.", item) except pyparsing.ParseException as err: logger.warning("Could not parse file: %s due to %s", item, err) - except nginxparser.UnsupportedDirectiveException as e: - logger.warning( - "%s:%d contained the '%s' directive, which is not supported by Certbot. The " - "file has been ignored, which may prevent Certbot from functioning properly. " - "Consider using the --webroot plugin and manually installing the certificate.", - item, e.line_no, e.directive_name) return trees def _find_config_root(self) -> str: diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 77dc3d257..8adfd2947 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -1,7 +1,7 @@ from setuptools import find_packages from setuptools import setup -version = '2.3.0.dev0' +version = '2.4.0.dev0' install_requires = [ # We specify the minimum acme and certbot version as the current plugin @@ -14,6 +14,10 @@ install_requires = [ 'setuptools>=41.6.0', ] +test_extras = [ + 'pytest', +] + setup( name='certbot-nginx', version=version, @@ -47,6 +51,9 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, + extras_require={ + 'test': test_extras, + }, entry_points={ 'certbot.plugins': [ 'nginx = certbot_nginx._internal.configurator:NginxConfigurator', diff --git a/certbot-nginx/tests/configurator_test.py b/certbot-nginx/tests/configurator_test.py index 916dfe3f5..9698b4108 100644 --- a/certbot-nginx/tests/configurator_test.py +++ b/certbot-nginx/tests/configurator_test.py @@ -1,8 +1,10 @@ """Test for certbot_nginx._internal.configurator.""" +import sys import unittest from unittest import mock import OpenSSL +import pytest from acme import challenges from acme import messages @@ -34,12 +36,12 @@ class NginxConfiguratorTest(util.NginxTest): @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") def test_prepare_no_install(self, mock_exe_exists): mock_exe_exists.return_value = False - self.assertRaises( - errors.NoInstallationError, self.config.prepare) + with pytest.raises(errors.NoInstallationError): + self.config.prepare() def test_prepare(self): - self.assertEqual((1, 6, 2), self.config.version) - self.assertEqual(14, len(self.config.parser.parsed)) + assert (1, 6, 2) == self.config.version + assert 14 == len(self.config.parser.parsed) @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") @mock.patch("certbot_nginx._internal.configurator.subprocess.run") @@ -58,7 +60,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.version = None self.config.config_test = mock.Mock() self.config.prepare() - self.assertEqual((1, 6, 2), self.config.version) + assert (1, 6, 2) == self.config.version def test_prepare_locked(self): server_root = self.config.conf("server-root") @@ -75,8 +77,8 @@ class NginxConfiguratorTest(util.NginxTest): self.config.prepare() except errors.PluginError as err: err_msg = str(err) - self.assertIn("lock", err_msg) - self.assertIn(self.config.conf("server-root"), err_msg) + assert "lock" in err_msg + assert self.config.conf("server-root") in err_msg else: # pragma: no cover self.fail("Exception wasn't raised!") @@ -86,23 +88,23 @@ class NginxConfiguratorTest(util.NginxTest): mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.net', [], []) mock_gethostname.return_value = ('example.net') names = self.config.get_all_names() - self.assertEqual(names, { + assert names == { "155.225.50.69.nephoscale.net", "www.example.org", "another.alias", "migration.com", "summer.com", "geese.com", "sslon.com", "globalssl.com", "globalsslsetssl.com", "ipv6.com", "ipv6ssl.com", - "headers.com", "example.net", "ssl.both.com"}) + "headers.com", "example.net", "ssl.both.com"} def test_supported_enhancements(self): - self.assertEqual(['redirect', 'ensure-http-header', 'staple-ocsp'], - self.config.supported_enhancements()) + assert ['redirect', 'ensure-http-header', 'staple-ocsp'] == \ + self.config.supported_enhancements() def test_enhance(self): - self.assertRaises( - errors.PluginError, self.config.enhance, 'myhost', 'unknown_enhancement') + with pytest.raises(errors.PluginError): + self.config.enhance('myhost', 'unknown_enhancement') def test_get_chall_pref(self): - self.assertEqual([challenges.HTTP01], - self.config.get_chall_pref('myhost')) + assert [challenges.HTTP01] == \ + self.config.get_chall_pref('myhost') def test_save(self): filep = self.config.parser.abs_path('sites-enabled/example.com') @@ -117,14 +119,14 @@ class NginxConfiguratorTest(util.NginxTest): # pylint: disable=protected-access parsed = self.config.parser._parse_files(filep, override=True) - self.assertEqual([[['server'], + assert [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], ['listen', '5001', 'ssl'], - ['#', parser.COMMENT]]]], - parsed[0]) + ['#', parser.COMMENT]]]] == \ + parsed[0] def test_choose_vhosts_alias(self): self._test_choose_vhosts_common('alias', 'server_conf') @@ -174,14 +176,13 @@ class NginxConfiguratorTest(util.NginxTest): vhost = self.config.choose_vhosts(name)[0] path = os.path.relpath(vhost.filep, self.temp_dir) - self.assertEqual(conf_names[conf], vhost.names) - self.assertEqual(conf_path[name], path) + assert conf_names[conf] == vhost.names + assert conf_path[name] == path # IPv6 specific checks if name == "ipv6.com": - self.assertTrue(vhost.ipv6_enabled()) + assert vhost.ipv6_enabled() # Make sure that we have SSL enabled also for IPv6 addr - self.assertTrue( - any(True for x in vhost.addrs if x.ssl and x.ipv6)) + assert any(True for x in vhost.addrs if x.ssl and x.ipv6) def test_choose_vhosts_bad(self): bad_results = ['www.foo.com', 'example', 't.www.bar.co', @@ -189,14 +190,14 @@ class NginxConfiguratorTest(util.NginxTest): for name in bad_results: with self.subTest(name=name): - self.assertRaises(errors.MisconfigurationError, - self.config.choose_vhosts, name) + with pytest.raises(errors.MisconfigurationError): + self.config.choose_vhosts(name) def test_ipv6only(self): # ipv6_info: (ipv6_active, ipv6only_present) - self.assertEqual((True, False), self.config.ipv6_info("80")) + assert (True, False) == self.config.ipv6_info("80") # Port 443 has ipv6only=on because of ipv6ssl.com vhost - self.assertEqual((True, True), self.config.ipv6_info("443")) + assert (True, True) == self.config.ipv6_info("443") def test_ipv6only_detection(self): self.config.version = (1, 3, 1) @@ -209,15 +210,15 @@ class NginxConfiguratorTest(util.NginxTest): "example/fullchain.pem") for addr in self.config.choose_vhosts("ipv6.com")[0].addrs: - self.assertFalse(addr.ipv6only) + assert not addr.ipv6only def test_more_info(self): - self.assertIn('nginx.conf', self.config.more_info()) + assert 'nginx.conf' in self.config.more_info() def test_deploy_cert_requires_fullchain_path(self): self.config.version = (1, 3, 1) - self.assertRaises(errors.PluginError, self.config.deploy_cert, - "www.example.com", + with pytest.raises(errors.PluginError): + self.config.deploy_cert("www.example.com", "example/cert.pem", "example/key.pem", "example/chain.pem", @@ -226,10 +227,8 @@ class NginxConfiguratorTest(util.NginxTest): @mock.patch('certbot_nginx._internal.parser.NginxParser.update_or_add_server_directives') def test_deploy_cert_raise_on_add_error(self, mock_update_or_add_server_directives): mock_update_or_add_server_directives.side_effect = errors.MisconfigurationError() - self.assertRaises( - errors.PluginError, - self.config.deploy_cert, - "migration.com", + with pytest.raises(errors.PluginError): + self.config.deploy_cert("migration.com", "example/cert.pem", "example/key.pem", "example/chain.pem", @@ -262,7 +261,7 @@ class NginxConfiguratorTest(util.NginxTest): parsed_server_conf = util.filter_comments(self.config.parser.parsed[server_conf]) parsed_nginx_conf = util.filter_comments(self.config.parser.parsed[nginx_conf]) - self.assertEqual([[['server'], + assert [[['server'], [ ['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], @@ -274,11 +273,11 @@ class NginxConfiguratorTest(util.NginxTest): ['ssl_certificate_key', 'example/key.pem'], ['include', self.config.mod_ssl_conf], ['ssl_dhparam', self.config.ssl_dhparams], - ]]], - parsed_example_conf) - self.assertEqual([['server_name', 'somename', 'alias', 'another.alias']], - parsed_server_conf) - self.assertTrue(util.contains_at_depth( + ]]] == \ + parsed_example_conf + assert [['server_name', 'somename', 'alias', 'another.alias']] == \ + parsed_server_conf + assert util.contains_at_depth( parsed_nginx_conf, [['server'], [ @@ -294,7 +293,7 @@ class NginxConfiguratorTest(util.NginxTest): ['include', self.config.mod_ssl_conf], ['ssl_dhparam', self.config.ssl_dhparams], ]], - 2)) + 2) def test_deploy_cert_add_explicit_listen(self): migration_conf = self.config.parser.abs_path('sites-enabled/migration.com') @@ -307,7 +306,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.save() self.config.parser.load() parsed_migration_conf = util.filter_comments(self.config.parser.parsed[migration_conf]) - self.assertEqual([['server'], + assert [['server'], [ ['server_name', 'migration.com'], ['server_name', 'summer.com'], @@ -318,8 +317,8 @@ class NginxConfiguratorTest(util.NginxTest): ['ssl_certificate_key', 'summer/key.pem'], ['include', self.config.mod_ssl_conf], ['ssl_dhparam', self.config.ssl_dhparams], - ]], - parsed_migration_conf[0]) + ]] == \ + parsed_migration_conf[0] @mock.patch("certbot_nginx._internal.configurator.http_01.NginxHttp01.perform") @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.restart") @@ -341,13 +340,13 @@ class NginxConfiguratorTest(util.NginxTest): mock_http_perform.return_value = expected[:] responses = self.config.perform([achall]) - self.assertEqual(mock_http_perform.call_count, 1) - self.assertEqual(responses, expected) + assert mock_http_perform.call_count == 1 + assert responses == expected self.config.cleanup([achall]) - self.assertEqual(0, self.config._chall_out) # pylint: disable=protected-access - self.assertEqual(mock_revert.call_count, 1) - self.assertEqual(mock_restart.call_count, 2) + assert 0 == self.config._chall_out # pylint: disable=protected-access + assert mock_revert.call_count == 1 + assert mock_restart.call_count == 2 @mock.patch("certbot_nginx._internal.configurator.subprocess.run") def test_get_version(self, mock_run): @@ -359,7 +358,7 @@ class NginxConfiguratorTest(util.NginxTest): "TLS SNI support enabled", "configure arguments: --prefix=/usr/local/Cellar/" "nginx/1.6.2 --with-http_ssl_module"]) - self.assertEqual(self.config.get_version(), (1, 4, 2)) + assert self.config.get_version() == (1, 4, 2) mock_run.return_value.stdout = "" mock_run.return_value.stderr = "\n".join( @@ -368,7 +367,7 @@ class NginxConfiguratorTest(util.NginxTest): " (based on LLVM 3.5svn)", "TLS SNI support enabled", "configure arguments: --with-http_ssl_module"]) - self.assertEqual(self.config.get_version(), (0, 9)) + assert self.config.get_version() == (0, 9) mock_run.return_value.stdout = "" mock_run.return_value.stderr = "\n".join( @@ -377,13 +376,15 @@ class NginxConfiguratorTest(util.NginxTest): " (based on LLVM 3.5svn)", "TLS SNI support enabled", "configure arguments: --with-http_ssl_module"]) - self.assertRaises(errors.PluginError, self.config.get_version) + with pytest.raises(errors.PluginError): + self.config.get_version() mock_run.return_value.stdout = "" mock_run.return_value.stderr = "\n".join( ["nginx version: nginx/1.4.2", "TLS SNI support enabled"]) - self.assertRaises(errors.PluginError, self.config.get_version) + with pytest.raises(errors.PluginError): + self.config.get_version() mock_run.return_value.stdout = "" mock_run.return_value.stderr = "\n".join( @@ -391,7 +392,8 @@ class NginxConfiguratorTest(util.NginxTest): "built by clang 6.0 (clang-600.0.56)" " (based on LLVM 3.5svn)", "configure arguments: --with-http_ssl_module"]) - self.assertRaises(errors.PluginError, self.config.get_version) + with pytest.raises(errors.PluginError): + self.config.get_version() mock_run.return_value.stdout = "" mock_run.return_value.stderr = "\n".join( @@ -400,10 +402,12 @@ class NginxConfiguratorTest(util.NginxTest): " (based on LLVM 3.5svn)", "TLS SNI support enabled", "configure arguments: --with-http_ssl_module"]) - self.assertRaises(errors.NotSupportedError, self.config.get_version) + with pytest.raises(errors.NotSupportedError): + self.config.get_version() mock_run.side_effect = OSError("Can't find program") - self.assertRaises(errors.PluginError, self.config.get_version) + with pytest.raises(errors.PluginError): + self.config.get_version() @mock.patch("certbot_nginx._internal.configurator.subprocess.run") def test_get_openssl_version(self, mock_run): @@ -416,7 +420,7 @@ class NginxConfiguratorTest(util.NginxTest): TLS SNI support enabled configure arguments: """ - self.assertEqual(self.config._get_openssl_version(), "1.0.2g") + assert self.config._get_openssl_version() == "1.0.2g" mock_run.return_value.stdout = "" mock_run.return_value.stderr = """ @@ -426,7 +430,7 @@ class NginxConfiguratorTest(util.NginxTest): TLS SNI support enabled configure arguments: """ - self.assertEqual(self.config._get_openssl_version(), "1.0.2-beta1") + assert self.config._get_openssl_version() == "1.0.2-beta1" mock_run.return_value.stdout = "" mock_run.return_value.stderr = """ @@ -436,7 +440,7 @@ class NginxConfiguratorTest(util.NginxTest): TLS SNI support enabled configure arguments: """ - self.assertEqual(self.config._get_openssl_version(), "1.0.2") + assert self.config._get_openssl_version() == "1.0.2" mock_run.return_value.stdout = "" mock_run.return_value.stderr = """ @@ -446,7 +450,7 @@ class NginxConfiguratorTest(util.NginxTest): TLS SNI support enabled configure arguments: """ - self.assertEqual(self.config._get_openssl_version(), "1.0.2a") + assert self.config._get_openssl_version() == "1.0.2a" mock_run.return_value.stdout = "" mock_run.return_value.stderr = """ @@ -456,7 +460,7 @@ class NginxConfiguratorTest(util.NginxTest): TLS SNI support enabled configure arguments: """ - self.assertEqual(self.config._get_openssl_version(), "") + assert self.config._get_openssl_version() == "" mock_run.return_value.stdout = "" mock_run.return_value.stderr = """ @@ -465,7 +469,7 @@ class NginxConfiguratorTest(util.NginxTest): TLS SNI support enabled configure arguments: """ - self.assertEqual(self.config._get_openssl_version(), "") + assert self.config._get_openssl_version() == "" @mock.patch("certbot_nginx._internal.configurator.subprocess.run") @mock.patch("certbot_nginx._internal.configurator.time") @@ -475,7 +479,7 @@ class NginxConfiguratorTest(util.NginxTest): mocked.stderr = '' mocked.returncode = 0 self.config.restart() - self.assertEqual(mock_run.call_count, 1) + assert mock_run.call_count == 1 mock_time.sleep.assert_called_once_with(0.1234) @mock.patch("certbot_nginx._internal.configurator.subprocess.run") @@ -485,19 +489,22 @@ class NginxConfiguratorTest(util.NginxTest): mocked.stdout = '' mocked.stderr = '' mocked.returncode = 1 - self.assertRaises(errors.MisconfigurationError, self.config.restart) - self.assertEqual(mock_run.call_count, 2) + with pytest.raises(errors.MisconfigurationError): + self.config.restart() + assert mock_run.call_count == 2 mock_log_debug.assert_called_once_with("nginx reload failed:\n%s", "") @mock.patch("certbot_nginx._internal.configurator.subprocess.run") def test_no_nginx_start(self, mock_run): mock_run.side_effect = OSError("Can't find program") - self.assertRaises(errors.MisconfigurationError, self.config.restart) + with pytest.raises(errors.MisconfigurationError): + self.config.restart() @mock.patch("certbot.util.run_script") def test_config_test_bad_process(self, mock_run_script): mock_run_script.side_effect = errors.SubprocessError - self.assertRaises(errors.MisconfigurationError, self.config.config_test) + with pytest.raises(errors.MisconfigurationError): + self.config.config_test() @mock.patch("certbot.util.run_script") def test_config_test(self, _): @@ -506,28 +513,32 @@ class NginxConfiguratorTest(util.NginxTest): @mock.patch("certbot.reverter.Reverter.recovery_routine") def test_recovery_routine_throws_error_from_reverter(self, mock_recovery_routine): mock_recovery_routine.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.recovery_routine) + with pytest.raises(errors.PluginError): + self.config.recovery_routine() @mock.patch("certbot.reverter.Reverter.rollback_checkpoints") def test_rollback_checkpoints_throws_error_from_reverter(self, mock_rollback_checkpoints): mock_rollback_checkpoints.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) + with pytest.raises(errors.PluginError): + self.config.rollback_checkpoints() @mock.patch("certbot.reverter.Reverter.revert_temporary_config") def test_revert_challenge_config_throws_error_from_reverter(self, mock_revert_temporary_config): mock_revert_temporary_config.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.revert_challenge_config) + with pytest.raises(errors.PluginError): + self.config.revert_challenge_config() @mock.patch("certbot.reverter.Reverter.add_to_checkpoint") def test_save_throws_error_from_reverter(self, mock_add_to_checkpoint): mock_add_to_checkpoint.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.save) + with pytest.raises(errors.PluginError): + self.config.save() def test_get_snakeoil_paths(self): # pylint: disable=protected-access cert, key = self.config._get_snakeoil_paths() - self.assertTrue(os.path.exists(cert)) - self.assertTrue(os.path.exists(key)) + assert os.path.exists(cert) + assert os.path.exists(key) with open(cert) as cert_file: OpenSSL.crypto.load_certificate( OpenSSL.crypto.FILETYPE_PEM, cert_file.read()) @@ -544,7 +555,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.enhance("www.example.com", "redirect") generated_conf = self.config.parser.parsed[example_conf] - self.assertIs(util.contains_at_depth(generated_conf, expected, 2), True) + assert util.contains_at_depth(generated_conf, expected, 2) is True # Test that we successfully add a redirect when there is # no listen directive @@ -554,7 +565,7 @@ class NginxConfiguratorTest(util.NginxTest): expected = UnspacedList(_redirect_block_for_domain("migration.com"))[0] generated_conf = self.config.parser.parsed[migration_conf] - self.assertIs(util.contains_at_depth(generated_conf, expected, 2), True) + assert util.contains_at_depth(generated_conf, expected, 2) is True def test_split_for_redirect(self): example_conf = self.config.parser.abs_path('sites-enabled/example.com') @@ -566,8 +577,7 @@ class NginxConfiguratorTest(util.NginxTest): "example/fullchain.pem") self.config.enhance("www.example.com", "redirect") generated_conf = self.config.parser.parsed[example_conf] - self.assertEqual( - [[['server'], [ + assert [[['server'], [ ['server_name', '.example.com'], ['server_name', 'example.*'], [], ['listen', '5001', 'ssl'], ['#', ' managed by Certbot'], @@ -584,8 +594,8 @@ class NginxConfiguratorTest(util.NginxTest): ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], - ['return', '404'], ['#', ' managed by Certbot'], [], [], []]]], - generated_conf) + ['return', '404'], ['#', ' managed by Certbot'], [], [], []]]] == \ + generated_conf def test_split_for_headers(self): example_conf = self.config.parser.abs_path('sites-enabled/example.com') @@ -597,8 +607,7 @@ class NginxConfiguratorTest(util.NginxTest): "example/fullchain.pem") self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security") generated_conf = self.config.parser.parsed[example_conf] - self.assertEqual( - [[['server'], [ + assert [[['server'], [ ['server_name', '.example.com'], ['server_name', 'example.*'], [], ['listen', '5001', 'ssl'], ['#', ' managed by Certbot'], @@ -615,8 +624,8 @@ class NginxConfiguratorTest(util.NginxTest): ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], - [], [], []]]], - generated_conf) + [], [], []]]] == \ + generated_conf def test_http_header_hsts(self): example_conf = self.config.parser.abs_path('sites-enabled/example.com') @@ -624,7 +633,7 @@ class NginxConfiguratorTest(util.NginxTest): "Strict-Transport-Security") expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'] generated_conf = self.config.parser.parsed[example_conf] - self.assertIs(util.contains_at_depth(generated_conf, expected, 2), True) + assert util.contains_at_depth(generated_conf, expected, 2) is True def test_multiple_headers_hsts(self): headers_conf = self.config.parser.abs_path('sites-enabled/headers.com') @@ -632,14 +641,13 @@ class NginxConfiguratorTest(util.NginxTest): "Strict-Transport-Security") expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'] generated_conf = self.config.parser.parsed[headers_conf] - self.assertIs(util.contains_at_depth(generated_conf, expected, 2), True) + assert util.contains_at_depth(generated_conf, expected, 2) is True def test_http_header_hsts_twice(self): self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security") - self.assertRaises( - errors.PluginEnhancementAlreadyPresent, - self.config.enhance, "www.example.com", + with pytest.raises(errors.PluginEnhancementAlreadyPresent): + self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security") @mock.patch('certbot_nginx._internal.obj.VirtualHost.contains_list') @@ -650,15 +658,15 @@ class NginxConfiguratorTest(util.NginxTest): mock_contains_list.return_value = True with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: self.config.enhance("www.example.com", "redirect") - self.assertEqual(mock_logger.info.call_args[0][0], - "Traffic on port %s already redirecting to ssl in %s") + assert mock_logger.info.call_args[0][0] == \ + "Traffic on port %s already redirecting to ssl in %s" def test_redirect_dont_enhance(self): # Test that we don't accidentally add redirect to ssl-only block with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: self.config.enhance("geese.com", "redirect") - self.assertEqual(mock_logger.info.call_args[0][0], - 'No matching insecure server blocks listening on port %s found.') + assert mock_logger.info.call_args[0][0] == \ + 'No matching insecure server blocks listening on port %s found.' def test_double_redirect(self): # Test that we add one redirect for each domain @@ -670,23 +678,23 @@ class NginxConfiguratorTest(util.NginxTest): expected2 = UnspacedList(_redirect_block_for_domain("example.org"))[0] generated_conf = self.config.parser.parsed[example_conf] - self.assertTrue(util.contains_at_depth(generated_conf, expected1, 2)) - self.assertTrue(util.contains_at_depth(generated_conf, expected2, 2)) + assert util.contains_at_depth(generated_conf, expected1, 2) + assert util.contains_at_depth(generated_conf, expected2, 2) def test_staple_ocsp_bad_version(self): self.config.version = (1, 3, 1) - self.assertRaises(errors.PluginError, self.config.enhance, - "www.example.com", "staple-ocsp", "chain_path") + with pytest.raises(errors.PluginError): + self.config.enhance("www.example.com", "staple-ocsp", "chain_path") def test_staple_ocsp_no_chain_path(self): - self.assertRaises(errors.PluginError, self.config.enhance, - "www.example.com", "staple-ocsp", None) + with pytest.raises(errors.PluginError): + self.config.enhance("www.example.com", "staple-ocsp", None) def test_staple_ocsp_internal_error(self): self.config.enhance("www.example.com", "staple-ocsp", "chain_path") # error is raised because the server block has conflicting directives - self.assertRaises(errors.PluginError, self.config.enhance, - "www.example.com", "staple-ocsp", "different_path") + with pytest.raises(errors.PluginError): + self.config.enhance("www.example.com", "staple-ocsp", "different_path") def test_staple_ocsp(self): chain_path = "example/chain.pem" @@ -695,13 +703,13 @@ class NginxConfiguratorTest(util.NginxTest): example_conf = self.config.parser.abs_path('sites-enabled/example.com') generated_conf = self.config.parser.parsed[example_conf] - self.assertTrue(util.contains_at_depth( + assert util.contains_at_depth( generated_conf, - ['ssl_trusted_certificate', 'example/chain.pem'], 2)) - self.assertTrue(util.contains_at_depth( - generated_conf, ['ssl_stapling', 'on'], 2)) - self.assertTrue(util.contains_at_depth( - generated_conf, ['ssl_stapling_verify', 'on'], 2)) + ['ssl_trusted_certificate', 'example/chain.pem'], 2) + assert util.contains_at_depth( + generated_conf, ['ssl_stapling', 'on'], 2) + assert util.contains_at_depth( + generated_conf, ['ssl_stapling_verify', 'on'], 2) def test_deploy_no_match_default_set(self): default_conf = self.config.parser.abs_path('sites-enabled/default') @@ -721,7 +729,7 @@ class NginxConfiguratorTest(util.NginxTest): parsed_default_conf = util.filter_comments(self.config.parser.parsed[default_conf]) - self.assertEqual([[['server'], + assert [[['server'], [['listen', 'myhost', 'default_server'], ['listen', 'otherhost', 'default_server'], ['server_name', '"www.example.org"'], @@ -739,8 +747,8 @@ class NginxConfiguratorTest(util.NginxTest): ['ssl_certificate', 'example/fullchain.pem'], ['ssl_certificate_key', 'example/key.pem'], ['include', self.config.mod_ssl_conf], - ['ssl_dhparam', self.config.ssl_dhparams]]]], - parsed_default_conf) + ['ssl_dhparam', self.config.ssl_dhparams]]]] == \ + parsed_default_conf self.config.deploy_cert( "nomatch.com", @@ -754,7 +762,7 @@ class NginxConfiguratorTest(util.NginxTest): parsed_default_conf = util.filter_comments(self.config.parser.parsed[default_conf]) - self.assertTrue(util.contains_at_depth(parsed_default_conf, "nomatch.com", 3)) + assert util.contains_at_depth(parsed_default_conf, "nomatch.com", 3) def test_deploy_no_match_default_set_multi_level_path(self): default_conf = self.config.parser.abs_path('sites-enabled/default') @@ -775,7 +783,7 @@ class NginxConfiguratorTest(util.NginxTest): parsed_foo_conf = util.filter_comments(self.config.parser.parsed[foo_conf]) - self.assertEqual([['server'], + assert [['server'], [['listen', '*:80', 'ssl'], ['server_name', 'www.nomatch.com'], ['root', '/home/ubuntu/sites/foo/'], @@ -786,8 +794,8 @@ class NginxConfiguratorTest(util.NginxTest): [['location', '=', 'exact_match\\.php$'], []], [['location', '^~', 'ignore_regex\\.php$'], []], ['ssl_certificate', 'example/fullchain.pem'], - ['ssl_certificate_key', 'example/key.pem']]], - parsed_foo_conf[1][1][1]) + ['ssl_certificate_key', 'example/key.pem']]] == \ + parsed_foo_conf[1][1][1] def test_deploy_no_match_no_default_set(self): default_conf = self.config.parser.abs_path('sites-enabled/default') @@ -797,14 +805,14 @@ class NginxConfiguratorTest(util.NginxTest): del self.config.parser.parsed[foo_conf][2][1][0][1][0] self.config.version = (1, 3, 1) - self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, - "www.nomatch.com", "example/cert.pem", "example/key.pem", + with pytest.raises(errors.MisconfigurationError): + self.config.deploy_cert("www.nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") def test_deploy_no_match_fail_multiple_defaults(self): self.config.version = (1, 3, 1) - self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, - "www.nomatch.com", "example/cert.pem", "example/key.pem", + with pytest.raises(errors.MisconfigurationError): + self.config.deploy_cert("www.nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") def test_deploy_no_match_multiple_defaults_ok(self): @@ -843,7 +851,7 @@ class NginxConfiguratorTest(util.NginxTest): expected = UnspacedList(_redirect_block_for_domain("www.nomatch.com"))[0] generated_conf = self.config.parser.parsed[default_conf] - self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) + assert util.contains_at_depth(generated_conf, expected, 2) @mock.patch('certbot.reverter.logger') @mock.patch('certbot_nginx._internal.parser.NginxParser.load') @@ -851,7 +859,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.recovery_routine() self.config.revert_challenge_config() self.config.rollback_checkpoints() - self.assertEqual(mock_parser_load.call_count, 3) + assert mock_parser_load.call_count == 3 def test_choose_vhosts_wildcard(self): # pylint: disable=protected-access @@ -863,11 +871,11 @@ class NginxConfiguratorTest(util.NginxTest): vhs = self.config._choose_vhosts_wildcard("*.com", prefer_ssl=True) # Check that the dialog was called with migration.com - self.assertIn(vhost, mock_select_vhs.call_args[0][0]) + assert vhost in mock_select_vhs.call_args[0][0] # And the actual returned values - self.assertEqual(len(vhs), 1) - self.assertEqual(vhs[0], vhost) + assert len(vhs) == 1 + assert vhs[0] == vhost def test_choose_vhosts_wildcard_redirect(self): # pylint: disable=protected-access @@ -879,11 +887,11 @@ class NginxConfiguratorTest(util.NginxTest): vhs = self.config._choose_vhosts_wildcard("*.com", prefer_ssl=False) # Check that the dialog was called with migration.com - self.assertIn(vhost, mock_select_vhs.call_args[0][0]) + assert vhost in mock_select_vhs.call_args[0][0] # And the actual returned values - self.assertEqual(len(vhs), 1) - self.assertEqual(vhs[0], vhost) + assert len(vhs) == 1 + assert vhs[0] == vhost def test_deploy_cert_wildcard(self): # pylint: disable=protected-access @@ -896,17 +904,16 @@ class NginxConfiguratorTest(util.NginxTest): with mock.patch(mock_d) as mock_dep: self.config.deploy_cert("*.com", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") - self.assertTrue(mock_dep.called) - self.assertEqual(len(mock_dep.call_args_list), 1) - self.assertEqual(vhost, mock_dep.call_args_list[0][0][0]) + assert mock_dep.called + assert len(mock_dep.call_args_list) == 1 + assert vhost == mock_dep.call_args_list[0][0][0] @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): # pylint: disable=protected-access mock_dialog.return_value = [] - self.assertRaises(errors.PluginError, - self.config.deploy_cert, - "*.wild.cat", "/tmp/path", "/tmp/path", + with pytest.raises(errors.PluginError): + self.config.deploy_cert("*.wild.cat", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") @@ -916,7 +923,7 @@ class NginxConfiguratorTest(util.NginxTest): if 'geese.com' in x.names][0] self.config._wildcard_vhosts["*.com"] = [vhost] self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") - self.assertFalse(mock_dialog.called) + assert not mock_dialog.called @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_enhance_wildcard_redirect_or_ocsp_no_install(self, mock_dialog): @@ -924,7 +931,7 @@ class NginxConfiguratorTest(util.NginxTest): if 'summer.com' in x.names][0] mock_dialog.return_value = [vhost] self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") - self.assertIs(mock_dialog.called, True) + assert mock_dialog.called is True @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_enhance_wildcard_double_redirect(self, mock_dialog): @@ -933,7 +940,7 @@ class NginxConfiguratorTest(util.NginxTest): if 'summer.com' in x.names][0] self.config._wildcard_redirect_vhosts["*.com"] = [vhost] self.config.enhance("*.com", "redirect") - self.assertFalse(mock_dialog.called) + assert not mock_dialog.called def test_choose_vhosts_wildcard_no_ssl_filter_port(self): # pylint: disable=protected-access @@ -944,19 +951,19 @@ class NginxConfiguratorTest(util.NginxTest): prefer_ssl=False, no_ssl_filter_port='80') # Check that the dialog was called with only port 80 vhosts - self.assertEqual(len(mock_select_vhs.call_args[0][0]), 8) + assert len(mock_select_vhs.call_args[0][0]) == 8 def test_choose_auth_vhosts(self): """choose_auth_vhosts correctly selects duplicative and HTTP/HTTPS vhosts""" http, https = self.config.choose_auth_vhosts('ssl.both.com') - self.assertEqual(len(http), 4) - self.assertEqual(len(https), 2) - self.assertEqual(http[0].names, {'ssl.both.com'}) - self.assertEqual(http[1].names, {'ssl.both.com'}) - self.assertEqual(http[2].names, {'ssl.both.com'}) - self.assertEqual(http[3].names, {'*.both.com'}) - self.assertEqual(https[0].names, {'ssl.both.com'}) - self.assertEqual(https[1].names, {'*.both.com'}) + assert len(http) == 4 + assert len(https) == 2 + assert http[0].names == {'ssl.both.com'} + assert http[1].names == {'ssl.both.com'} + assert http[2].names == {'ssl.both.com'} + assert http[3].names == {'*.both.com'} + assert https[0].names == {'ssl.both.com'} + assert https[1].names == {'*.both.com'} class InstallSslOptionsConfTest(util.NginxTest): @@ -976,15 +983,15 @@ class InstallSslOptionsConfTest(util.NginxTest): return crypto_util.sha256sum(self.config.mod_ssl_conf_src) def _assert_current_file(self): - self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) - self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), - self._current_ssl_options_hash()) + assert os.path.isfile(self.config.mod_ssl_conf) + assert crypto_util.sha256sum(self.config.mod_ssl_conf) == \ + self._current_ssl_options_hash() def test_no_file(self): # prepare should have placed a file there self._assert_current_file() os.remove(self.config.mod_ssl_conf) - self.assertFalse(os.path.isfile(self.config.mod_ssl_conf)) + assert not os.path.isfile(self.config.mod_ssl_conf) self._call() self._assert_current_file() @@ -1024,12 +1031,12 @@ class InstallSslOptionsConfTest(util.NginxTest): mod_ssl_conf.write("a new line for the wrong hash\n") with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() - self.assertFalse(mock_logger.warning.called) - self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) - self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), - self._current_ssl_options_hash()) - self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), - self._current_ssl_options_hash()) + assert not mock_logger.warning.called + assert os.path.isfile(self.config.mod_ssl_conf) + assert crypto_util.sha256sum(self.config.mod_ssl_conf_src) == \ + self._current_ssl_options_hash() + assert crypto_util.sha256sum(self.config.mod_ssl_conf) != \ + self._current_ssl_options_hash() def test_manually_modified_past_file_warns(self): with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: @@ -1038,24 +1045,21 @@ class InstallSslOptionsConfTest(util.NginxTest): f.write("hashofanoldversion") with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() - self.assertEqual( - mock_logger.warning.call_args[0][0], - "%s has been manually modified; updated file " - "saved to %s. We recommend updating %s for security purposes.") - self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), - self._current_ssl_options_hash()) + assert mock_logger.warning.call_args[0][0] == \ + "%s has been manually modified; updated file " \ + "saved to %s. We recommend updating %s for security purposes." + assert crypto_util.sha256sum(self.config.mod_ssl_conf_src) == \ + self._current_ssl_options_hash() # only print warning once with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() - self.assertFalse(mock_logger.warning.called) + assert not mock_logger.warning.called def test_current_file_hash_in_all_hashes(self): from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES - self.assertIn( - self._current_ssl_options_hash(), ALL_SSL_OPTIONS_HASHES, - "Constants.ALL_SSL_OPTIONS_HASHES must be appended" + assert self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, \ + "Constants.ALL_SSL_OPTIONS_HASHES must be appended" \ " with the sha256 hash of self.config.mod_ssl_conf when it is updated." - ) def test_ssl_config_files_hash_in_all_hashes(self): """ @@ -1064,8 +1068,9 @@ class InstallSslOptionsConfTest(util.NginxTest): file has been manually edited by the user, and will refuse to update it. This test ensures that all necessary hashes are present. """ - from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES import pkg_resources + + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES all_files = [ pkg_resources.resource_filename("certbot_nginx", os.path.join("_internal", "tls_configs", x)) @@ -1073,37 +1078,35 @@ class InstallSslOptionsConfTest(util.NginxTest): "options-ssl-nginx-old.conf", "options-ssl-nginx-tls12-only.conf") ] - self.assertTrue(all_files) + assert all_files for one_file in all_files: file_hash = crypto_util.sha256sum(one_file) - self.assertIn( - file_hash, ALL_SSL_OPTIONS_HASHES, - f"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " + assert file_hash in ALL_SSL_OPTIONS_HASHES, \ + f"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " \ f"hash of {one_file} when it is updated." - ) def test_nginx_version_uses_correct_config(self): self.config.version = (1, 5, 8) self.config.openssl_version = "1.0.2g" # shouldn't matter - self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), - "options-ssl-nginx-old.conf") + assert os.path.basename(self.config.mod_ssl_conf_src) == \ + "options-ssl-nginx-old.conf" self._call() self._assert_current_file() self.config.version = (1, 5, 9) self.config.openssl_version = "1.0.2l" - self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), - "options-ssl-nginx-tls12-only.conf") + assert os.path.basename(self.config.mod_ssl_conf_src) == \ + "options-ssl-nginx-tls12-only.conf" self._call() self._assert_current_file() self.config.version = (1, 13, 0) - self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), - "options-ssl-nginx.conf") + assert os.path.basename(self.config.mod_ssl_conf_src) == \ + "options-ssl-nginx.conf" self._call() self._assert_current_file() self.config.version = (1, 13, 0) self.config.openssl_version = "1.0.2k" - self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), - "options-ssl-nginx-tls13-session-tix-on.conf") + assert os.path.basename(self.config.mod_ssl_conf_src) == \ + "options-ssl-nginx-tls13-session-tix-on.conf" class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): @@ -1125,11 +1128,11 @@ class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): server_root = self._call() if expect_both_values: - self.assertIn("/usr/local/etc/nginx", server_root) - self.assertIn("/etc/nginx", server_root) + assert "/usr/local/etc/nginx" in server_root + assert "/etc/nginx" in server_root else: - self.assertIn(server_root, ("/etc/nginx", "/usr/local/etc/nginx")) + assert server_root in ("/etc/nginx", "/usr/local/etc/nginx") if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-nginx/tests/display_ops_test.py b/certbot-nginx/tests/display_ops_test.py index 19f51e7e8..4e9266c52 100644 --- a/certbot-nginx/tests/display_ops_test.py +++ b/certbot-nginx/tests/display_ops_test.py @@ -1,6 +1,9 @@ """Test certbot_nginx._internal.display_ops.""" +import sys import unittest +import pytest + from certbot.display import util as display_util from certbot.tests import util as certbot_util from certbot_nginx._internal import parser @@ -17,7 +20,7 @@ class SelectVhostMultiTest(util.NginxTest): self.vhosts = nparser.get_vhosts() def test_select_no_input(self): - self.assertFalse(select_vhost_multiple([])) + assert not select_vhost_multiple([]) @certbot_util.patch_display_util() def test_select_correct(self, mock_util): @@ -27,17 +30,17 @@ class SelectVhostMultiTest(util.NginxTest): vhs = select_vhost_multiple([self.vhosts[3], self.vhosts[2], self.vhosts[1]]) - self.assertIn(self.vhosts[2], vhs) - self.assertIn(self.vhosts[3], vhs) - self.assertNotIn(self.vhosts[1], vhs) + assert self.vhosts[2] in vhs + assert self.vhosts[3] in vhs + assert self.vhosts[1] not in vhs @certbot_util.patch_display_util() def test_select_cancel(self, mock_util): mock_util().checklist.return_value = (display_util.CANCEL, "whatever") vhs = select_vhost_multiple([self.vhosts[2], self.vhosts[3]]) - self.assertEqual(len(vhs), 0) - self.assertEqual(vhs, []) + assert len(vhs) == 0 + assert vhs == [] if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-nginx/tests/http_01_test.py b/certbot-nginx/tests/http_01_test.py index 05be06202..e0809f68a 100644 --- a/certbot-nginx/tests/http_01_test.py +++ b/certbot-nginx/tests/http_01_test.py @@ -1,8 +1,10 @@ """Tests for certbot_nginx._internal.http_01""" +import sys import unittest from unittest import mock import josepy as jose +import pytest from acme import challenges from certbot import achallenges @@ -58,7 +60,7 @@ class HttpPerformTest(util.NginxTest): def test_perform0(self): responses = self.http01.perform() - self.assertEqual([], responses) + assert [] == responses @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.save") def test_perform1(self, mock_save): @@ -67,8 +69,8 @@ class HttpPerformTest(util.NginxTest): responses = self.http01.perform() - self.assertEqual([response], responses) - self.assertEqual(mock_save.call_count, 1) + assert [response] == responses + assert mock_save.call_count == 1 def test_perform2(self): acme_responses = [] @@ -78,9 +80,9 @@ class HttpPerformTest(util.NginxTest): http_responses = self.http01.perform() - self.assertEqual(len(http_responses), 5) + assert len(http_responses) == 5 for i in range(5): - self.assertEqual(http_responses[i], acme_responses[i]) + assert http_responses[i] == acme_responses[i] def test_mod_config(self): self.http01.add_chall(self.achalls[0]) @@ -115,7 +117,7 @@ class HttpPerformTest(util.NginxTest): # Domain has an HTTP and HTTPS vhost # 2 * 'rewrite' + 2 * 'return 200 keyauthz' = 4 - self.assertEqual(mock_add_server_directives.call_count, 4) + assert mock_add_server_directives.call_count == 4 @mock.patch('certbot_nginx._internal.parser.nginxparser.dump') @mock.patch('certbot_nginx._internal.parser.NginxParser.add_server_directives') @@ -125,10 +127,10 @@ class HttpPerformTest(util.NginxTest): self.http01._mod_config() # pylint: disable=protected-access # It should modify the existing HTTPS vhost - self.assertEqual(mock_add_server_directives.call_count, 2) + assert mock_add_server_directives.call_count == 2 # since there was no suitable HTTP vhost or default HTTP vhost, a non-empty one # should have been created and written to the challenge conf file - self.assertNotEqual(mock_dump.call_args[0][0], []) + assert mock_dump.call_args[0][0] != [] @mock.patch('certbot_nginx._internal.parser.NginxParser.add_server_directives') def test_mod_config_deduplicate(self, mock_add_server_directives): @@ -141,14 +143,14 @@ class HttpPerformTest(util.NginxTest): self.http01._mod_config() # pylint: disable=protected-access # Should only get called 5 times, rather than 6, because two vhosts are the same - self.assertEqual(mock_add_server_directives.call_count, 5*2) + assert mock_add_server_directives.call_count == 5*2 def test_mod_config_insert_bucket_directive(self): nginx_conf = self.http01.configurator.parser.abs_path('nginx.conf') expected = ['server_names_hash_bucket_size', '128'] original_conf = self.http01.configurator.parser.parsed[nginx_conf] - self.assertFalse(util.contains_at_depth(original_conf, expected, 2)) + assert not util.contains_at_depth(original_conf, expected, 2) self.http01.add_chall(self.achalls[0]) self.http01._mod_config() # pylint: disable=protected-access @@ -156,7 +158,7 @@ class HttpPerformTest(util.NginxTest): self.http01.configurator.parser.load() generated_conf = self.http01.configurator.parser.parsed[nginx_conf] - self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) + assert util.contains_at_depth(generated_conf, expected, 2) def test_mod_config_update_bucket_directive_in_included_file(self): # save old example.com config @@ -180,11 +182,11 @@ class HttpPerformTest(util.NginxTest): expected = ['server_names_hash_bucket_size', '128'] nginx_conf_loc = self.http01.configurator.parser.abs_path('nginx.conf') nginx_conf = self.http01.configurator.parser.parsed[nginx_conf_loc] - self.assertFalse(util.contains_at_depth(nginx_conf, expected, 2)) + assert not util.contains_at_depth(nginx_conf, expected, 2) # is updated in example.com conf generated_conf = self.http01.configurator.parser.parsed[example_com_loc] - self.assertTrue(util.contains_at_depth(generated_conf, expected, 0)) + assert util.contains_at_depth(generated_conf, expected, 0) # put back example.com config with open(example_com_loc, 'w') as f: @@ -196,10 +198,10 @@ class HttpPerformTest(util.NginxTest): # pylint: disable=protected-access ipv6_info.return_value = (True, True) self.http01._default_listen_addresses() - self.assertEqual(ipv6_info.call_count, 1) + assert ipv6_info.call_count == 1 ipv6_info.return_value = (False, False) self.http01._default_listen_addresses() - self.assertEqual(ipv6_info.call_count, 2) + assert ipv6_info.call_count == 2 @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_t_t(self, ipv6_info): @@ -208,7 +210,7 @@ class HttpPerformTest(util.NginxTest): addrs = self.http01._default_listen_addresses() http_addr = Addr.fromstring("80") http_ipv6_addr = Addr.fromstring("[::]:80") - self.assertEqual(addrs, [http_addr, http_ipv6_addr]) + assert addrs == [http_addr, http_ipv6_addr] @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_t_f(self, ipv6_info): @@ -217,7 +219,7 @@ class HttpPerformTest(util.NginxTest): addrs = self.http01._default_listen_addresses() http_addr = Addr.fromstring("80") http_ipv6_addr = Addr.fromstring("[::]:80 ipv6only=on") - self.assertEqual(addrs, [http_addr, http_ipv6_addr]) + assert addrs == [http_addr, http_ipv6_addr] @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_f_f(self, ipv6_info): @@ -225,7 +227,7 @@ class HttpPerformTest(util.NginxTest): ipv6_info.return_value = (False, False) addrs = self.http01._default_listen_addresses() http_addr = Addr.fromstring("80") - self.assertEqual(addrs, [http_addr]) + assert addrs == [http_addr] if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-nginx/tests/nginxparser_test.py b/certbot-nginx/tests/nginxparser_test.py index 3713f16e4..44fdfd141 100644 --- a/certbot-nginx/tests/nginxparser_test.py +++ b/certbot-nginx/tests/nginxparser_test.py @@ -1,10 +1,12 @@ """Test for certbot_nginx._internal.nginxparser.""" import copy import operator +import sys import tempfile import unittest from pyparsing import ParseException +import pytest from certbot_nginx._internal.nginxparser import dump from certbot_nginx._internal.nginxparser import dumps @@ -12,7 +14,6 @@ from certbot_nginx._internal.nginxparser import load from certbot_nginx._internal.nginxparser import loads from certbot_nginx._internal.nginxparser import RawNginxParser from certbot_nginx._internal.nginxparser import UnspacedList -from certbot_nginx._internal.nginxparser import UnsupportedDirectiveException import test_util as util FIRST = operator.itemgetter(0) @@ -23,23 +24,23 @@ class TestRawNginxParser(unittest.TestCase): def test_assignments(self): parsed = RawNginxParser.assignment.parseString('root /test;').asList() - self.assertEqual(parsed, ['root', ' ', '/test']) + assert parsed == ['root', ' ', '/test'] parsed = RawNginxParser.assignment.parseString('root /test;foo bar;').asList() - self.assertEqual(parsed, ['root', ' ', '/test'], ['foo', ' ', 'bar']) + assert parsed == ['root', ' ', '/test'], ['foo', ' ', 'bar'] def test_blocks(self): parsed = RawNginxParser.block.parseString('foo {}').asList() - self.assertEqual(parsed, [['foo', ' '], []]) + assert parsed == [['foo', ' '], []] parsed = RawNginxParser.block.parseString('location /foo{}').asList() - self.assertEqual(parsed, [['location', ' ', '/foo'], []]) + assert parsed == [['location', ' ', '/foo'], []] parsed = RawNginxParser.block.parseString('foo { bar foo ; }').asList() - self.assertEqual(parsed, [['foo', ' '], [[' ', 'bar', ' ', 'foo', ' '], ' ']]) + assert parsed == [['foo', ' '], [[' ', 'bar', ' ', 'foo', ' '], ' ']] def test_nested_blocks(self): parsed = RawNginxParser.block.parseString('foo { bar {} }').asList() block, content = parsed - self.assertEqual(FIRST(content), [[' ', 'bar', ' '], []]) - self.assertEqual(FIRST(block), 'foo') + assert FIRST(content) == [[' ', 'bar', ' '], []] + assert FIRST(block) == 'foo' def test_dump_as_string(self): dumped = dumps(UnspacedList([ @@ -55,24 +56,23 @@ class TestRawNginxParser(unittest.TestCase): ]] ]]])) - self.assertEqual(dumped.split('\n'), - 'user www-data;\n' - 'server {\n' - ' listen 80;\n' - ' server_name foo.com;\n' - ' root /home/ubuntu/sites/foo/;\n' - '\n' - ' location /status {\n' - ' check_status;\n' - '\n' - ' types {\n' - ' image/jpeg jpg;}}}'.split('\n')) + assert dumped.split('\n') == \ + 'user www-data;\n' \ + 'server {\n' \ + ' listen 80;\n' \ + ' server_name foo.com;\n' \ + ' root /home/ubuntu/sites/foo/;\n' \ + '\n' \ + ' location /status {\n' \ + ' check_status;\n' \ + '\n' \ + ' types {\n' \ + ' image/jpeg jpg;}}}'.split('\n') def test_parse_from_file(self): with open(util.get_data_filename('foo.conf')) as handle: parsed = util.filter_comments(load(handle)) - self.assertEqual( - parsed, + assert parsed == \ [['user', 'www-data'], [['http'], [[['server'], [ @@ -90,13 +90,11 @@ class TestRawNginxParser(unittest.TestCase): [['location', '=', r'exact_match\.php$'], []], [['location', '^~', r'ignore_regex\.php$'], []] ]]]]] - ) def test_parse_from_file2(self): with open(util.get_data_filename('edge_cases.conf')) as handle: parsed = util.filter_comments(load(handle)) - self.assertEqual( - parsed, + assert parsed == \ [[['server'], [['server_name', 'simple']]], [['server'], [['server_name', 'with.if'], @@ -111,13 +109,12 @@ class TestRawNginxParser(unittest.TestCase): 'Cache-Control', '\'public, must-revalidate, proxy-revalidate\'', '"test,;{}"', 'foo'], ['blah', '"hello;world"'], - ['try_files', '$uri', '@rewrites']]]]]]) + ['try_files', '$uri', '@rewrites']]]]]] def test_parse_from_file3(self): with open(util.get_data_filename('multiline_quotes.conf')) as handle: parsed = util.filter_comments(load(handle)) - self.assertEqual( - parsed, + assert parsed == \ [[['http'], [[['server'], [['listen', '*:443'], @@ -129,11 +126,12 @@ class TestRawNginxParser(unittest.TestCase): 'if ngx.arg[2] then\n' ' ' 'ngx.var.resp_body = ngx.ctx.buffered\n' - ' end\'']]]]]]]]) + ' end\'']]]]]]]] def test_abort_on_parse_failure(self): with open(util.get_data_filename('broken.conf')) as handle: - self.assertRaises(ParseException, load, handle) + with pytest.raises(ParseException): + load(handle) def test_dump_as_file(self): with open(util.get_data_filename('nginx.conf')) as handle: @@ -154,7 +152,7 @@ class TestRawNginxParser(unittest.TestCase): dump(parsed, f) f.seek(0) parsed_new = load(f) - self.assertEqual(parsed, parsed_new) + assert parsed == parsed_new def test_comments(self): with open(util.get_data_filename('minimalistic_comments.conf')) as handle: @@ -165,8 +163,8 @@ class TestRawNginxParser(unittest.TestCase): f.seek(0) parsed_new = load(f) - self.assertEqual(parsed, parsed_new) - self.assertEqual(parsed_new, [ + assert parsed == parsed_new + assert parsed_new == [ ['#', " Use bar.conf when it's a full moon!"], ['include', 'foo.conf'], ['#', ' Kilroy was here'], @@ -177,44 +175,44 @@ class TestRawNginxParser(unittest.TestCase): ['#', ''], ['listen', '1234'], ['#', ' listen 80;']]], - ]) + ] def test_issue_518(self): parsed = loads('if ($http_accept ~* "webp") { set $webp "true"; }') - self.assertEqual(parsed, [ + assert parsed == [ [['if', '($http_accept', '~*', '"webp")'], [['set', '$webp', '"true"']]] - ]) + ] def test_comment_in_block(self): parsed = loads("""http { # server{ }""") - self.assertEqual(parsed, [ + assert parsed == [ [['http'], [['#', ' server{']]] - ]) + ] def test_access_log(self): # see issue #3798 parsed = loads('access_log syslog:server=unix:/dev/log,facility=auth,' 'tag=nginx_post,severity=info custom;') - self.assertEqual(parsed, [ + assert parsed == [ ['access_log', 'syslog:server=unix:/dev/log,facility=auth,tag=nginx_post,severity=info', 'custom'] - ]) + ] def test_add_header(self): # see issue #3798 parsed = loads('add_header Cache-Control no-cache,no-store,must-revalidate,max-age=0;') - self.assertEqual(parsed, [ + assert parsed == [ ['add_header', 'Cache-Control', 'no-cache,no-store,must-revalidate,max-age=0'] - ]) + ] def test_map_then_assignment_in_block(self): # see issue #3798 @@ -228,7 +226,7 @@ class TestRawNginxParser(unittest.TestCase): one; }""" parsed = loads(test_str) - self.assertEqual(parsed, [ + assert parsed == [ [['http'], [ [['map', '$http_upgrade', '$connection_upgrade'], [ ['default', 'upgrade'], @@ -238,17 +236,17 @@ class TestRawNginxParser(unittest.TestCase): ]], ['one'] ]] - ]) + ] def test_variable_name(self): parsed = loads('try_files /typo3temp/tx_ncstaticfilecache/' '$host${request_uri}index.html @nocache;') - self.assertEqual(parsed, [ + assert parsed == [ ['try_files', '/typo3temp/tx_ncstaticfilecache/$host${request_uri}index.html', '@nocache'] - ]) + ] def test_weird_blocks(self): test = r""" @@ -279,7 +277,7 @@ class TestRawNginxParser(unittest.TestCase): proxy_set_header X-Origin-URI ${scheme}://${http_host}/$request_uri; """ parsed = loads(test) - self.assertEqual(parsed, [[['if', '($http_user_agent', '~', 'MSIE)'], + assert parsed == [[['if', '($http_user_agent', '~', 'MSIE)'], [['rewrite', '^(.*)$', '/msie/$1', 'break']]], [['if', '($http_cookie', '~*', '"id=([^;]+)(?:;|$)")'], [['set', '$id', '$1']]], [['if', '($request_method', '=', 'POST)'], [['return', '405']]], @@ -289,18 +287,17 @@ class TestRawNginxParser(unittest.TestCase): [['location', '~', '^/users/(.+\\.(?:gif|jpe?g|png))$'], [['alias', '/data/w3/images/$1']]], ['proxy_set_header', 'X-Origin-URI', '${scheme}://${http_host}/$request_uri']] - ) def test_edge_cases(self): # quotes parsed = loads(r'"hello\""; # blah "heh heh"') - self.assertEqual(parsed, [['"hello\\""'], ['#', ' blah "heh heh"']]) + assert parsed == [['"hello\\""'], ['#', ' blah "heh heh"']] # if with comment parsed = loads("""if ($http_cookie ~* "id=([^;]+)(?:;|$)") { # blah ) }""") - self.assertEqual(parsed, [[['if', '($http_cookie', '~*', '"id=([^;]+)(?:;|$)")'], - [['#', ' blah )']]]]) + assert parsed == [[['if', '($http_cookie', '~*', '"id=([^;]+)(?:;|$)")'], + [['#', ' blah )']]]] # end paren test = """ @@ -315,7 +312,7 @@ class TestRawNginxParser(unittest.TestCase): one"test"one; """ parsed = loads(test) - self.assertEqual(parsed, [ + assert parsed == [ ['one"test"'], ['("two")'], ['"test")red'], @@ -325,9 +322,11 @@ class TestRawNginxParser(unittest.TestCase): ['one"'], ['one"test'], ['one"test"one'] - ]) - self.assertRaises(ParseException, loads, r'"test"one;') # fails - self.assertRaises(ParseException, loads, r'"test;') # fails + ] + with pytest.raises(ParseException): + loads(r'"test"one;') # fails + with pytest.raises(ParseException): + loads(r'"test;') # fails # newlines test = """ @@ -337,87 +336,26 @@ class TestRawNginxParser(unittest.TestCase): baz.example.com qux.example.com; """ parsed = loads(test) - self.assertEqual(parsed, [ + assert parsed == [ ['server_name', 'foo.example.com', 'bar.example.com', 'baz.example.com', 'qux.example.com'], ['server_name', 'foo.example.com', 'bar.example.com', 'baz.example.com', 'qux.example.com'] - ]) + ] # variable weirdness parsed = loads("directive $var ${var} $ ${};") - self.assertEqual(parsed, [['directive', '$var', '${var}', '$', '${}']]) - self.assertRaises(ParseException, loads, "server {server_name test.com};") - self.assertEqual(loads("blag${dfgdfg};"), [['blag${dfgdfg}']]) - self.assertRaises(ParseException, loads, "blag${dfgdf{g};") + assert parsed == [['directive', '$var', '${var}', '$', '${}']] + with pytest.raises(ParseException): + loads("server {server_name test.com};") + assert loads("blag${dfgdfg};") == [['blag${dfgdfg}']] + with pytest.raises(ParseException): + loads("blag${dfgdf{g};") # empty file parsed = loads("") - self.assertEqual(parsed, []) + assert parsed == [] - def test_lua(self): - # https://github.com/certbot/certbot/issues/9066 - self.assertRaises(UnsupportedDirectiveException, loads, """ - location /foo { - content_by_lua_block { - ngx.say('Hello World') - } - } - """) - - # Without leading whitespace - self.assertRaises(UnsupportedDirectiveException, loads, """ - location /foo {content_by_lua_block { - ngx.say('Hello World') - } - } - """) - - # Doesn't trigger if it's commented or not in the right position. - parsed = loads(""" - location /foo {server_name content_by_lua_block; - #content_by_lua_block { - # ngx.say('Hello World') - # } - } - """) - self.assertEqual( - parsed, - [ - [['location', '/foo'], - [['server_name', 'content_by_lua_block'], - ['#', 'content_by_lua_block {'], - ['#', " ngx.say('Hello World')"], - ['#', ' }'] - ]] - ]) - - # *_by_lua should parse successfully. - parsed = loads(""" - location / { - set $a 32; - set $b 56; - set_by_lua $sum - 'return tonumber(ngx.arg[1]) + tonumber(ngx.arg[2])' - $a $b; - content_by_lua ' - ngx.say("foo"); - '; - } - """) - self.assertEqual( - parsed, - [ - [['location', '/'], - [['set', '$a', '32'], - ['set', '$b', '56'], - ['set_by_lua', '$sum', - "'return tonumber(ngx.arg[1]) + tonumber(ngx.arg[2])'", '$a', '$b' - ], - ['content_by_lua', '\'\n ngx.say("foo");\n \''] - ]] - ] - ) class TestUnspacedList(unittest.TestCase): """Test the UnspacedList data structure""" @@ -430,42 +368,44 @@ class TestUnspacedList(unittest.TestCase): self.ul2 = UnspacedList(self.l2) def test_construction(self): - self.assertEqual(self.ul, ["things", "quirk"]) - self.assertEqual(self.ul2, ["y"]) + assert self.ul == ["things", "quirk"] + assert self.ul2 == ["y"] def test_append(self): ul3 = copy.deepcopy(self.ul) ul3.append("wise") - self.assertEqual(ul3, ["things", "quirk", "wise"]) - self.assertEqual(ul3.spaced, self.a + ["wise"]) + assert ul3 == ["things", "quirk", "wise"] + assert ul3.spaced == self.a + ["wise"] def test_add(self): ul3 = self.ul + self.ul2 - self.assertEqual(ul3, ["things", "quirk", "y"]) - self.assertEqual(ul3.spaced, self.a + self.b) - self.assertEqual(self.ul.spaced, self.a) + assert ul3 == ["things", "quirk", "y"] + assert ul3.spaced == self.a + self.b + assert self.ul.spaced == self.a ul3 = self.ul + self.l2 - self.assertEqual(ul3, ["things", "quirk", "y"]) - self.assertEqual(ul3.spaced, self.a + self.b) + assert ul3 == ["things", "quirk", "y"] + assert ul3.spaced == self.a + self.b def test_extend(self): ul3 = copy.deepcopy(self.ul) ul3.extend(self.ul2) - self.assertEqual(ul3, ["things", "quirk", "y"]) - self.assertEqual(ul3.spaced, self.a + self.b) - self.assertEqual(self.ul.spaced, self.a) + assert ul3 == ["things", "quirk", "y"] + assert ul3.spaced == self.a + self.b + assert self.ul.spaced == self.a def test_set(self): ul3 = copy.deepcopy(self.ul) ul3[0] = "zither" l = ["\n ", "zather", "zest"] ul3[1] = UnspacedList(l) - self.assertEqual(ul3, ["zither", ["zather", "zest"]]) - self.assertEqual(ul3.spaced, [self.a[0], "zither", " ", l]) + assert ul3 == ["zither", ["zather", "zest"]] + assert ul3.spaced == [self.a[0], "zither", " ", l] def test_get(self): - self.assertRaises(IndexError, self.ul2.__getitem__, 2) - self.assertRaises(IndexError, self.ul2.__getitem__, -3) + with pytest.raises(IndexError): + self.ul2.__getitem__(2) + with pytest.raises(IndexError): + self.ul2.__getitem__(-3) def test_insert(self): x = UnspacedList( @@ -475,17 +415,17 @@ class TestUnspacedList(unittest.TestCase): ['\n ', 'server_name', ' ', 'example.*'], '\n', ['listen', ' ', '5001', ' ', 'ssl']]) x.insert(5, "FROGZ") - self.assertEqual(x, + assert x == \ [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], - ['listen', '5001', 'ssl'], 'FROGZ']) - self.assertEqual(x.spaced, + ['listen', '5001', 'ssl'], 'FROGZ'] + assert x.spaced == \ [['\n ', 'listen', ' ', '69.50.225.155:9000'], ['\n ', 'listen', ' ', '127.0.0.1'], ['\n ', 'server_name', ' ', '.example.com'], ['\n ', 'server_name', ' ', 'example.*'], '\n', ['listen', ' ', '5001', ' ', 'ssl'], - 'FROGZ']) + 'FROGZ'] def test_rawlists(self): ul3 = copy.deepcopy(self.ul) @@ -493,19 +433,19 @@ class TestUnspacedList(unittest.TestCase): ul3.append("why") ul3.extend(["did", "whether"]) del ul3[2] - self.assertEqual(ul3, ["some", "things", "why", "did", "whether"]) + assert ul3 == ["some", "things", "why", "did", "whether"] def test_is_dirty(self): - self.assertIs(self.ul2.is_dirty(), False) + assert self.ul2.is_dirty() is False ul3 = UnspacedList([]) ul3.append(self.ul) - self.assertIs(self.ul.is_dirty(), False) - self.assertIs(ul3.is_dirty(), True) + assert self.ul.is_dirty() is False + assert ul3.is_dirty() is True ul4 = UnspacedList([[1], [2, 3, 4]]) - self.assertIs(ul4.is_dirty(), False) + assert ul4.is_dirty() is False ul4[1][2] = 5 - self.assertIs(ul4.is_dirty(), True) + assert ul4.is_dirty() is True if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-nginx/tests/obj_test.py b/certbot-nginx/tests/obj_test.py index de82e5682..43ac31d27 100644 --- a/certbot-nginx/tests/obj_test.py +++ b/certbot-nginx/tests/obj_test.py @@ -1,7 +1,10 @@ """Test the helper objects in certbot_nginx._internal.obj.""" import itertools +import sys import unittest +import pytest + class AddrTest(unittest.TestCase): """Test the Addr class.""" @@ -17,65 +20,65 @@ class AddrTest(unittest.TestCase): self.addr8 = Addr.fromstring("*:80 default ssl") def test_fromstring(self): - self.assertEqual(self.addr1.get_addr(), "192.168.1.1") - self.assertEqual(self.addr1.get_port(), "") - self.assertIs(self.addr1.ssl, False) - self.assertIs(self.addr1.default, False) + assert self.addr1.get_addr() == "192.168.1.1" + assert self.addr1.get_port() == "" + assert self.addr1.ssl is False + assert self.addr1.default is False - self.assertEqual(self.addr2.get_addr(), "192.168.1.1") - self.assertEqual(self.addr2.get_port(), "*") - self.assertIs(self.addr2.ssl, True) - self.assertIs(self.addr2.default, False) + assert self.addr2.get_addr() == "192.168.1.1" + assert self.addr2.get_port() == "*" + assert self.addr2.ssl is True + assert self.addr2.default is False - self.assertEqual(self.addr3.get_addr(), "192.168.1.1") - self.assertEqual(self.addr3.get_port(), "80") - self.assertIs(self.addr3.ssl, False) - self.assertIs(self.addr3.default, False) + assert self.addr3.get_addr() == "192.168.1.1" + assert self.addr3.get_port() == "80" + assert self.addr3.ssl is False + assert self.addr3.default is False - self.assertEqual(self.addr4.get_addr(), "*") - self.assertEqual(self.addr4.get_port(), "80") - self.assertIs(self.addr4.ssl, True) - self.assertIs(self.addr4.default, True) + assert self.addr4.get_addr() == "*" + assert self.addr4.get_port() == "80" + assert self.addr4.ssl is True + assert self.addr4.default is True - self.assertEqual(self.addr5.get_addr(), "myhost") - self.assertEqual(self.addr5.get_port(), "") - self.assertIs(self.addr5.ssl, False) - self.assertIs(self.addr5.default, False) + assert self.addr5.get_addr() == "myhost" + assert self.addr5.get_port() == "" + assert self.addr5.ssl is False + assert self.addr5.default is False - self.assertEqual(self.addr6.get_addr(), "") - self.assertEqual(self.addr6.get_port(), "80") - self.assertIs(self.addr6.ssl, False) - self.assertIs(self.addr6.default, True) + assert self.addr6.get_addr() == "" + assert self.addr6.get_port() == "80" + assert self.addr6.ssl is False + assert self.addr6.default is True - self.assertIs(self.addr8.default, True) + assert self.addr8.default is True - self.assertIsNone(self.addr7) + assert self.addr7 is None def test_str(self): - self.assertEqual(str(self.addr1), "192.168.1.1") - self.assertEqual(str(self.addr2), "192.168.1.1:* ssl") - self.assertEqual(str(self.addr3), "192.168.1.1:80") - self.assertEqual(str(self.addr4), "*:80 default_server ssl") - self.assertEqual(str(self.addr5), "myhost") - self.assertEqual(str(self.addr6), "80 default_server") - self.assertEqual(str(self.addr8), "*:80 default_server ssl") + assert str(self.addr1) == "192.168.1.1" + assert str(self.addr2) == "192.168.1.1:* ssl" + assert str(self.addr3) == "192.168.1.1:80" + assert str(self.addr4) == "*:80 default_server ssl" + assert str(self.addr5) == "myhost" + assert str(self.addr6) == "80 default_server" + assert str(self.addr8) == "*:80 default_server ssl" def test_to_string(self): - self.assertEqual(self.addr1.to_string(), "192.168.1.1") - self.assertEqual(self.addr2.to_string(), "192.168.1.1:* ssl") - self.assertEqual(self.addr3.to_string(), "192.168.1.1:80") - self.assertEqual(self.addr4.to_string(), "*:80 default_server ssl") - self.assertEqual(self.addr4.to_string(include_default=False), "*:80 ssl") - self.assertEqual(self.addr5.to_string(), "myhost") - self.assertEqual(self.addr6.to_string(), "80 default_server") - self.assertEqual(self.addr6.to_string(include_default=False), "80") + assert self.addr1.to_string() == "192.168.1.1" + assert self.addr2.to_string() == "192.168.1.1:* ssl" + assert self.addr3.to_string() == "192.168.1.1:80" + assert self.addr4.to_string() == "*:80 default_server ssl" + assert self.addr4.to_string(include_default=False) == "*:80 ssl" + assert self.addr5.to_string() == "myhost" + assert self.addr6.to_string() == "80 default_server" + assert self.addr6.to_string(include_default=False) == "80" def test_eq(self): from certbot_nginx._internal.obj import Addr new_addr1 = Addr.fromstring("192.168.1.1 spdy") - self.assertEqual(self.addr1, new_addr1) - self.assertNotEqual(self.addr1, self.addr2) - self.assertNotEqual(self.addr1, 3333) + assert self.addr1 == new_addr1 + assert self.addr1 != self.addr2 + assert self.addr1 != 3333 def test_equivalent_any_addresses(self): from certbot_nginx._internal.obj import Addr @@ -84,17 +87,16 @@ class AddrTest(unittest.TestCase): "*:80 default_server ssl", "80 default ssl") for first, second in itertools.combinations(any_addresses, 2): - self.assertEqual(Addr.fromstring(first), Addr.fromstring(second)) + assert Addr.fromstring(first) == Addr.fromstring(second) # Also, make sure ports are checked. - self.assertNotEqual(Addr.fromstring(any_addresses[0]), - Addr.fromstring("0.0.0.0:443 default_server ssl")) + assert Addr.fromstring(any_addresses[0]) != \ + Addr.fromstring("0.0.0.0:443 default_server ssl") # And they aren't equivalent to a specified address. for any_address in any_addresses: - self.assertNotEqual( - Addr.fromstring("192.168.1.2:80 default_server ssl"), - Addr.fromstring(any_address)) + assert Addr.fromstring("192.168.1.2:80 default_server ssl") != \ + Addr.fromstring(any_address) def test_set_inclusion(self): from certbot_nginx._internal.obj import Addr @@ -103,14 +105,14 @@ class AddrTest(unittest.TestCase): addr2b = Addr.fromstring("192.168.1.1:* ssl") set_b = {addr1b, addr2b} - self.assertEqual(set_a, set_b) + assert set_a == set_b class VirtualHostTest(unittest.TestCase): """Test the VirtualHost class.""" def setUp(self): - from certbot_nginx._internal.obj import VirtualHost from certbot_nginx._internal.obj import Addr + from certbot_nginx._internal.obj import VirtualHost raw1 = [ ['listen', '69.50.225.155:9000'], [['if', '($scheme', '!=', '"https") '], @@ -166,26 +168,26 @@ class VirtualHostTest(unittest.TestCase): {Addr.fromstring("localhost blah")}, False, False, {'localhost'}, [], []) - self.assertEqual(vhost1b, self.vhost1) - self.assertEqual(str(vhost1b), str(self.vhost1)) - self.assertNotEqual(vhost1b, 1234) + assert vhost1b == self.vhost1 + assert str(vhost1b) == str(self.vhost1) + assert vhost1b != 1234 def test_str(self): stringified = '\n'.join(['file: filep', 'addrs: localhost', "names: ['localhost']", 'ssl: False', 'enabled: False']) - self.assertEqual(stringified, str(self.vhost1)) + assert stringified == str(self.vhost1) def test_has_header(self): - self.assertIs(self.vhost_has_hsts.has_header('Strict-Transport-Security'), True) - self.assertIs(self.vhost_has_hsts.has_header('Bogus-Header'), False) - self.assertIs(self.vhost1.has_header('Strict-Transport-Security'), False) - self.assertIs(self.vhost1.has_header('Bogus-Header'), False) + assert self.vhost_has_hsts.has_header('Strict-Transport-Security') is True + assert self.vhost_has_hsts.has_header('Bogus-Header') is False + assert self.vhost1.has_header('Strict-Transport-Security') is False + assert self.vhost1.has_header('Bogus-Header') is False def test_contains_list(self): - from certbot_nginx._internal.obj import VirtualHost - from certbot_nginx._internal.obj import Addr from certbot_nginx._internal.configurator import _test_block_from_block + from certbot_nginx._internal.obj import Addr + from certbot_nginx._internal.obj import VirtualHost test_block = [ ['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'], ['\n'] @@ -221,9 +223,9 @@ class VirtualHostTest(unittest.TestCase): "filp", {Addr.fromstring("localhost")}, False, False, {'localhost'}, test_bad_haystack, []) - self.assertTrue(vhost_haystack.contains_list(test_needle)) - self.assertFalse(vhost_bad_haystack.contains_list(test_needle)) + assert vhost_haystack.contains_list(test_needle) + assert not vhost_bad_haystack.contains_list(test_needle) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-nginx/tests/parser_obj_test.py b/certbot-nginx/tests/parser_obj_test.py index 60ff1c975..7503897cc 100644 --- a/certbot-nginx/tests/parser_obj_test.py +++ b/certbot-nginx/tests/parser_obj_test.py @@ -1,8 +1,11 @@ """ Tests for functions and classes in parser_obj.py """ +import sys import unittest from unittest import mock +import pytest + from certbot_nginx._internal.parser_obj import COMMENT_BLOCK from certbot_nginx._internal.parser_obj import parse_raw @@ -10,52 +13,53 @@ from certbot_nginx._internal.parser_obj import parse_raw class CommentHelpersTest(unittest.TestCase): def test_is_comment(self): from certbot_nginx._internal.parser_obj import _is_comment - self.assertTrue(_is_comment(parse_raw(['#']))) - self.assertTrue(_is_comment(parse_raw(['#', ' literally anything else']))) - self.assertFalse(_is_comment(parse_raw(['not', 'even', 'a', 'comment']))) + assert _is_comment(parse_raw(['#'])) + assert _is_comment(parse_raw(['#', ' literally anything else'])) + assert not _is_comment(parse_raw(['not', 'even', 'a', 'comment'])) def test_is_certbot_comment(self): from certbot_nginx._internal.parser_obj import _is_certbot_comment - self.assertTrue(_is_certbot_comment( - parse_raw(COMMENT_BLOCK))) - self.assertFalse(_is_certbot_comment( - parse_raw(['#', ' not a certbot comment']))) - self.assertFalse(_is_certbot_comment( - parse_raw(['#', ' managed by Certbot', ' also not a certbot comment']))) - self.assertFalse(_is_certbot_comment( - parse_raw(['not', 'even', 'a', 'comment']))) + assert _is_certbot_comment( + parse_raw(COMMENT_BLOCK)) + assert not _is_certbot_comment( + parse_raw(['#', ' not a certbot comment'])) + assert not _is_certbot_comment( + parse_raw(['#', ' managed by Certbot', ' also not a certbot comment'])) + assert not _is_certbot_comment( + parse_raw(['not', 'even', 'a', 'comment'])) def test_certbot_comment(self): - from certbot_nginx._internal.parser_obj import _certbot_comment, _is_certbot_comment + from certbot_nginx._internal.parser_obj import _certbot_comment + from certbot_nginx._internal.parser_obj import _is_certbot_comment comment = _certbot_comment(None) - self.assertTrue(_is_certbot_comment(comment)) - self.assertEqual(comment.dump(), COMMENT_BLOCK) - self.assertEqual(comment.dump(True), [' '] + COMMENT_BLOCK) - self.assertEqual(_certbot_comment(None, 2).dump(True), [' '] + COMMENT_BLOCK) + assert _is_certbot_comment(comment) + assert comment.dump() == COMMENT_BLOCK + assert comment.dump(True) == [' '] + COMMENT_BLOCK + assert _certbot_comment(None, 2).dump(True) == [' '] + COMMENT_BLOCK class ParsingHooksTest(unittest.TestCase): def test_is_sentence(self): from certbot_nginx._internal.parser_obj import Sentence - self.assertFalse(Sentence.should_parse([])) - self.assertTrue(Sentence.should_parse([''])) - self.assertTrue(Sentence.should_parse(['word'])) - self.assertTrue(Sentence.should_parse(['two', 'words'])) - self.assertFalse(Sentence.should_parse([[]])) - self.assertFalse(Sentence.should_parse(['word', []])) + assert not Sentence.should_parse([]) + assert Sentence.should_parse(['']) + assert Sentence.should_parse(['word']) + assert Sentence.should_parse(['two', 'words']) + assert not Sentence.should_parse([[]]) + assert not Sentence.should_parse(['word', []]) def test_is_block(self): from certbot_nginx._internal.parser_obj import Block - self.assertFalse(Block.should_parse([])) - self.assertFalse(Block.should_parse([''])) - self.assertFalse(Block.should_parse(['two', 'words'])) - self.assertFalse(Block.should_parse([[[]], []])) - self.assertFalse(Block.should_parse([['block_name'], ['hi', []], []])) - self.assertFalse(Block.should_parse([['block_name'], 'lol'])) - self.assertTrue(Block.should_parse([['block_name'], ['hi', []]])) - self.assertTrue(Block.should_parse([['hello'], []])) - self.assertTrue(Block.should_parse([['block_name'], [['many'], ['statements'], 'here']])) - self.assertTrue(Block.should_parse([['if', ' ', '(whatever)'], ['hi']])) + assert not Block.should_parse([]) + assert not Block.should_parse(['']) + assert not Block.should_parse(['two', 'words']) + assert not Block.should_parse([[[]], []]) + assert not Block.should_parse([['block_name'], ['hi', []], []]) + assert not Block.should_parse([['block_name'], 'lol']) + assert Block.should_parse([['block_name'], ['hi', []]]) + assert Block.should_parse([['hello'], []]) + assert Block.should_parse([['block_name'], [['many'], ['statements'], 'here']]) + assert Block.should_parse([['if', ' ', '(whatever)'], ['hi']]) def test_parse_raw(self): fake_parser1 = mock.Mock() @@ -78,9 +82,11 @@ class ParsingHooksTest(unittest.TestCase): fake_parser1 = mock.Mock() fake_parser1.should_parse = lambda x: False parsing_hooks.return_value = (fake_parser1,) - self.assertRaises(errors.MisconfigurationError, parse_raw, []) + with pytest.raises(errors.MisconfigurationError): + parse_raw([]) parsing_hooks.return_value = () - self.assertRaises(errors.MisconfigurationError, parse_raw, []) + with pytest.raises(errors.MisconfigurationError): + parse_raw([]) def test_parse_raw_passes_add_spaces(self): fake_parser1 = mock.Mock() @@ -98,44 +104,47 @@ class SentenceTest(unittest.TestCase): def test_parse_bad_sentence_raises_error(self): from certbot import errors - self.assertRaises(errors.MisconfigurationError, self.sentence.parse, 'lol') - self.assertRaises(errors.MisconfigurationError, self.sentence.parse, [[]]) - self.assertRaises(errors.MisconfigurationError, self.sentence.parse, [5]) + with pytest.raises(errors.MisconfigurationError): + self.sentence.parse('lol') + with pytest.raises(errors.MisconfigurationError): + self.sentence.parse([[]]) + with pytest.raises(errors.MisconfigurationError): + self.sentence.parse([5]) def test_parse_sentence_words_hides_spaces(self): og_sentence = ['\r\n', 'hello', ' ', ' ', '\t\n ', 'lol', ' ', 'spaces'] self.sentence.parse(og_sentence) - self.assertEqual(self.sentence.words, ['hello', 'lol', 'spaces']) - self.assertEqual(self.sentence.dump(), ['hello', 'lol', 'spaces']) - self.assertEqual(self.sentence.dump(True), og_sentence) + assert self.sentence.words == ['hello', 'lol', 'spaces'] + assert self.sentence.dump() == ['hello', 'lol', 'spaces'] + assert self.sentence.dump(True) == og_sentence def test_parse_sentence_with_add_spaces(self): self.sentence.parse(['hi', 'there'], add_spaces=True) - self.assertEqual(self.sentence.dump(True), ['hi', ' ', 'there']) + assert self.sentence.dump(True) == ['hi', ' ', 'there'] self.sentence.parse(['one', ' ', 'space', 'none'], add_spaces=True) - self.assertEqual(self.sentence.dump(True), ['one', ' ', 'space', ' ', 'none']) + assert self.sentence.dump(True) == ['one', ' ', 'space', ' ', 'none'] def test_iterate(self): expected = [['1', '2', '3']] self.sentence.parse(['1', ' ', '2', ' ', '3']) for i, sentence in enumerate(self.sentence.iterate()): - self.assertEqual(sentence.dump(), expected[i]) + assert sentence.dump() == expected[i] def test_set_tabs(self): self.sentence.parse(['tabs', 'pls'], add_spaces=True) self.sentence.set_tabs() - self.assertEqual(self.sentence.dump(True)[0], '\n ') + assert self.sentence.dump(True)[0] == '\n ' self.sentence.parse(['tabs', 'pls'], add_spaces=True) def test_get_tabs(self): self.sentence.parse(['no', 'tabs']) - self.assertEqual(self.sentence.get_tabs(), '') + assert self.sentence.get_tabs() == '' self.sentence.parse(['\n \n ', 'tabs']) - self.assertEqual(self.sentence.get_tabs(), ' ') + assert self.sentence.get_tabs() == ' ' self.sentence.parse(['\n\t ', 'tabs']) - self.assertEqual(self.sentence.get_tabs(), '\t ') + assert self.sentence.get_tabs() == '\t ' self.sentence.parse(['\n\t \n', 'tabs']) - self.assertEqual(self.sentence.get_tabs(), '') + assert self.sentence.get_tabs() == '' class BlockTest(unittest.TestCase): @@ -148,50 +157,54 @@ class BlockTest(unittest.TestCase): def test_iterate(self): # Iterates itself normally - self.assertEqual(self.bloc, next(self.bloc.iterate())) + assert self.bloc == next(self.bloc.iterate()) # Iterates contents while expanded expected = [self.bloc.dump()] + self.contents for i, elem in enumerate(self.bloc.iterate(expanded=True)): - self.assertEqual(expected[i], elem.dump()) + assert expected[i] == elem.dump() def test_iterate_match(self): # can match on contents while expanded - from certbot_nginx._internal.parser_obj import Block, Sentence + from certbot_nginx._internal.parser_obj import Block + from certbot_nginx._internal.parser_obj import Sentence expected = [['thing', '1'], ['thing', '2']] for i, elem in enumerate(self.bloc.iterate(expanded=True, match=lambda x: isinstance(x, Sentence) and 'thing' in x.words)): - self.assertEqual(expected[i], elem.dump()) + assert expected[i] == elem.dump() # can match on self - self.assertEqual(self.bloc, next(self.bloc.iterate( + assert self.bloc == next(self.bloc.iterate( expanded=True, - match=lambda x: isinstance(x, Block) and 'server' in x.names))) + match=lambda x: isinstance(x, Block) and 'server' in x.names)) def test_parse_with_added_spaces(self): import copy self.bloc.parse([copy.copy(self.name), self.contents], add_spaces=True) - self.assertEqual(self.bloc.dump(), [self.name, self.contents]) - self.assertEqual(self.bloc.dump(True), [ + assert self.bloc.dump() == [self.name, self.contents] + assert self.bloc.dump(True) == [ ['server', ' ', 'name', ' '], [['thing', ' ', '1'], ['thing', ' ', '2'], - ['another', ' ', 'one']]]) + ['another', ' ', 'one']]] def test_bad_parse_raises_error(self): from certbot import errors - self.assertRaises(errors.MisconfigurationError, self.bloc.parse, [[[]], [[]]]) - self.assertRaises(errors.MisconfigurationError, self.bloc.parse, ['lol']) - self.assertRaises(errors.MisconfigurationError, self.bloc.parse, ['fake', 'news']) + with pytest.raises(errors.MisconfigurationError): + self.bloc.parse([[[]], [[]]]) + with pytest.raises(errors.MisconfigurationError): + self.bloc.parse(['lol']) + with pytest.raises(errors.MisconfigurationError): + self.bloc.parse(['fake', 'news']) def test_set_tabs(self): self.bloc.set_tabs() - self.assertEqual(self.bloc.names.dump(True)[0], '\n ') + assert self.bloc.names.dump(True)[0] == '\n ' for elem in self.bloc.contents.dump(True)[:-1]: - self.assertEqual(elem[0], '\n ') - self.assertEqual(self.bloc.contents.dump(True)[-1][0], '\n') + assert elem[0] == '\n ' + assert self.bloc.contents.dump(True)[-1][0] == '\n' def test_get_tabs(self): self.bloc.parse([[' \n \t', 'lol'], []]) - self.assertEqual(self.bloc.get_tabs(), ' \t') + assert self.bloc.get_tabs() == ' \t' class StatementsTest(unittest.TestCase): def setUp(self): @@ -213,7 +226,7 @@ class StatementsTest(unittest.TestCase): self.statements.parse(self.raw) self.statements.set_tabs() for statement in self.statements.iterate(): - self.assertEqual(statement.dump(True)[0], '\n ') + assert statement.dump(True)[0] == '\n ' def test_set_tabs_with_parent(self): # Trailing whitespace should inherit from parent tabbing. @@ -222,36 +235,37 @@ class StatementsTest(unittest.TestCase): self.statements.parent.get_tabs.return_value = '\t\t' self.statements.set_tabs() for statement in self.statements.iterate(): - self.assertEqual(statement.dump(True)[0], '\n ') - self.assertEqual(self.statements.dump(True)[-1], '\n\t\t') + assert statement.dump(True)[0] == '\n ' + assert self.statements.dump(True)[-1] == '\n\t\t' def test_get_tabs(self): self.raw[0].insert(0, '\n \n \t') self.statements.parse(self.raw) - self.assertEqual(self.statements.get_tabs(), ' \t') + assert self.statements.get_tabs() == ' \t' self.statements.parse([]) - self.assertEqual(self.statements.get_tabs(), '') + assert self.statements.get_tabs() == '' def test_parse_with_added_spaces(self): self.statements.parse(self.raw, add_spaces=True) - self.assertEqual(self.statements.dump(True)[0], ['sentence', ' ', 'one']) + assert self.statements.dump(True)[0] == ['sentence', ' ', 'one'] def test_parse_bad_list_raises_error(self): from certbot import errors - self.assertRaises(errors.MisconfigurationError, self.statements.parse, 'lol not a list') + with pytest.raises(errors.MisconfigurationError): + self.statements.parse('lol not a list') def test_parse_hides_trailing_whitespace(self): self.statements.parse(self.raw + ['\n\n ']) - self.assertIsInstance(self.statements.dump()[-1], list) - self.assertIs(self.statements.dump(True)[-1].isspace(), True) - self.assertEqual(self.statements.dump(True)[-1], '\n\n ') + assert isinstance(self.statements.dump()[-1], list) + assert self.statements.dump(True)[-1].isspace() is True + assert self.statements.dump(True)[-1] == '\n\n ' def test_iterate(self): self.statements.parse(self.raw) expected = [['sentence', 'one'], ['sentence', 'two']] for i, elem in enumerate(self.statements.iterate(match=lambda x: 'sentence' in x)): - self.assertEqual(expected[i], elem.dump()) + assert expected[i] == elem.dump() if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-nginx/tests/parser_test.py b/certbot-nginx/tests/parser_test.py index 93a8dcedc..f9cdb819c 100644 --- a/certbot-nginx/tests/parser_test.py +++ b/certbot-nginx/tests/parser_test.py @@ -2,9 +2,10 @@ import glob import re import shutil -import unittest +import sys from typing import List -from unittest import mock + +import pytest from certbot import errors from certbot.compat import os @@ -26,7 +27,7 @@ class NginxParserTest(util.NginxTest): path = os.path.join(self.temp_dir, "etc_nginx/////" "ubuntu_nginx/../../etc_nginx") nparser = parser.NginxParser(path) - self.assertEqual(nparser.root, self.config_path) + assert nparser.root == self.config_path def test_root_absolute(self): curr_dir = os.getcwd() @@ -36,13 +37,13 @@ class NginxParserTest(util.NginxTest): # self.tempdir to ensure that we stay on the same drive. os.chdir(self.temp_dir) nparser = parser.NginxParser(os.path.relpath(self.config_path)) - self.assertEqual(nparser.root, self.config_path) + assert nparser.root == self.config_path finally: os.chdir(curr_dir) def test_root_no_trailing_slash(self): nparser = parser.NginxParser(self.config_path + os.path.sep) - self.assertEqual(nparser.root, self.config_path) + assert nparser.root == self.config_path def test_load(self): """Test recursive conf file parsing. @@ -50,7 +51,7 @@ class NginxParserTest(util.NginxTest): """ nparser = parser.NginxParser(self.config_path) nparser.load() - self.assertEqual({nparser.abs_path(x) for x in + assert {nparser.abs_path(x) for x in ['foo.conf', 'nginx.conf', 'server.conf', 'mime.types', 'sites-enabled/default', 'sites-enabled/both.com', @@ -61,27 +62,27 @@ class NginxParserTest(util.NginxTest): 'sites-enabled/globalssl.com', 'sites-enabled/ipv6.com', 'sites-enabled/ipv6ssl.com', - 'sites-enabled/example.net']}, - set(nparser.parsed.keys())) - self.assertEqual([['server_name', 'somename', 'alias', 'another.alias']], - nparser.parsed[nparser.abs_path('server.conf')]) - self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'], + 'sites-enabled/example.net']} == \ + set(nparser.parsed.keys()) + assert [['server_name', 'somename', 'alias', 'another.alias']] == \ + nparser.parsed[nparser.abs_path('server.conf')] + assert [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], - ['server_name', 'example.*']]]], + ['server_name', 'example.*']]]] == \ nparser.parsed[nparser.abs_path( - 'sites-enabled/example.com')]) + 'sites-enabled/example.com')] def test_abs_path(self): nparser = parser.NginxParser(self.config_path) if os.name != 'nt': - self.assertEqual('/etc/nginx/*', nparser.abs_path('/etc/nginx/*')) - self.assertEqual(os.path.join(self.config_path, 'foo/bar'), - nparser.abs_path('foo/bar')) + assert '/etc/nginx/*' == nparser.abs_path('/etc/nginx/*') + assert os.path.join(self.config_path, 'foo/bar') == \ + nparser.abs_path('foo/bar') else: - self.assertEqual('C:\\etc\\nginx\\*', nparser.abs_path('C:\\etc\\nginx\\*')) - self.assertEqual(os.path.join(self.config_path, 'foo\\bar'), - nparser.abs_path('foo\\bar')) + assert 'C:\\etc\\nginx\\*' == nparser.abs_path('C:\\etc\\nginx\\*') + assert os.path.join(self.config_path, 'foo\\bar') == \ + nparser.abs_path('foo\\bar') def test_filedump(self): @@ -90,14 +91,14 @@ class NginxParserTest(util.NginxTest): # pylint: disable=protected-access parsed = nparser._parse_files(nparser.abs_path( 'sites-enabled/example.com.test')) - self.assertEqual(4, len(glob.glob(nparser.abs_path('*.test')))) - self.assertEqual(10, len( - glob.glob(nparser.abs_path('sites-enabled/*.test')))) - self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'], + assert 4 == len(glob.glob(nparser.abs_path('*.test'))) + assert 10 == len( + glob.glob(nparser.abs_path('sites-enabled/*.test'))) + assert [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], - ['server_name', 'example.*']]]], - parsed[0]) + ['server_name', 'example.*']]]] == \ + parsed[0] def test__do_for_subarray(self): # pylint: disable=protected-access @@ -120,7 +121,7 @@ class NginxParserTest(util.NginxTest): len(x) >= 1 and x[0] == 2, lambda x, y, pts=paths: pts.append(y)) - self.assertEqual(paths, result) + assert paths == result def test_get_vhosts_global_ssl(self): nparser = parser.NginxParser(self.config_path) @@ -132,7 +133,7 @@ class NginxParserTest(util.NginxTest): True, True, {'globalssl.com'}, [], [0]) globalssl_com = [x for x in vhosts if 'globalssl.com' in x.filep][0] - self.assertEqual(vhost, globalssl_com) + assert vhost == globalssl_com def test_get_vhosts(self): nparser = parser.NginxParser(self.config_path) @@ -174,17 +175,17 @@ class NginxParserTest(util.NginxTest): '*.www.example.com'}, [], [2, 1, 0]) - self.assertEqual(19, len(vhosts)) + assert 19 == len(vhosts) example_com = [x for x in vhosts if 'example.com' in x.filep][0] - self.assertEqual(vhost3, example_com) + assert vhost3 == example_com default = [x for x in vhosts if 'default' in x.filep][0] - self.assertEqual(vhost4, default) + assert vhost4 == default fooconf = [x for x in vhosts if 'foo.conf' in x.filep][0] - self.assertEqual(vhost5, fooconf) + assert vhost5 == fooconf localhost = [x for x in vhosts if 'localhost' in x.names][0] - self.assertEqual(vhost1, localhost) + assert vhost1 == localhost somename = [x for x in vhosts if 'somename' in x.names][0] - self.assertEqual(vhost2, somename) + assert vhost2 == somename def test_has_ssl_on_directive(self): nparser = parser.NginxParser(self.config_path) @@ -193,18 +194,18 @@ class NginxParserTest(util.NginxTest): ['server_name', 'www.example.org'], [['location', '/'], [['root', 'html'], ['index', 'index.html index.htm']]] ], None) - self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) + assert not nparser.has_ssl_on_directive(mock_vhost) mock_vhost.raw = [['listen', '*:80', 'default_server', 'ssl'], ['server_name', '*.www.foo.com', '*.www.example.com'], ['root', '/home/ubuntu/sites/foo/']] - self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) + assert not nparser.has_ssl_on_directive(mock_vhost) mock_vhost.raw = [['listen', '80 ssl'], ['server_name', '*.www.foo.com', '*.www.example.com']] - self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) + assert not nparser.has_ssl_on_directive(mock_vhost) mock_vhost.raw = [['listen', '80'], ['ssl', 'on'], ['server_name', '*.www.foo.com', '*.www.example.com']] - self.assertIs(nparser.has_ssl_on_directive(mock_vhost), True) + assert nparser.has_ssl_on_directive(mock_vhost) is True def test_remove_server_directives(self): nparser = parser.NginxParser(self.config_path) @@ -223,12 +224,12 @@ class NginxParserTest(util.NginxTest): '/etc/ssl/cert2.pem']]) nparser.remove_server_directives(mock_vhost, 'foo') nparser.remove_server_directives(mock_vhost, 'ssl_certificate') - self.assertEqual(nparser.parsed[example_com], + assert nparser.parsed[example_com] == \ [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], - []]]]) + []]]] def test_add_server_directives(self): nparser = parser.NginxParser(self.config_path) @@ -242,7 +243,7 @@ class NginxParserTest(util.NginxTest): '/etc/ssl/cert.pem']]) ssl_re = re.compile(r'\n\s+ssl_certificate /etc/ssl/cert.pem') dump = nginxparser.dumps(nparser.parsed[nparser.abs_path('nginx.conf')]) - self.assertEqual(1, len(re.findall(ssl_re, dump))) + assert 1 == len(re.findall(ssl_re, dump)) example_com = nparser.abs_path('sites-enabled/example.com') names = {'.example.com', 'example.*'} @@ -254,7 +255,7 @@ class NginxParserTest(util.NginxTest): '/etc/ssl/cert2.pem']]) nparser.add_server_directives(mock_vhost, [['foo', 'bar']]) from certbot_nginx._internal.parser import COMMENT - self.assertEqual(nparser.parsed[example_com], + assert nparser.parsed[example_com] == \ [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], @@ -263,16 +264,15 @@ class NginxParserTest(util.NginxTest): ['#', COMMENT], ['ssl_certificate', '/etc/ssl/cert2.pem'], ['#', COMMENT], [], [] - ]]]) + ]]] server_conf = nparser.abs_path('server.conf') names = {'alias', 'another.alias', 'somename'} mock_vhost.filep = server_conf mock_vhost.names = names mock_vhost.path = [] - self.assertRaises(errors.MisconfigurationError, - nparser.add_server_directives, - mock_vhost, + with pytest.raises(errors.MisconfigurationError): + nparser.add_server_directives(mock_vhost, [['foo', 'bar'], ['ssl_certificate', '/etc/ssl/cert2.pem']]) @@ -289,7 +289,7 @@ class NginxParserTest(util.NginxTest): [['\n ', 'include', ' ', nparser.abs_path('comment_in_file.conf')]]) from certbot_nginx._internal.parser import COMMENT - self.assertEqual(nparser.parsed[example_com], + assert nparser.parsed[example_com] == \ [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], @@ -299,7 +299,6 @@ class NginxParserTest(util.NginxTest): ['include', nparser.abs_path('comment_in_file.conf')], ['#', COMMENT], []]]] -) def test_replace_server_directives(self): nparser = parser.NginxParser(self.config_path) @@ -309,24 +308,22 @@ class NginxParserTest(util.NginxTest): nparser.update_or_add_server_directives( mock_vhost, [['server_name', 'foobar.com']]) from certbot_nginx._internal.parser import COMMENT - self.assertEqual( - nparser.parsed[filep], + assert nparser.parsed[filep] == \ [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', 'foobar.com'], ['#', COMMENT], ['server_name', 'example.*'], [] - ]]]) + ]]] mock_vhost.names = {'foobar.com', 'example.*'} nparser.update_or_add_server_directives( mock_vhost, [['ssl_certificate', 'cert.pem']]) - self.assertEqual( - nparser.parsed[filep], + assert nparser.parsed[filep] == \ [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', 'foobar.com'], ['#', COMMENT], ['server_name', 'example.*'], [], ['ssl_certificate', 'cert.pem'], ['#', COMMENT], [], - ]]]) + ]]] def test_get_best_match(self): target_name = 'www.eff.org' @@ -362,8 +359,8 @@ class NginxParserTest(util.NginxTest): ('wildcard_start', '.efF.org')] for i, winner in enumerate(winners): - self.assertEqual(winner, - parser.get_best_match(target_name, names[i])) + assert winner == \ + parser.get_best_match(target_name, names[i]) def test_comment_directive(self): # pylint: disable=protected-access @@ -371,16 +368,17 @@ class NginxParserTest(util.NginxTest): ["\n", "a", " ", "b", "\n"], ["c", " ", "d"], ["\n", "e", " ", "f"]]) - from certbot_nginx._internal.parser import comment_directive, COMMENT_BLOCK + from certbot_nginx._internal.parser import COMMENT_BLOCK + from certbot_nginx._internal.parser import comment_directive comment_directive(block, 1) comment_directive(block, 0) - self.assertEqual(block.spaced, [ + assert block.spaced == [ ["\n", "a", " ", "b", "\n"], COMMENT_BLOCK, "\n", ["c", " ", "d"], COMMENT_BLOCK, - ["\n", "e", " ", "f"]]) + ["\n", "e", " ", "f"]] def test_comment_out_directive(self): server_block = nginxparser.loads(""" @@ -399,7 +397,7 @@ class NginxParserTest(util.NginxTest): _comment_out_directive(block, 4, "blah1") _comment_out_directive(block, 5, "blah2") _comment_out_directive(block, 6, "blah3") - self.assertEqual(block.spaced, [ + assert block.spaced == [ ['\n ', 'listen', ' ', '80'], ['\n ', 'root', ' ', '/var/www/html'], ['\n ', 'index', ' ', 'star.html'], @@ -407,41 +405,41 @@ class NginxParserTest(util.NginxTest): ['\n ', '#', ' ssl_session_timeout 1440m; # duplicated in blah1'], [' ', '#', ' ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # duplicated in blah2'], ['\n\n ', '#', ' ssl_prefer_server_ciphers on; # duplicated in blah3'], - '\n ']) + '\n '] def test_parse_server_raw_ssl(self): server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', '443'] ]) - self.assertFalse(server['ssl']) + assert not server['ssl'] server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', '443', 'ssl'] ]) - self.assertTrue(server['ssl']) + assert server['ssl'] server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', '443'], ['ssl', 'off'] ]) - self.assertFalse(server['ssl']) + assert not server['ssl'] server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', '443'], ['ssl', 'on'] ]) - self.assertTrue(server['ssl']) + assert server['ssl'] def test_parse_server_raw_unix(self): server = parser._parse_server_raw([ #pylint: disable=protected-access ['listen', 'unix:/var/run/nginx.sock'] ]) - self.assertEqual(len(server['addrs']), 0) + assert len(server['addrs']) == 0 def test_parse_server_global_ssl_applied(self): nparser = parser.NginxParser(self.config_path) server = nparser.parse_server([ ['listen', '443'] ]) - self.assertTrue(server['ssl']) + assert server['ssl'] def test_duplicate_vhost(self): nparser = parser.NginxParser(self.config_path) @@ -452,19 +450,19 @@ class NginxParserTest(util.NginxTest): nparser.filedump(ext='') # check properties of new vhost - self.assertIs(next(iter(new_vhost.addrs)).default, False) - self.assertNotEqual(new_vhost.path, default.path) + assert next(iter(new_vhost.addrs)).default is False + assert new_vhost.path != default.path # check that things are written to file correctly new_nparser = parser.NginxParser(self.config_path) new_vhosts = new_nparser.get_vhosts() new_defaults = [x for x in new_vhosts if 'default' in x.filep] - self.assertEqual(len(new_defaults), 2) + assert len(new_defaults) == 2 new_vhost_parsed = new_defaults[1] - self.assertIs(next(iter(new_vhost_parsed.addrs)).default, False) - self.assertEqual(next(iter(default.names)), next(iter(new_vhost_parsed.names))) - self.assertEqual(len(default.raw), len(new_vhost_parsed.raw)) - self.assertTrue(next(iter(default.addrs)).super_eq(next(iter(new_vhost_parsed.addrs)))) + assert next(iter(new_vhost_parsed.addrs)).default is False + assert next(iter(default.names)) == next(iter(new_vhost_parsed.names)) + assert len(default.raw) == len(new_vhost_parsed.raw) + assert next(iter(default.addrs)).super_eq(next(iter(new_vhost_parsed.addrs))) def test_duplicate_vhost_remove_ipv6only(self): nparser = parser.NginxParser(self.config_path) @@ -475,7 +473,7 @@ class NginxParserTest(util.NginxTest): nparser.filedump(ext='') for addr in new_vhost.addrs: - self.assertFalse(addr.ipv6only) + assert not addr.ipv6only identical_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=False) nparser.filedump(ext='') @@ -483,16 +481,16 @@ class NginxParserTest(util.NginxTest): called = False for addr in identical_vhost.addrs: if addr.ipv6: - self.assertTrue(addr.ipv6only) + assert addr.ipv6only called = True - self.assertTrue(called) + assert called def test_valid_unicode_characters(self): nparser = parser.NginxParser(self.config_path) path = nparser.abs_path('valid_unicode_comments.conf') parsed = nparser._parse_files(path) # pylint: disable=protected-access - self.assertEqual(['server'], parsed[0][2][0]) - self.assertEqual(['listen', '80'], parsed[0][2][1][3]) + assert ['server'] == parsed[0][2][0] + assert ['listen', '80'] == parsed[0][2][1][3] def test_valid_unicode_roundtrip(self): """This tests the parser's ability to load and save a config containing Unicode""" @@ -508,18 +506,18 @@ class NginxParserTest(util.NginxTest): path = nparser.abs_path('invalid_unicode_comments.conf') parsed = nparser._parse_files(path) # pylint: disable=protected-access - self.assertEqual([], parsed) - self.assertTrue(any( + assert [] == parsed + assert any( ('invalid character' in output) and ('UTF-8' in output) for output in log.output - )) + ) def test_valid_unicode_characters_in_ssl_options(self): nparser = parser.NginxParser(self.config_path) path = nparser.abs_path('valid_unicode_comments.conf') parsed = parser._parse_ssl_options(path) # pylint: disable=protected-access - self.assertEqual(['server'], parsed[2][0]) - self.assertEqual(['listen', '80'], parsed[2][1][3]) + assert ['server'] == parsed[2][0] + assert ['listen', '80'] == parsed[2][1][3] def test_invalid_unicode_characters_in_ssl_options(self): with self.assertLogs() as log: @@ -527,20 +525,12 @@ class NginxParserTest(util.NginxTest): path = nparser.abs_path('invalid_unicode_comments.conf') parsed = parser._parse_ssl_options(path) # pylint: disable=protected-access - self.assertEqual([], parsed) - self.assertTrue(any( + assert [] == parsed + assert any( ('invalid character' in output) and ('UTF-8' in output) for output in log.output - )) - - @mock.patch('certbot_nginx._internal.parser.logger.warning') - def test_load_unsupported_directive_logged(self, mock_warn): - nparser = parser.NginxParser(self.config_path) - nparser.config_root = nparser.abs_path('unsupported_directives.conf') - nparser.load() - self.assertEqual(mock_warn.call_count, 1) - self.assertIn("which is not supported by Certbot", mock_warn.call_args[0][0]) + ) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot-nginx/tests/test_util.py b/certbot-nginx/tests/test_util.py index 1ac649318..d38c515b4 100644 --- a/certbot-nginx/tests/test_util.py +++ b/certbot-nginx/tests/test_util.py @@ -2,9 +2,9 @@ import copy import shutil import tempfile +from unittest import mock import josepy as jose -from unittest import mock import pkg_resources from certbot import util diff --git a/certbot-nginx/tests/testdata/etc_nginx/unsupported_directives.conf b/certbot-nginx/tests/testdata/etc_nginx/unsupported_directives.conf deleted file mode 100644 index d071c68c1..000000000 --- a/certbot-nginx/tests/testdata/etc_nginx/unsupported_directives.conf +++ /dev/null @@ -1,11 +0,0 @@ -# This configuration file contains unsupported direcives. - -server { - listen 80; - - location / { - foobar_by_lua_block { - ngx.say("Hello World") - } - } -} diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 25eaff6f2..59553ea8c 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,19 +2,42 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 2.3.0 - master +## 2.4.0 - master ### Added -* +* We deprecated support for the update_symlinks command. Support will be removed in a following + version of Certbot. ### Changed -* +* Docker build and deploy scripts now generate multiarch manifests for non-architecture-specific tags, instead of defaulting to amd64 images. ### Fixed -* +* Reverted [#9475](https://github.com/certbot/certbot/pull/9475) due to a performance regression in large nginx deployments. + +More details about these changes can be found on our GitHub repo. + +## 2.3.0 - 2023-02-14 + +### Added + +* Allow a user to modify the configuration of a certificate without renewing it using the new `reconfigure` subcommand. See `certbot help reconfigure` for details. +* `certbot show_account` now displays the [ACME Account Thumbprint](https://datatracker.ietf.org/doc/html/rfc8555#section-8.1). + +### Changed + +* Certbot will no longer save previous CSRs and certificate private keys to `/etc/letsencrypt/csr` and `/etc/letsencrypt/keys`, respectively. These directories may be safely deleted. +* Certbot will now only keep the current and 5 previous certificates in the `/etc/letsencrypt/archive` directory for each certificate lineage. Any prior certificates will be automatically deleted upon renewal. This number may be further lowered in future releases. + * As always, users should only reference the certificate files within `/etc/letsencrypt/live` and never use `/etc/letsencrypt/archive` directly. See [Where are my certificates?](https://eff-certbot.readthedocs.io/en/stable/using.html#where-are-my-certificates) in the Certbot User Guide. +* `certbot.configuration.NamespaceConfig.key_dir` and `.csr_dir` are now deprecated. +* All Certbot components now require `pytest` to run tests. + +### Fixed + +* Fixed a crash when registering an account with BuyPass' ACME server. +* Fixed a bug where Certbot would crash with `AttributeError: can't set attribute` on ACME server errors in Python 3.11. See [GH #9539](https://github.com/certbot/certbot/issues/9539). More details about these changes can be found on our GitHub repo. @@ -197,7 +220,7 @@ More details about these changes can be found on our GitHub repo. ### Added -* Updated Windows installer to be signed and trusted in Windows +* Updated Windows installer to be signed and trusted in Windows ### Changed diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index 331332bb7..dbe6c3787 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,3 +1,3 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '2.3.0.dev0' +__version__ = '2.4.0.dev0' diff --git a/certbot/certbot/_internal/account.py b/certbot/certbot/_internal/account.py index eb2466f32..9c8146b63 100644 --- a/certbot/certbot/_internal/account.py +++ b/certbot/certbot/_internal/account.py @@ -126,18 +126,6 @@ class AccountMemoryStorage(interfaces.AccountStorage): raise errors.AccountNotFound(account_id) -class RegistrationResourceWithNewAuthzrURI(messages.RegistrationResource): - """A backwards-compatible RegistrationResource with a new-authz URI. - - Hack: Certbot versions pre-0.11.1 expect to load - new_authzr_uri as part of the account. Because people - sometimes switch between old and new versions, we will - continue to write out this field for some time so older - clients don't crash in that scenario. - """ - new_authzr_uri: str = jose.field('new_authzr_uri') - - class AccountFileStorage(interfaces.AccountStorage): """Accounts file storage. @@ -254,20 +242,19 @@ class AccountFileStorage(interfaces.AccountStorage): dir_path = self._prepare(account) self._create(account, dir_path) self._update_meta(account, dir_path) - self._update_regr(account, client, dir_path) + self._update_regr(account, dir_path) except IOError as error: raise errors.AccountStorageError(error) - def update_regr(self, account: Account, client: ClientV2) -> None: + def update_regr(self, account: Account) -> None: """Update the registration resource. :param Account account: account to update - :param ClientV2 client: ACME client associated to the account """ try: dir_path = self._prepare(account) - self._update_regr(account, client, dir_path) + self._update_regr(account, dir_path) except IOError as error: raise errors.AccountStorageError(error) @@ -358,22 +345,11 @@ class AccountFileStorage(interfaces.AccountStorage): with util.safe_open(self._key_path(dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) - def _update_regr(self, account: Account, acme: ClientV2, dir_path: str) -> None: + def _update_regr(self, account: Account, dir_path: str) -> None: with open(self._regr_path(dir_path), "w") as regr_file: - regr = account.regr - # If we have a value for new-authz, save it for forwards - # compatibility with older versions of Certbot. If we don't - # have a value for new-authz, this is an ACMEv2 directory where - # an older version of Certbot won't work anyway. - if hasattr(acme.directory, "new-authz"): - regr = RegistrationResourceWithNewAuthzrURI( - new_authzr_uri=acme.directory.new_authz, - body={}, - uri=regr.uri) - else: - regr = messages.RegistrationResource( - body={}, - uri=regr.uri) + regr = messages.RegistrationResource( + body={}, + uri=account.regr.uri) regr_file.write(regr.json_dumps()) def _update_meta(self, account: Account, dir_path: str) -> None: diff --git a/certbot/certbot/_internal/cli/__init__.py b/certbot/certbot/_internal/cli/__init__.py index d3f4a2cbe..2719f0641 100644 --- a/certbot/certbot/_internal/cli/__init__.py +++ b/certbot/certbot/_internal/cli/__init__.py @@ -115,17 +115,12 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st "-d", "--domains", "--domain", dest="domains", metavar="DOMAIN", action=_DomainsAction, default=flag_default("domains"), - help="Domain names to apply. For multiple domains you can use " - "multiple -d flags or enter a comma separated list of domains " - "as a parameter. The first domain provided will be the " - "subject CN of the certificate, and all domains will be " - "Subject Alternative Names on the certificate. " - "The first domain will also be used in " - "some software user interfaces and as the file paths for the " - "certificate and related material unless otherwise " - "specified or you already have a certificate with the same " - "name. In the case of a name collision it will append a number " - "like 0001 to the file path name. (default: Ask)") + help="Domain names to include. For multiple domains you can use multiple -d flags " + "or enter a comma separated list of domains as a parameter. All domains will " + "be included as Subject Alternative Names on the certificate. The first domain " + "will be used as the certificate name, unless otherwise specified or if you " + "already have a certificate with the same name. In the case of a name conflict, " + "a number like -0001 will be appended to the certificate name. (default: Ask)") helpful.add( [None, "run", "certonly", "register"], "--eab-kid", dest="eab_kid", @@ -140,7 +135,7 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st ) helpful.add( [None, "run", "certonly", "manage", "delete", "certificates", - "renew", "enhance"], "--cert-name", dest="certname", + "renew", "enhance", "reconfigure"], "--cert-name", dest="certname", metavar="CERTNAME", default=flag_default("certname"), help="Certificate name to apply. This name is used by Certbot for housekeeping " "and in file paths; it doesn't affect the content of the certificate itself. " @@ -162,6 +157,17 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st " roll back those changes. It also calls --pre-hook and --post-hook commands" " if they are defined because they may be necessary to accurately simulate" " renewal. --deploy-hook commands are not called.") + helpful.add( + ["testing", "renew", "certonly", "reconfigure"], + "--run-deploy-hooks", action="store_true", dest="run_deploy_hooks", + default=flag_default("run_deploy_hooks"), + help="When performing a test run using `--dry-run` or `reconfigure`, run any applicable" + " deploy hooks. This includes hooks set on the command line, saved in the" + " certificate's renewal configuration file, or present in the renewal-hooks directory." + " To exclude directory hooks, use --no-directory-hooks. The hook(s) will only" + " be run if the dry run succeeds, and will use the current active certificate, not" + " the temporary test certificate acquired during the dry run. This flag is recommended" + " when modifying the deploy hook using `reconfigure`.") helpful.add( ["register", "automation"], "--register-unsafely-without-email", action="store_true", default=flag_default("register_unsafely_without_email"), @@ -379,7 +385,7 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st default=flag_default("issuance_timeout"), help=config_help("issuance_timeout")) helpful.add( - "renew", "--pre-hook", + ["renew", "reconfigure"], "--pre-hook", help="Command to be run in a shell before obtaining any certificates." " Intended primarily for renewal, where it can be used to temporarily" " shut down a webserver that might conflict with the standalone" @@ -387,21 +393,21 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st " obtained/renewed. When renewing several certificates that have" " identical pre-hooks, only the first will be executed.") helpful.add( - "renew", "--post-hook", + ["renew", "reconfigure"], "--post-hook", help="Command to be run in a shell after attempting to obtain/renew" " certificates. Can be used to deploy renewed certificates, or to" " restart any servers that were stopped by --pre-hook. This is only" " run if an attempt was made to obtain/renew a certificate. If" " multiple renewed certificates have identical post-hooks, only" " one will be run.") - helpful.add("renew", "--renew-hook", + helpful.add(["renew", "reconfigure"], "--renew-hook", action=_RenewHookAction, help=argparse.SUPPRESS) helpful.add( "renew", "--no-random-sleep-on-renew", action="store_false", default=flag_default("random_sleep_on_renew"), dest="random_sleep_on_renew", help=argparse.SUPPRESS) helpful.add( - "renew", "--deploy-hook", action=_DeployHookAction, + ["renew", "reconfigure"], "--deploy-hook", action=_DeployHookAction, help='Command to be run in a shell once for each successfully' ' issued certificate. For this command, the shell variable' ' $RENEWED_LINEAGE will point to the config live subdirectory' diff --git a/certbot/certbot/_internal/cli/cli_constants.py b/certbot/certbot/_internal/cli/cli_constants.py index 64b94a8ab..cbf7b6a03 100644 --- a/certbot/certbot/_internal/cli/cli_constants.py +++ b/certbot/certbot/_internal/cli/cli_constants.py @@ -38,6 +38,7 @@ manage certificates: certificates Display information about certificates you have from Certbot revoke Revoke a certificate (supply --cert-name or --cert-path) delete Delete a certificate (supply --cert-name) + reconfigure Update a certificate's configuration (supply --cert-name) manage your account: register Create an ACME account diff --git a/certbot/certbot/_internal/cli/group_adder.py b/certbot/certbot/_internal/cli/group_adder.py index 96d58824b..69cb34383 100644 --- a/certbot/certbot/_internal/cli/group_adder.py +++ b/certbot/certbot/_internal/cli/group_adder.py @@ -16,7 +16,7 @@ def _add_all_groups(helpful: "helpful.HelpfulArgumentParser") -> None: helpful.add_group("paths", description="Flags for changing execution paths & servers") helpful.add_group("manage", description="Various subcommands and flags are available for managing your certificates:", - verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"]) + verbs=["certificates", "delete", "renew", "revoke", "reconfigure"]) # VERBS for verb, docs in VERB_HELP: diff --git a/certbot/certbot/_internal/cli/helpful.py b/certbot/certbot/_internal/cli/helpful.py index cf3615fd4..0dd72e53a 100644 --- a/certbot/certbot/_internal/cli/helpful.py +++ b/certbot/certbot/_internal/cli/helpful.py @@ -66,6 +66,7 @@ class HelpfulArgumentParser: "certificates": main.certificates, "delete": main.delete, "enhance": main.enhance, + "reconfigure": main.reconfigure, } # Get notification function for printing diff --git a/certbot/certbot/_internal/cli/plugins_parsing.py b/certbot/certbot/_internal/cli/plugins_parsing.py index d19825738..d01d9903b 100644 --- a/certbot/certbot/_internal/cli/plugins_parsing.py +++ b/certbot/certbot/_internal/cli/plugins_parsing.py @@ -22,9 +22,9 @@ def _plugins_parsing(helpful: "helpful.HelpfulArgumentParser", help="Name of the plugin that is both an authenticator and an installer." " Should not be used together with --authenticator or --installer. " "(default: Ask)") - helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"), - help="Authenticator plugin name.") - helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), + helpful.add(["plugins", "reconfigure"], "-a", "--authenticator", + default=flag_default("authenticator"), help="Authenticator plugin name.") + helpful.add(["plugins", "reconfigure"], "-i", "--installer", default=flag_default("installer"), help="Installer plugin name (also used to find domains).") helpful.add(["plugins", "certonly", "run", "install"], "--apache", action="store_true", default=flag_default("apache"), @@ -38,7 +38,7 @@ def _plugins_parsing(helpful: "helpful.HelpfulArgumentParser", helpful.add(["plugins", "certonly"], "--manual", action="store_true", default=flag_default("manual"), help="Provide laborious manual instructions for obtaining a certificate") - helpful.add(["plugins", "certonly"], "--webroot", action="store_true", + helpful.add(["plugins", "certonly", "reconfigure"], "--webroot", action="store_true", default=flag_default("webroot"), help="Obtain certificates by placing files in a webroot directory.") helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true", diff --git a/certbot/certbot/_internal/cli/verb_help.py b/certbot/certbot/_internal/cli/verb_help.py index b15be4631..f73b2b46a 100644 --- a/certbot/certbot/_internal/cli/verb_help.py +++ b/certbot/certbot/_internal/cli/verb_help.py @@ -1,7 +1,5 @@ """This module contain help information for verbs supported by certbot""" from certbot._internal.cli.cli_constants import SHORT_USAGE -from certbot._internal.cli.cli_utils import flag_default -from certbot.compat import os # The attributes here are: # short: a string that will be displayed by "certbot -h commands" @@ -84,13 +82,6 @@ VERB_HELP = [ "opts": 'Options for the "plugins" subcommand', "usage": "\n\n certbot plugins [options]\n\n" }), - ("update_symlinks", { - "short": "Recreate symlinks in your /etc/letsencrypt/live/ directory", - "opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand " - "or edited a renewal configuration file".format( - os.path.join(flag_default("config_dir"), "live"))), - "usage": "\n\n certbot update_symlinks [options]\n\n" - }), ("enhance", { "short": "Add security enhancements to your existing configuration", "opts": ("Helps to harden the TLS configuration by adding security enhancements " @@ -102,6 +93,11 @@ VERB_HELP = [ "opts": 'Options useful for the "show_account" subcommand:', "usage": "\n\n certbot show_account [options]\n\n" }), + ("reconfigure", { + "short": "Update renewal configuration for a certificate specified by --cert-name", + "opts": 'Common options that may be updated with the "reconfigure" subcommand:', + "usage": "\n\n certbot reconfigure --cert-name CERTNAME [options]\n\n" + }), ] diff --git a/certbot/certbot/_internal/client.py b/certbot/certbot/_internal/client.py index 89c0e498a..59f5f8a4e 100644 --- a/certbot/certbot/_internal/client.py +++ b/certbot/certbot/_internal/client.py @@ -2,9 +2,9 @@ import datetime import logging import platform -from typing import cast from typing import Any from typing import Callable +from typing import cast from typing import Dict from typing import IO from typing import List @@ -14,11 +14,11 @@ from typing import Tuple from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key import josepy as jose -import OpenSSL from josepy import ES256 from josepy import ES384 from josepy import ES512 from josepy import RS256 +import OpenSSL from acme import client as acme_client from acme import crypto_util as acme_crypto_util @@ -416,13 +416,13 @@ class Client: else: key = key or crypto_util.generate_key( key_size=key_size, - key_dir=self.config.key_dir, + key_dir=None, key_type=self.config.key_type, elliptic_curve=elliptic_curve, strict_permissions=self.config.strict_permissions, ) - csr = crypto_util.generate_csr(key, domains, self.config.csr_dir, - self.config.must_staple, self.config.strict_permissions) + csr = crypto_util.generate_csr( + key, domains, None, self.config.must_staple, self.config.strict_permissions) try: orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names) @@ -433,7 +433,7 @@ class Client: if self.config.allow_subset_of_names: successful_domains = self._successful_domains_from_error(error, domains) if successful_domains != domains and len(successful_domains) != 0: - return self._retry_obtain_certificate(key, csr, domains, successful_domains) + return self._retry_obtain_certificate(domains, successful_domains) raise authzr = orderr.authorizations auth_domains = {a.body.identifier.value for a in authzr} @@ -445,7 +445,7 @@ class Client: # domains contains a wildcard because the ACME spec forbids identifiers # in authzs from containing a wildcard character. if self.config.allow_subset_of_names and successful_domains != domains: - return self._retry_obtain_certificate(key, csr, domains, successful_domains) + return self._retry_obtain_certificate(domains, successful_domains) else: try: cert, chain = self.obtain_certificate_from_csr(csr, orderr) @@ -457,7 +457,7 @@ class Client: if self.config.allow_subset_of_names: successful_domains = self._successful_domains_from_error(error, domains) if successful_domains != domains and len(successful_domains) != 0: - return self._retry_obtain_certificate(key, csr, domains, successful_domains) + return self._retry_obtain_certificate(domains, successful_domains) raise def _get_order_and_authorizations(self, csr_pem: bytes, @@ -540,16 +540,12 @@ class Client: return successful_domains return [] - def _retry_obtain_certificate(self, key: util.Key, - csr: util.CSR, domains: List[str], successful_domains: List[str] + def _retry_obtain_certificate(self, domains: List[str], successful_domains: List[str] ) -> Tuple[bytes, bytes, util.Key, util.CSR]: failed_domains = [d for d in domains if d not in successful_domains] domains_list = ", ".join(failed_domains) display_util.notify("Unable to obtain a certificate with every requested " f"domain. Retrying without: {domains_list}") - if not self.config.dry_run: - os.remove(key.file) - os.remove(csr.file) return self.obtain_certificate(successful_domains) def _choose_lineagename(self, domains: List[str], certname: Optional[str]) -> str: diff --git a/certbot/certbot/_internal/constants.py b/certbot/certbot/_internal/constants.py index bd999bf6a..35ee5d54e 100644 --- a/certbot/certbot/_internal/constants.py +++ b/certbot/certbot/_internal/constants.py @@ -80,6 +80,7 @@ CLI_DEFAULTS: Dict[str, Any] = dict( # noqa eab_hmac_key=None, eab_kid=None, issuance_timeout=90, + run_deploy_hooks=False, # Subparsers num=None, diff --git a/certbot/certbot/_internal/hooks.py b/certbot/certbot/_internal/hooks.py index 76cda466a..0517e625a 100644 --- a/certbot/certbot/_internal/hooks.py +++ b/certbot/certbot/_internal/hooks.py @@ -166,7 +166,7 @@ def deploy_hook(config: configuration.NamespaceConfig, domains: List[str], """ if config.deploy_hook: _run_deploy_hook(config.deploy_hook, domains, - lineage_path, config.dry_run) + lineage_path, config.dry_run, config.run_deploy_hooks) def renew_hook(config: configuration.NamespaceConfig, domains: List[str], @@ -190,7 +190,7 @@ def renew_hook(config: configuration.NamespaceConfig, domains: List[str], executed_dir_hooks = set() if config.directory_hooks: for hook in list_hooks(config.renewal_deploy_hooks_dir): - _run_deploy_hook(hook, domains, lineage_path, config.dry_run) + _run_deploy_hook(hook, domains, lineage_path, config.dry_run, config.run_deploy_hooks) executed_dir_hooks.add(hook) if config.renew_hook: @@ -199,10 +199,11 @@ def renew_hook(config: configuration.NamespaceConfig, domains: List[str], config.renew_hook) else: _run_deploy_hook(config.renew_hook, domains, - lineage_path, config.dry_run) + lineage_path, config.dry_run, config.run_deploy_hooks) -def _run_deploy_hook(command: str, domains: List[str], lineage_path: str, dry_run: bool) -> None: +def _run_deploy_hook(command: str, domains: List[str], lineage_path: str, dry_run: bool, + run_deploy_hooks: bool) -> None: """Run the specified deploy-hook (if not doing a dry run). If dry_run is True, command is not run and a message is logged @@ -214,9 +215,10 @@ def _run_deploy_hook(command: str, domains: List[str], lineage_path: str, dry_ru :type domains: `list` of `str` :param str lineage_path: live directory path for the new cert :param bool dry_run: True iff Certbot is doing a dry run + :param bool run_deploy_hooks: True if deploy hooks should run despite Certbot doing a dry run """ - if dry_run: + if dry_run and not run_deploy_hooks: logger.info("Dry run: skipping deploy hook command: %s", command) return diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 0712f1962..880850db0 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -2,6 +2,7 @@ # pylint: disable=too-many-lines from contextlib import contextmanager +import copy import functools import logging.handlers import sys @@ -14,9 +15,11 @@ from typing import Optional from typing import Tuple from typing import TypeVar from typing import Union +import warnings import configobj import josepy as jose +from josepy import b64 from acme import client as acme_client from acme import errors as acme_errors @@ -948,7 +951,7 @@ def update_account(config: configuration.NamespaceConfig, # the v2 uri. Since it's the same object on disk, put it back to the v1 uri # so that we can also continue to use the account object with acmev1. acc.regr = acc.regr.update(uri=prev_regr_uri) - account_storage.update_regr(acc, cb_client.acme) + account_storage.update_regr(acc) if not config.email: display_util.notify("Any contact information associated " @@ -992,6 +995,9 @@ def show_account(config: configuration.NamespaceConfig, output = [f"Account details for server {config.server}:", f" Account URL: {regr.uri}"] + thumbprint = b64.b64encode(acc.key.thumbprint()).decode() + output.append(f" Account Thumbprint: {thumbprint}") + emails = [] for contact in regr.body.contact: @@ -1274,6 +1280,7 @@ def update_symlinks(config: configuration.NamespaceConfig, :rtype: None """ + warnings.warn("update_symlinks is deprecated and will be removed", PendingDeprecationWarning) cert_manager.update_live_symlinks(config) @@ -1654,6 +1661,127 @@ def make_or_verify_needed_dirs(config: configuration.NamespaceConfig) -> None: util.make_or_verify_dir(hook_dir, strict=config.strict_permissions) +def _report_reconfigure_results(renewal_file: str, orig_renewal_conf: configobj.ConfigObj) -> None: + """Reports the outcome of certificate renewal reconfiguration to the user. + + :param renewal_file: Path to the cert's renewal file + :type renewal_file: str + + :param orig_renewal_conf: Loaded original renewal configuration + :type orig_renewal_conf: configobj.ConfigObj + + :returns: `None` + :rtype: None + + """ + try: + final_renewal_conf = configobj.ConfigObj( + renewal_file, encoding='utf-8', default_encoding='utf-8') + except configobj.ConfigObjError: + raise errors.CertStorageError( + f'error parsing {renewal_file}') + + orig_renewal_params = orig_renewal_conf['renewalparams'] + final_renewal_params = final_renewal_conf['renewalparams'] + + if final_renewal_params == orig_renewal_params: + success_message = '\nNo changes were made to the renewal configuration.' + else: + success_message = '\nSuccessfully updated configuration.' + \ + '\nChanges will apply when the certificate renews.' + + display_util.notify(success_message) + + +def reconfigure(config: configuration.NamespaceConfig, + plugins: plugins_disco.PluginsRegistry) -> None: + """Allow the user to set new configuration options for an existing certificate without + forcing renewal. This can be used for things like authenticator, installer, and hooks, + but not for the domains on the cert, since those are only saved in the cert. + + :param config: Configuration object + :type config: configuration.NamespaceConfig + + :param plugins: List of plugins + :type plugins: plugins_disco.PluginsRegistry + + :raises errors.Error: if the dry run fails + :raises errors.ConfigurationError: if certificate could not be loaded + + """ + + if config.domains: + raise errors.ConfigurationError("You have specified domains, but this function cannot " + "be used to modify the domains in a certificate. If you would like to do so, follow " + "the instructions at https://certbot.org/change-cert-domain. Otherwise, remove the " + "domains from the command to continue reconfiguring. You can specify which certificate " + "you want on the command line with flag --cert-name instead.") + # While we could technically allow domains to be used to specify the certificate in addition to + # --cert-name, there's enough complexity with matching certs to domains that it's not worth it, + # to say nothing of the difficulty in explaining what exactly this subcommand can modify + + + # To make sure that the requested changes work, do a dry run. While setting up the dry run, + # we will set all the needed fields in config, which will then be saved upon success. + config.dry_run = True + + if not config.certname: + certname_question = "Which certificate would you like to reconfigure?" + config.certname = cert_manager.get_certnames( + config, "reconfigure", allow_multiple=False, + custom_prompt=certname_question)[0] + + certname = config.certname + + try: + renewal_file = storage.renewal_file_for_certname(config, certname) + except errors.CertStorageError: + raise errors.ConfigurationError(f"An existing certificate with name {certname} could not " + "be found. Run `certbot certificates` to list available certificates.") + + # figure this out before we modify config + if config.deploy_hook and not config.run_deploy_hooks: + msg = ("You are attempting to set a --deploy-hook. Would you like Certbot to run deploy " + "hooks when it performs a dry run with the new settings? This will run all " + "relevant deploy hooks, including directory hooks, unless --no-directory-hooks " + "is set. This will use the current active certificate, and not the temporary test " + "certificate acquired during the dry run.") + config.run_deploy_hooks = display_util.yesno(msg,"Run deploy hooks", + "Do not run deploy hooks", default=False) + + # cache previous version for later comparison + try: + orig_renewal_conf = configobj.ConfigObj( + renewal_file, encoding='utf-8', default_encoding='utf-8') + except configobj.ConfigObjError: + raise errors.CertStorageError( + f"error parsing {renewal_file}") + + lineage_config = copy.deepcopy(config) + try: + renewal_candidate = renewal.reconstitute(lineage_config, renewal_file) + except Exception as e: # pylint: disable=broad-except + raise errors.ConfigurationError(f"Renewal configuration file {renewal_file} " + f"(cert: {certname}) produced an unexpected error: {e}.") + if not renewal_candidate: + raise errors.ConfigurationError("Could not load certificate. See logs for errors.") + + # this is where lineage_config gets fully filled out (e.g. --apache will set auth and installer) + installer, auth = plug_sel.choose_configurator_plugins(lineage_config, plugins, "certonly") + le_client = _init_le_client(lineage_config, auth, installer) + + # renews cert as dry run to test that the new values are ok + # at this point, renewal_candidate.configuration has the old values, but will use + # the values from lineage_config when doing the dry run + _get_and_save_cert(le_client, lineage_config, certname=certname, + lineage=renewal_candidate) + + # this function will update lineage.configuration with the new values, and save it to disk + renewal_candidate.save_new_config_values(lineage_config) + + _report_reconfigure_results(renewal_file, orig_renewal_conf) + + @contextmanager def make_displayer(config: configuration.NamespaceConfig ) -> Generator[Union[display_obj.NoninteractiveDisplay, diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index df5168ea2..39f704f2f 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -53,7 +53,7 @@ CONFIG_ITEMS = set(itertools.chain( BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',))) -def _reconstitute(config: configuration.NamespaceConfig, +def reconstitute(config: configuration.NamespaceConfig, full_path: str) -> Optional[storage.RenewableCert]: """Try to instantiate a RenewableCert, updating config with relevant items. @@ -392,6 +392,7 @@ def renew_cert(config: configuration.NamespaceConfig, domains: Optional[List[str # TODO: Check return value of save_successor lineage.save_successor(prior_version, new_cert, new_key.pem, new_chain, config) lineage.update_all_links_to(lineage.latest_common_version()) + lineage.truncate() hooks.renew_hook(config, domains, lineage.live_dir) @@ -490,7 +491,7 @@ def handle_renewal_request(config: configuration.NamespaceConfig) -> None: # Note that this modifies config (to add back the configuration # elements from within the renewal configuration file). try: - renewal_candidate = _reconstitute(lineage_config, renewal_file) + renewal_candidate = reconstitute(lineage_config, renewal_file) except Exception as e: # pylint: disable=broad-except logger.error("Renewal configuration file %s (cert: %s) " "produced an unexpected error: %s. Skipping.", diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py index 978295cce..086d3882f 100644 --- a/certbot/certbot/_internal/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -1,4 +1,5 @@ """Renewable certificates storage.""" +# pylint: disable=too-many-lines import datetime import glob import logging @@ -6,6 +7,7 @@ import re import shutil import stat from typing import Any +from typing import cast from typing import Dict from typing import Iterable from typing import List @@ -16,8 +18,8 @@ from typing import Union import configobj from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey from cryptography.hazmat.primitives.serialization import load_pem_private_key import parsedatetime import pkg_resources @@ -45,6 +47,8 @@ README = "README" CURRENT_VERSION = pkg_resources.parse_version(certbot.__version__) BASE_PRIVKEY_MODE = 0o600 +# pylint: disable=too-many-lines + def renewal_conf_files(config: configuration.NamespaceConfig) -> List[str]: """Build a list of all renewal configuration files. @@ -1130,7 +1134,7 @@ class RenewableCert(interfaces.RenewableCert): password=None, backend=default_backend() ) - return key + return cast(Union[RSAPrivateKey, EllipticCurvePrivateKey], key) @property def private_key_type(self) -> str: @@ -1243,3 +1247,45 @@ class RenewableCert(interfaces.RenewableCert): self.configuration = config_with_defaults(self.configfile) return target_version + + def save_new_config_values(self, cli_config: configuration.NamespaceConfig) -> None: + """Save only the config information without writing the new cert. + + :param .NamespaceConfig cli_config: parsed command line + arguments + """ + self.cli_config = cli_config + symlinks = {kind: self.configuration[kind] for kind in ALL_FOUR} + # Update renewal config file + self.configfile = update_configuration( + self.lineagename, self.archive_dir, symlinks, cli_config) + self.configuration = config_with_defaults(self.configfile) + + def truncate(self, num_prior_certs_to_keep: int = 5) -> None: + """Delete unused historical certificate, chain and key items from the lineage. + + A certificate version will be deleted if it is: + 1. not the current target, and + 2. not a previous version within num_prior_certs_to_keep. + + :param num_prior_certs_to_keep: How many prior certificate versions to keep. + + """ + # Do not want to delete the current or the previous num_prior_certs_to_keep certs + current_version = self.latest_common_version() + versions_to_delete = set(self.available_versions("cert")) + versions_to_delete -= set(range(current_version, + current_version - 1 - num_prior_certs_to_keep, -1)) + archive = self.archive_dir + + # Delete the remaining lineage items kinds for those certificate versions. + for ver in versions_to_delete: + logger.debug("Deleting %s/cert%d.pem and related items during clean up", + archive, ver) + for kind in ALL_FOUR: + item_path = os.path.join(archive, f"{kind}{ver}.pem") + try: + if os.path.exists(item_path): + os.unlink(item_path) + except OSError: + logger.debug("Failed to clean up %s", item_path, exc_info=True) diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py index 1bf89a733..b975f95b5 100644 --- a/certbot/certbot/compat/filesystem.py +++ b/certbot/certbot/compat/filesystem.py @@ -8,8 +8,8 @@ import stat import sys from typing import Any from typing import Dict -from typing import List from typing import Generator +from typing import List from typing import Optional try: diff --git a/certbot/certbot/configuration.py b/certbot/certbot/configuration.py index dd40a096f..9e0d3f9de 100644 --- a/certbot/certbot/configuration.py +++ b/certbot/certbot/configuration.py @@ -5,6 +5,7 @@ from typing import Any from typing import List from typing import Optional from urllib import parse +import warnings from certbot import errors from certbot import util @@ -150,6 +151,8 @@ class NamespaceConfig: @property def csr_dir(self) -> str: """Directory where new Certificate Signing Requests (CSRs) are saved.""" + warnings.warn("NamespaceConfig.csr_dir is deprecated and will be removed in an upcoming " + "release of Certbot", DeprecationWarning) return os.path.join(self.namespace.config_dir, constants.CSR_DIR) @property @@ -160,6 +163,8 @@ class NamespaceConfig: @property def key_dir(self) -> str: """Keys storage.""" + warnings.warn("NamespaceConfig.key_dir is deprecated and will be removed in an upcoming " + "release of Certbot", DeprecationWarning) return os.path.join(self.namespace.config_dir, constants.KEY_DIR) @property diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py index a9a8269fe..d0240aefd 100644 --- a/certbot/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -45,13 +45,15 @@ from certbot.compat import os if TYPE_CHECKING: from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + from cryptography.hazmat.primitives.asymmetric.x448 import X448PublicKey + from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey logger = logging.getLogger(__name__) # High level functions -def generate_key(key_size: int, key_dir: str, key_type: str = "rsa", +def generate_key(key_size: int, key_dir: Optional[str], key_type: str = "rsa", elliptic_curve: str = "secp256r1", keyname: str = "key-certbot.pem", strict_permissions: bool = True) -> util.Key: """Initializes and saves a privkey. @@ -62,7 +64,7 @@ def generate_key(key_size: int, key_dir: str, key_type: str = "rsa", already exists at the path. :param int key_size: key size in bits if key size is rsa. - :param str key_dir: Key save directory. + :param str key_dir: Optional key save directory. :param str key_type: Key Type [rsa, ecdsa] :param str elliptic_curve: Name of the elliptic curve if key type is ecdsa. :param str keyname: Filename of key @@ -85,27 +87,29 @@ def generate_key(key_size: int, key_dir: str, key_type: str = "rsa", raise err # Save file - util.make_or_verify_dir(key_dir, 0o700, strict_permissions) - key_f, key_path = util.unique_file( - os.path.join(key_dir, keyname), 0o600, "wb") - with key_f: - key_f.write(key_pem) - if key_type == 'rsa': - logger.debug("Generating RSA key (%d bits): %s", key_size, key_path) - else: - logger.debug("Generating ECDSA key (%d bits): %s", key_size, key_path) + key_path = None + if key_dir: + util.make_or_verify_dir(key_dir, 0o700, strict_permissions) + key_f, key_path = util.unique_file( + os.path.join(key_dir, keyname), 0o600, "wb") + with key_f: + key_f.write(key_pem) + if key_type == 'rsa': + logger.debug("Generating RSA key (%d bits): %s", key_size, key_path) + else: + logger.debug("Generating ECDSA key (%d bits): %s", key_size, key_path) return util.Key(key_path, key_pem) -def generate_csr(privkey: util.Key, names: Union[List[str], Set[str]], path: str, +def generate_csr(privkey: util.Key, names: Union[List[str], Set[str]], path: Optional[str], must_staple: bool = False, strict_permissions: bool = True) -> util.CSR: """Initialize a CSR with the given private key. :param privkey: Key to include in the CSR :type privkey: :class:`certbot.util.Key` :param set names: `str` names to include in the CSR - :param str path: Certificate save directory. + :param str path: Optional certificate save directory. :param bool must_staple: If true, include the TLS Feature extension "OCSP Must-Staple" :param bool strict_permissions: If true and path exists, an exception is raised if the directory doesn't have 0755 permissions or isn't owned by the current user. @@ -117,13 +121,15 @@ def generate_csr(privkey: util.Key, names: Union[List[str], Set[str]], path: str csr_pem = acme_crypto_util.make_csr( privkey.pem, names, must_staple=must_staple) - # Save CSR - util.make_or_verify_dir(path, 0o755, strict_permissions) - csr_f, csr_filename = util.unique_file( - os.path.join(path, "csr-certbot.pem"), 0o644, "wb") - with csr_f: - csr_f.write(csr_pem) - logger.debug("Creating CSR: %s", csr_filename) + # Save CSR, if requested + csr_filename = None + if path: + util.make_or_verify_dir(path, 0o755, strict_permissions) + csr_f, csr_filename = util.unique_file( + os.path.join(path, "csr-certbot.pem"), 0o644, "wb") + with csr_f: + csr_f.write(csr_pem) + logger.debug("Creating CSR: %s", csr_filename) return util.CSR(csr_filename, csr_pem, "pem") @@ -239,11 +245,7 @@ def make_key(bits: int = 1024, key_type: str = "rsa", raise errors.Error("Unsupported elliptic curve: {}".format(elliptic_curve)) except UnsupportedAlgorithm as e: raise e from errors.Error(str(e)) - # This type ignore directive is required due to an outdated version of types-cryptography. - # It can be removed once package types-pyOpenSSL depends on cryptography instead of - # types-cryptography and so types-cryptography is not installed anymore. - # See https://github.com/python/typeshed/issues/5618 - _key_pem = _key.private_bytes( # type: ignore + _key_pem = _key.private_bytes( encoding=Encoding.PEM, format=PrivateFormat.TraditionalOpenSSL, encryption_algorithm=NoEncryption() @@ -254,10 +256,10 @@ def make_key(bits: int = 1024, key_type: str = "rsa", return crypto.dump_privatekey(crypto.FILETYPE_PEM, key) -def valid_privkey(privkey: str) -> bool: +def valid_privkey(privkey: Union[str, bytes]) -> bool: """Is valid RSA private key? - :param str privkey: Private key file contents in PEM + :param privkey: Private key file contents in PEM :returns: Validity of private key. :rtype: bool @@ -302,6 +304,7 @@ def verify_renewable_cert_sig(renewable_cert: interfaces.RenewableCert) -> None: with open(renewable_cert.cert_path, 'rb') as cert_file: cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend()) pk = chain.public_key() + assert cert.signature_hash_algorithm # always present for RSA and ECDSA verify_signed_payload(pk, cert.signature, cert.tbs_certificate_bytes, cert.signature_hash_algorithm) except (IOError, ValueError, InvalidSignature) as e: @@ -312,7 +315,8 @@ def verify_renewable_cert_sig(renewable_cert: interfaces.RenewableCert) -> None: def verify_signed_payload(public_key: Union[DSAPublicKey, 'Ed25519PublicKey', 'Ed448PublicKey', - EllipticCurvePublicKey, RSAPublicKey], + EllipticCurvePublicKey, RSAPublicKey, + 'X25519PublicKey', 'X448PublicKey'], signature: bytes, payload: bytes, signature_hash_algorithm: hashes.HashAlgorithm) -> None: """Check the signature of a payload. diff --git a/certbot/certbot/ocsp.py b/certbot/certbot/ocsp.py index 2a51fe834..8f558eb7b 100644 --- a/certbot/certbot/ocsp.py +++ b/certbot/certbot/ocsp.py @@ -24,7 +24,6 @@ from certbot import util from certbot.compat.os import getenv from certbot.interfaces import RenewableCert - logger = logging.getLogger(__name__) @@ -285,6 +284,7 @@ def _check_ocsp_response_signature(response_ocsp: 'ocsp.OCSPResponse', # Following line may raise UnsupportedAlgorithm chosen_cert_hash = responder_cert.signature_hash_algorithm + assert chosen_cert_hash # always present for RSA and ECDSA certificates. # For a delegate OCSP responder, we need first check that its certificate is effectively # signed by the certificate issuer. crypto_util.verify_signed_payload(issuer_cert.public_key(), responder_cert.signature, diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py index 1acee2dfc..a6086acad 100644 --- a/certbot/certbot/plugins/common.py +++ b/certbot/certbot/plugins/common.py @@ -19,12 +19,11 @@ from typing import TypeVar import pkg_resources from acme import challenges - from certbot import achallenges from certbot import configuration from certbot import crypto_util -from certbot import interfaces from certbot import errors +from certbot import interfaces from certbot import reverter from certbot._internal import constants from certbot.compat import filesystem diff --git a/certbot/certbot/plugins/dns_test_common.py b/certbot/certbot/plugins/dns_test_common.py index 65c9cc2c8..cb89cd4d9 100644 --- a/certbot/certbot/plugins/dns_test_common.py +++ b/certbot/certbot/plugins/dns_test_common.py @@ -2,10 +2,10 @@ from typing import Any from typing import Mapping from typing import TYPE_CHECKING +from unittest import mock import configobj import josepy as jose -from unittest import mock from acme import challenges from certbot import achallenges diff --git a/certbot/certbot/tests/util.py b/certbot/certbot/tests/util.py index 772fcd5a5..5db26c4cd 100644 --- a/certbot/certbot/tests/util.py +++ b/certbot/certbot/tests/util.py @@ -9,17 +9,18 @@ import sys import tempfile from typing import Any from typing import Callable -from typing import Union from typing import cast from typing import IO from typing import Iterable from typing import List from typing import Optional +from typing import Union import unittest from unittest import mock from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey import josepy as jose from OpenSSL import crypto import pkg_resources @@ -128,8 +129,9 @@ def load_rsa_private_key(*names: str) -> jose.ComparableRSAKey: loader_fn = serialization.load_pem_private_key else: loader_fn = serialization.load_der_private_key - return jose.ComparableRSAKey(loader_fn( - load_vector(*names), password=None, backend=default_backend())) + return jose.ComparableRSAKey( + cast(RSAPrivateKey, + loader_fn(load_vector(*names), password=None, backend=default_backend()))) def load_pyopenssl_private_key(*names: str) -> crypto.PKey: diff --git a/certbot/certbot/util.py b/certbot/certbot/util.py index 12507ef36..12427aa92 100644 --- a/certbot/certbot/util.py +++ b/certbot/certbot/util.py @@ -1,7 +1,6 @@ """Utilities for all Certbot.""" import argparse import atexit -import collections import errno import logging import platform @@ -14,6 +13,7 @@ from typing import Callable from typing import Dict from typing import IO from typing import List +from typing import NamedTuple from typing import Optional from typing import Set from typing import Tuple @@ -35,9 +35,18 @@ if _USE_DISTRO: logger = logging.getLogger(__name__) -Key = collections.namedtuple("Key", "file pem") -# Note: form is the type of data, "pem" or "der" -CSR = collections.namedtuple("CSR", "file data form") +class Key(NamedTuple): + """Container for an optional file path and contents for a PEM-formated private key.""" + file: Optional[str] + pem: bytes + + +class CSR(NamedTuple): + """Container for an optional file path and contents for a PEM or DER-formatted CSR.""" + file: Optional[str] + data: bytes + # Note: form is the type of data, "pem" or "der" + form: str # ANSI SGR escape codes diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index dd270e6eb..d2eb17da2 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -26,6 +26,7 @@ manage certificates: certificates Display information about certificates you have from Certbot revoke Revoke a certificate (supply --cert-name or --cert-path) delete Delete a certificate (supply --cert-name) + reconfigure Update a certificate's configuration (supply --cert-name) manage your account: register Create an ACME account @@ -35,7 +36,7 @@ manage your account: --agree-tos Agree to the ACME server's Subscriber Agreement -m EMAIL Email address for important account notifications -optional arguments: +options: -h, --help show this help message and exit -c CONFIG_FILE, --config CONFIG_FILE path to config file (default: /etc/letsencrypt/cli.ini @@ -58,18 +59,15 @@ optional arguments: it's not being run in a terminal. This flag cannot be used with the renew subcommand. (default: False) -d DOMAIN, --domains DOMAIN, --domain DOMAIN - Domain names to apply. For multiple domains you can + Domain names to include. For multiple domains you can use multiple -d flags or enter a comma separated list - of domains as a parameter. The first domain provided - will be the subject CN of the certificate, and all - domains will be Subject Alternative Names on the - certificate. The first domain will also be used in - some software user interfaces and as the file paths - for the certificate and related material unless - otherwise specified or you already have a certificate - with the same name. In the case of a name collision it - will append a number like 0001 to the file path name. - (default: Ask) + of domains as a parameter. All domains will be + included as Subject Alternative Names on the + certificate. The first domain will be used as the + certificate name, unless otherwise specified or if you + already have a certificate with the same name. In the + case of a name conflict, a number like -0001 will be + appended to the certificate name. (default: Ask) --eab-kid EAB_KID Key Identifier for External Account Binding (default: None) --eab-hmac-key EAB_HMAC_KEY @@ -126,7 +124,7 @@ optional arguments: case, and to know when to deprecate support for past Python versions and flags. If you wish to hide this information from the Let's Encrypt server, set this to - "". (default: CertbotACMEClient/2.2.0 (certbot; + "". (default: CertbotACMEClient/2.3.0 (certbot; OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the user agent are: --duplicate, @@ -229,6 +227,17 @@ security: testing: The following flags are meant for testing and integration purposes only. + --run-deploy-hooks When performing a test run using `--dry-run` or + `reconfigure`, run any applicable deploy hooks. This + includes hooks set on the command line, saved in the + certificate's renewal configuration file, or present + in the renewal-hooks directory. To exclude directory + hooks, use --no-directory-hooks. The hook(s) will only + be run if the dry run succeeds, and will use the + current active certificate, not the temporary test + certificate acquired during the dry run. This flag is + recommended when modifying the deploy hook using + `reconfigure`. (default: False) --test-cert, --staging Use the staging server to obtain or revoke test (invalid) certificates; equivalent to --server @@ -288,6 +297,8 @@ manage: --cert-name update_symlinks Recreate symlinks in your /etc/letsencrypt/live/ directory + reconfigure Update renewal configuration for a certificate + specified by --cert-name run: Options for obtaining & installing certificates @@ -432,6 +443,9 @@ enhance: show_account: Options useful for the "show_account" subcommand: +reconfigure: + Common options that may be updated with the "reconfigure" subcommand: + plugins: Plugin Selection: Certbot client supports an extensible plugins architecture. See 'certbot plugins' for a list of all installed plugins @@ -707,10 +721,16 @@ null: Null Installer standalone: - Spin up a temporary webserver + Runs an HTTP server locally which serves the necessary validation files + under the /.well-known/acme-challenge/ request path. Suitable if there is + no HTTP server already running. HTTP challenge only (wildcards not + supported). webroot: - Place files in webroot directory + Saves the necessary validation files to a .well-known/acme-challenge/ + directory within the nominated webroot path. A seperate HTTP server must + be running and serving files from the webroot path. HTTP challenge only + (wildcards not supported). --webroot-path WEBROOT_PATH, -w WEBROOT_PATH public_html / webroot path. This can be specified diff --git a/certbot/docs/conf.py b/certbot/docs/conf.py index de9e287fc..5102e7a1e 100644 --- a/certbot/docs/conf.py +++ b/certbot/docs/conf.py @@ -286,7 +286,7 @@ latex_documents = [ man_pages = [ ('index', 'certbot', u'Certbot Documentation', [project], 7), - ('man/certbot', 'certbot', u'certbot script documentation', + ('man/certbot', 'certbot', u"Automatically configure HTTPS using Let's Encrypt", [project], 1), ] diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index 6d1faf061..f86bed01a 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -130,7 +130,7 @@ For debugging, we recommend putting Once you are done with your code changes, and the tests in ``foo_test.py`` pass, run all of the unit tests for Certbot and check for coverage with ``tox --e py3-cover``. You should then check for code style with ``tox -e lint`` (all +-e cover``. You should then check for code style with ``tox -e lint`` (all files) or ``pylint --rcfile=.pylintrc path/to/file.py`` (single file at a time). @@ -585,8 +585,8 @@ include our snaps, Docker images, Windows installer, CI, and our development environments. In most cases, the file where dependency versions are specified is -``tools/requirements.txt``. There are three exceptions to this. The first is -our "oldest" tests where ``tools/oldest_constraints.txt`` is used instead. The +``tools/requirements.txt``. There are two exceptions to this. The first is our +"oldest" tests where ``tools/oldest_constraints.txt`` is used instead. The purpose of the "oldest" tests is to ensure Certbot continues to work with the oldest versions of our dependencies which we claim to support. The oldest versions of the dependencies we support should also be declared in our setup.py @@ -598,12 +598,6 @@ leave Certbot's dependencies unpinned. The thinking behind this test is to help us learn about breaking changes in our dependencies so that we can respond accordingly. -The third exception is temporary and at ``tools/1.32.x/requirements.txt``. -This file is simply a copy of ``tools/requirements.txt`` from our 1.32.x branch -to help us ensure those dependencies stay reasonably well updated while we still -have Certbot 1.x snap users. Once we've moved all snap users to Certbot 2.0, -this file should be deleted. - The choices of whether Certbot's dependencies are pinned and what file is used if they are should be automatically handled for you most of the time by Certbot's tooling. The way it works though is ``tools/pip_install.py`` (which diff --git a/certbot/docs/man/certbot.rst b/certbot/docs/man/certbot.rst index 2f25504b0..cc690d2da 100644 --- a/certbot/docs/man/certbot.rst +++ b/certbot/docs/man/certbot.rst @@ -1,3 +1,23 @@ :orphan: -.. literalinclude:: ../cli-help.txt +======= +certbot +======= + +Synopsis +======== +The objective of Certbot, Let's Encrypt, and the ACME (Automated Certificate Management +Environment) protocol is to make it possible to set up an HTTPS server and have it automatically +obtain a browser-trusted certificate, without any human intervention. This is accomplished by +running a certificate management agent on the web server. + +This agent is used to: + +- Automatically prove to the Let's Encrypt CA that you control the website +- Obtain a browser-trusted certificate and set it up on your web server +- Keep track of when your certificate is going to expire, and renew it +- Help you revoke the certificate if that ever becomes necessary. + +Options +======= +.. literalinclude:: ../cli-help.txt \ No newline at end of file diff --git a/certbot/docs/using.rst b/certbot/docs/using.rst index f5e9652ca..198ca6c01 100644 --- a/certbot/docs/using.rst +++ b/certbot/docs/using.rst @@ -323,6 +323,7 @@ Porkbun_ Y N DNS Authentication for Porkbun Infomaniak_ Y N DNS Authentication using Infomaniak Domains API dns-multi_ Y N DNS authentication of 100+ providers using go-acme/lego dns-dnsmanager_ Y N DNS Authentication for dnsmanager.io +standalone-nfq_ Y N HTTP Authentication that works with any webserver (Linux only) ================== ==== ==== =============================================================== .. _haproxy: https://github.com/greenhost/certbot-haproxy @@ -347,6 +348,7 @@ dns-dnsmanager_ Y N DNS Authentication for dnsmanager.io .. _Infomaniak: https://github.com/Infomaniak/certbot-dns-infomaniak .. _dns-multi: https://github.com/alexzorin/certbot-dns-multi .. _dns-dnsmanager: https://github.com/stayallive/certbot-dns-dnsmanager +.. _standalone-nfq: https://github.com/alexzorin/certbot-standalone-nfq If you're interested, you can also :ref:`write your own plugin `. @@ -690,6 +692,26 @@ time, Certbot will remember these options and apply them once again. Sometimes, you may encounter the need to change some of these options for future certificate renewals. To achieve this, you will need to perform the following steps: +Certbot v2.3.0 and newer +~~~~~~~~~~~~~~~~~~~~~~~~ +The ``certbot reconfigure`` command can be used to change a certificate's renewal options. +This command will use the new renewal options to perform a test renewal against the Let's Encrypt staging server. +If this is successful, the new renewal options will be saved and will apply to future renewals. + +You will need to specify the ``--cert-name``, which can be found by running ``certbot certificates``. + +A list of common options that may be updated with the ``reconfigure`` command can be found by running +``certbot help reconfigure``. + +As a practical example, if you were using the ``webroot`` authenticator and had relocated your website to another directory, +you can change the ``--webroot-path`` to the new directory using the following command: + +.. code-block:: shell + + certbot reconfigure --cert-name example.com --webroot-path /path/to/new/location + +Certbot v2.2.0 and older +~~~~~~~~~~~~~~~~~~~~~~~~ 1. Perform a *dry run renewal* with the amended options on the command line. This allows you to confirm that the change is valid and will result in successful future renewals. 2. If the dry run is successful, perform a *live renewal* of the certificate. This will persist the change for future @@ -870,10 +892,6 @@ private key file, you will also need to use ``chgrp`` and ``chmod 0640`` to allow the server to read ``/etc/letsencrypt/live/$domain/privkey.pem``. -.. note:: ``/etc/letsencrypt/archive`` and ``/etc/letsencrypt/keys`` - contain all previous keys and certificates, while - ``/etc/letsencrypt/live`` symlinks to the latest versions. - The following files are available: ``privkey.pem`` diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index 0037de31e..1cf79d152 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -1,10 +1,12 @@ """Tests for certbot._internal.account.""" import datetime import json +import sys import unittest +from unittest import mock import josepy as jose -from unittest import mock +import pytest import pytz from acme import messages @@ -14,7 +16,6 @@ from certbot.compat import misc from certbot.compat import os import certbot.tests.util as test_util - KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) @@ -38,21 +39,19 @@ class AccountTest(unittest.TestCase): self.acc_no_meta = Account(self.regr, KEY) def test_init(self): - self.assertEqual(self.regr, self.acc.regr) - self.assertEqual(KEY, self.acc.key) - self.assertEqual(self.meta, self.acc_no_meta.meta) + assert self.regr == self.acc.regr + assert KEY == self.acc.key + assert self.meta == self.acc_no_meta.meta def test_id(self): - self.assertEqual( - self.acc.id, "7adac10320f585ddf118429c0c4af2cd") + assert self.acc.id == "7adac10320f585ddf118429c0c4af2cd" def test_slug(self): - self.assertEqual( - self.acc.slug, "test.certbot.org@2015-07-04T14:04:10Z (7ada)") + assert self.acc.slug == "test.certbot.org@2015-07-04T14:04:10Z (7ada)" def test_repr(self): - self.assertTrue(repr(self.acc).startswith( - " 3 - self.assertEqual(self.mock_auth.cleanup.call_count, 1) + assert self.mock_auth.cleanup.call_count == 1 # Test if list first element is http-01, use typ because it is an achall - self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") + assert self.mock_auth.cleanup.call_args[0][0][0].typ == "http-01" - self.assertEqual(len(authzr), 1) + assert len(authzr) == 1 def test_name1_http_01_1_acme_2(self): self._test_name1_http_01_1_common() @@ -122,17 +121,17 @@ class HandleAuthorizationsTest(unittest.TestCase): mock_order = mock.MagicMock(authorizations=[authzr]) authzr = self.handler.handle_authorizations(mock_order, self.mock_config) - self.assertEqual(self.mock_net.answer_challenge.call_count, 1) + assert self.mock_net.answer_challenge.call_count == 1 - self.assertEqual(self.mock_net.poll.call_count, 1) + assert self.mock_net.poll.call_count == 1 - self.assertEqual(self.mock_auth.cleanup.call_count, 1) + assert self.mock_auth.cleanup.call_count == 1 cleaned_up_achalls = self.mock_auth.cleanup.call_args[0][0] - self.assertEqual(len(cleaned_up_achalls), 1) - self.assertEqual(cleaned_up_achalls[0].typ, "http-01") + assert len(cleaned_up_achalls) == 1 + assert cleaned_up_achalls[0].typ == "http-01" # Length of authorizations list - self.assertEqual(len(authzr), 1) + assert len(authzr) == 1 def test_name3_http_01_3_common_acme_2(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES), @@ -143,14 +142,14 @@ class HandleAuthorizationsTest(unittest.TestCase): self.mock_net.poll.side_effect = _gen_mock_on_poll() authzr = self.handler.handle_authorizations(mock_order, self.mock_config) - self.assertEqual(self.mock_net.answer_challenge.call_count, 3) + assert self.mock_net.answer_challenge.call_count == 3 # Check poll call - self.assertEqual(self.mock_net.poll.call_count, 3) + assert self.mock_net.poll.call_count == 3 - self.assertEqual(self.mock_auth.cleanup.call_count, 1) + assert self.mock_auth.cleanup.call_count == 1 - self.assertEqual(len(authzr), 3) + assert len(authzr) == 3 def test_debug_challenges(self): config = mock.Mock(debug_challenges=True, verbose_count=0) @@ -164,15 +163,15 @@ class HandleAuthorizationsTest(unittest.TestCase): self.handler.handle_authorizations(mock_order, config) - self.assertEqual(self.mock_net.answer_challenge.call_count, 1) - self.assertEqual(self.mock_display.notification.call_count, 1) - self.assertIn('Pass "-v" for more info', - self.mock_display.notification.call_args[0][0]) - self.assertNotIn(f"http://{authzrs[0].body.identifier.value}/.well-known/acme-challenge/" + - b64encode(authzrs[0].body.challenges[0].chall.token).decode(), - self.mock_display.notification.call_args[0][0]) - self.assertNotIn(b64encode(account_key_thumbprint).decode(), - self.mock_display.notification.call_args[0][0]) + assert self.mock_net.answer_challenge.call_count == 1 + assert self.mock_display.notification.call_count == 1 + assert 'Pass "-v" for more info' in \ + self.mock_display.notification.call_args[0][0] + assert f"http://{authzrs[0].body.identifier.value}/.well-known/acme-challenge/" + \ + b64encode(authzrs[0].body.challenges[0].chall.token).decode() not in \ + self.mock_display.notification.call_args[0][0] + assert b64encode(account_key_thumbprint).decode() not in \ + self.mock_display.notification.call_args[0][0] def test_debug_challenges_verbose(self): config = mock.Mock(debug_challenges=True, verbose_count=1) @@ -190,19 +189,19 @@ class HandleAuthorizationsTest(unittest.TestCase): self.handler.handle_authorizations(mock_order, config) - self.assertEqual(self.mock_net.answer_challenge.call_count, 2) - self.assertEqual(self.mock_display.notification.call_count, 1) - self.assertNotIn('Pass "-v" for more info', - self.mock_display.notification.call_args[0][0]) - self.assertIn(f"http://{authzrs[0].body.identifier.value}/.well-known/acme-challenge/" + - b64encode(authzrs[0].body.challenges[0].chall.token).decode(), - self.mock_display.notification.call_args[0][0]) - self.assertIn(b64encode(account_key_thumbprint).decode(), - self.mock_display.notification.call_args[0][0]) - self.assertIn(f"_acme-challenge.{authzrs[1].body.identifier.value}", - self.mock_display.notification.call_args[0][0]) - self.assertIn(authzrs[1].body.challenges[0].validation(self.mock_account.key), - self.mock_display.notification.call_args[0][0]) + assert self.mock_net.answer_challenge.call_count == 2 + assert self.mock_display.notification.call_count == 1 + assert 'Pass "-v" for more info' not in \ + self.mock_display.notification.call_args[0][0] + assert f"http://{authzrs[0].body.identifier.value}/.well-known/acme-challenge/" + \ + b64encode(authzrs[0].body.challenges[0].chall.token).decode() in \ + self.mock_display.notification.call_args[0][0] + assert b64encode(account_key_thumbprint).decode() in \ + self.mock_display.notification.call_args[0][0] + assert f"_acme-challenge.{authzrs[1].body.identifier.value}" in \ + self.mock_display.notification.call_args[0][0] + assert authzrs[1].body.challenges[0].validation(self.mock_account.key) in \ + self.mock_display.notification.call_args[0][0] def test_perform_failure(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] @@ -210,9 +209,8 @@ class HandleAuthorizationsTest(unittest.TestCase): self.mock_auth.perform.side_effect = errors.AuthorizationError - self.assertRaises( - errors.AuthorizationError, self.handler.handle_authorizations, - mock_order, self.mock_config) + with pytest.raises(errors.AuthorizationError): + self.handler.handle_authorizations(mock_order, self.mock_config) def test_max_retries_exceeded(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] @@ -221,10 +219,10 @@ class HandleAuthorizationsTest(unittest.TestCase): # We will return STATUS_PENDING twice before returning STATUS_VALID. self.mock_net.poll.side_effect = _gen_mock_on_poll(retry=2) - with self.assertRaises(errors.AuthorizationError) as error: + with pytest.raises(errors.AuthorizationError, + match='All authorizations were not finalized by the CA.'): # We retry only once, so retries will be exhausted before STATUS_VALID is returned. self.handler.handle_authorizations(mock_order, self.mock_config, False, 1) - self.assertIn('All authorizations were not finalized by the CA.', str(error.exception)) @mock.patch('certbot._internal.auth_handler.time.sleep') def test_deadline_exceeded(self, mock_sleep): @@ -246,23 +244,23 @@ class HandleAuthorizationsTest(unittest.TestCase): self.mock_net.poll.side_effect = _gen_mock_on_poll(status=messages.STATUS_PENDING, wait_value=interval) - with self.assertRaises(errors.AuthorizationError) as error, \ - mock.patch('certbot._internal.auth_handler.datetime.datetime') as mock_dt: - mock_dt.now.side_effect = mock_now_effect - # Polling will only proceed for 30 minutes at most, so the second 20 minute sleep - # should be truncated and the polling should be aborted. - self.handler.handle_authorizations(mock_order, self.mock_config, False) - self.assertIn('All authorizations were not finalized by the CA.', str(error.exception)) + with pytest.raises(errors.AuthorizationError, + match='All authorizations were not finalized by the CA.'): + with mock.patch('certbot._internal.auth_handler.datetime.datetime') as mock_dt: + mock_dt.now.side_effect = mock_now_effect + # Polling will only proceed for 30 minutes at most, so the second 20 minute sleep + # should be truncated and the polling should be aborted. + self.handler.handle_authorizations(mock_order, self.mock_config, False) - self.assertEqual(mock_sleep.call_count, 3) # 1s, 20m and 10m sleep - self.assertEqual(mock_sleep.call_args_list[0][0][0], 1) - self.assertAlmostEqual(mock_sleep.call_args_list[1][0][0], interval - 1, delta=1) - self.assertAlmostEqual(mock_sleep.call_args_list[2][0][0], interval/2 - 1, delta=1) + assert mock_sleep.call_count == 3 # 1s, 20m and 10m sleep + assert mock_sleep.call_args_list[0][0][0] == 1 + assert abs(mock_sleep.call_args_list[1][0][0] - (interval - 1)) <= 1 + assert abs(mock_sleep.call_args_list[2][0][0] - (interval/2 - 1)) <= 1 def test_no_domains(self): mock_order = mock.MagicMock(authorizations=[]) - self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations, - mock_order, self.mock_config) + with pytest.raises(errors.AuthorizationError): + self.handler.handle_authorizations(mock_order, self.mock_config) def test_preferred_challenge_choice_common_acme_2(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] @@ -276,36 +274,32 @@ class HandleAuthorizationsTest(unittest.TestCase): self.mock_net.poll.side_effect = _gen_mock_on_poll() self.handler.handle_authorizations(mock_order, self.mock_config) - self.assertEqual(self.mock_auth.cleanup.call_count, 1) - self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") + assert self.mock_auth.cleanup.call_count == 1 + assert self.mock_auth.cleanup.call_args[0][0][0].typ == "http-01" def test_preferred_challenges_not_supported_acme_2(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] mock_order = mock.MagicMock(authorizations=authzrs) self.handler.pref_challs.append(challenges.DNS01.typ) - self.assertRaises( - errors.AuthorizationError, self.handler.handle_authorizations, - mock_order, self.mock_config) + with pytest.raises(errors.AuthorizationError): + self.handler.handle_authorizations(mock_order, self.mock_config) def test_dns_only_challenge_not_supported(self): authzrs = [gen_dom_authzr(domain="0", challs=[acme_util.DNS01])] mock_order = mock.MagicMock(authorizations=authzrs) - self.assertRaises( - errors.AuthorizationError, self.handler.handle_authorizations, - mock_order, self.mock_config) + with pytest.raises(errors.AuthorizationError): + self.handler.handle_authorizations(mock_order, self.mock_config) def test_perform_error(self): self.mock_auth.perform.side_effect = errors.AuthorizationError authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES) mock_order = mock.MagicMock(authorizations=[authzr]) - self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations, - mock_order, self.mock_config) + with pytest.raises(errors.AuthorizationError): + self.handler.handle_authorizations(mock_order, self.mock_config) - self.assertEqual(self.mock_auth.cleanup.call_count, 1) - self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") + assert self.mock_auth.cleanup.call_count == 1 + assert self.mock_auth.cleanup.call_args[0][0][0].typ == "http-01" def test_answer_error(self): self.mock_net.answer_challenge.side_effect = errors.AuthorizationError @@ -313,12 +307,10 @@ class HandleAuthorizationsTest(unittest.TestCase): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] mock_order = mock.MagicMock(authorizations=authzrs) - self.assertRaises( - errors.AuthorizationError, self.handler.handle_authorizations, - mock_order, self.mock_config) - self.assertEqual(self.mock_auth.cleanup.call_count, 1) - self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") + with pytest.raises(errors.AuthorizationError): + self.handler.handle_authorizations(mock_order, self.mock_config) + assert self.mock_auth.cleanup.call_count == 1 + assert self.mock_auth.cleanup.call_args[0][0][0].typ == "http-01" def test_incomplete_authzr_error(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] @@ -326,12 +318,10 @@ class HandleAuthorizationsTest(unittest.TestCase): self.mock_net.poll.side_effect = _gen_mock_on_poll(status=messages.STATUS_INVALID) with test_util.patch_display_util(): - with self.assertRaises(errors.AuthorizationError) as error: + with pytest.raises(errors.AuthorizationError, match='Some challenges have failed.'): self.handler.handle_authorizations(mock_order, self.mock_config, False) - self.assertIn('Some challenges have failed.', str(error.exception)) - self.assertEqual(self.mock_auth.cleanup.call_count, 1) - self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") + assert self.mock_auth.cleanup.call_count == 1 + assert self.mock_auth.cleanup.call_args[0][0][0].typ == "http-01" def test_best_effort(self): def _conditional_mock_on_poll(authzr): @@ -355,18 +345,16 @@ class HandleAuthorizationsTest(unittest.TestCase): valid_authzr = self.handler.handle_authorizations(mock_order, self.mock_config, True) # Because best_effort=True, we did not blow up. Instead ... - self.assertEqual(len(valid_authzr), 1) # ... the valid authzr has been processed - self.assertEqual(mock_report.call_count, 1) # ... the invalid authzr has been reported + assert len(valid_authzr) == 1 # ... the valid authzr has been processed + assert mock_report.call_count == 1 # ... the invalid authzr has been reported self.mock_net.poll.side_effect = _gen_mock_on_poll(status=messages.STATUS_INVALID) with test_util.patch_display_util(): - with self.assertRaises(errors.AuthorizationError) as error: + with pytest.raises(errors.AuthorizationError, match='All challenges have failed.'): + # Despite best_effort=True, process will fail because no authzr is valid. self.handler.handle_authorizations(mock_order, self.mock_config, True) - # Despite best_effort=True, process will fail because no authzr is valid. - self.assertIn('All challenges have failed.', str(error.exception)) - def test_validated_challenge_not_rerun(self): # With a pending challenge that is not supported by the plugin, we # expect an exception to be raised. @@ -375,9 +363,8 @@ class HandleAuthorizationsTest(unittest.TestCase): [acme_util.DNS01], [messages.STATUS_PENDING]) mock_order = mock.MagicMock(authorizations=[authzr]) - self.assertRaises( - errors.AuthorizationError, self.handler.handle_authorizations, - mock_order, self.mock_config) + with pytest.raises(errors.AuthorizationError): + self.handler.handle_authorizations(mock_order, self.mock_config) # With a validated challenge that is not supported by the plugin, we # expect the challenge to not be solved again and @@ -414,13 +401,13 @@ class HandleAuthorizationsTest(unittest.TestCase): authzrs, failed = self.handler.deactivate_valid_authorizations(orderr) - self.assertEqual(self.mock_net.deactivate_authorization.call_count, 2) - self.assertEqual(len(authzrs), 1) - self.assertEqual(len(failed), 1) - self.assertEqual(authzrs[0].body.identifier.value, "is_valid") - self.assertEqual(authzrs[0].body.status, messages.STATUS_DEACTIVATED) - self.assertEqual(failed[0].body.identifier.value, "is_valid_but_will_fail") - self.assertEqual(failed[0].body.status, messages.STATUS_VALID) + assert self.mock_net.deactivate_authorization.call_count == 2 + assert len(authzrs) == 1 + assert len(failed) == 1 + assert authzrs[0].body.identifier.value == "is_valid" + assert authzrs[0].body.status == messages.STATUS_DEACTIVATED + assert failed[0].body.identifier.value == "is_valid_but_will_fail" + assert failed[0].body.status == messages.STATUS_VALID def _gen_mock_on_poll(status=messages.STATUS_VALID, retry=0, wait_value=1): @@ -446,12 +433,10 @@ class ChallbToAchallTest(unittest.TestCase): return challb_to_achall(challb, "account_key", "domain") def test_it(self): - self.assertEqual( - self._call(acme_util.HTTP01_P), + assert self._call(acme_util.HTTP01_P) == \ achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.HTTP01_P, account_key="account_key", - domain="domain"), - ) + domain="domain") class GenChallengePathTest(unittest.TestCase): @@ -474,16 +459,16 @@ class GenChallengePathTest(unittest.TestCase): challbs = (acme_util.DNS01_P, acme_util.HTTP01_P) prefs = [challenges.DNS01, challenges.HTTP01] - self.assertEqual(self._call(challbs, prefs), (0,)) - self.assertEqual(self._call(challbs[::-1], prefs), (1,)) + assert self._call(challbs, prefs) == (0,) + assert self._call(challbs[::-1], prefs) == (1,) def test_not_supported(self): challbs = (acme_util.DNS01_P,) prefs = [challenges.HTTP01] # smart path fails because no challs in prefs satisfies combos - self.assertRaises( - errors.AuthorizationError, self._call, challbs, prefs) + with pytest.raises(errors.AuthorizationError): + self._call(challbs, prefs) class ReportFailedAuthzrsTest(unittest.TestCase): @@ -507,7 +492,7 @@ class ReportFailedAuthzrsTest(unittest.TestCase): } # Prevent future regressions if the error type changes - self.assertIsNotNone(kwargs["error"].description) + assert kwargs["error"].description is not None http_01 = messages.ChallengeBody(**kwargs) @@ -575,7 +560,7 @@ class ReportFailedAuthzrsTest(unittest.TestCase): self.mock_auth.auth_hint.side_effect = Exception self.handler = AuthHandler(self.mock_auth, mock.MagicMock(), mock.MagicMock(), []) self.handler._report_failed_authzrs([self.authzr1]) - self.assertEqual(mock_notify.call_count, 1) + assert mock_notify.call_count == 1 def gen_auth_resp(chall_list): @@ -592,4 +577,4 @@ def gen_dom_authzr(domain, challs): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 157d45b55..caacc520f 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -3,13 +3,16 @@ # pylint: disable=protected-access import re import shutil +import sys import tempfile import unittest - -import configobj from unittest import mock -from certbot import errors, configuration +import configobj +import pytest + +from certbot import configuration +from certbot import errors from certbot._internal.storage import ALL_FOUR from certbot.compat import filesystem from certbot.compat import os @@ -94,9 +97,8 @@ class UpdateLiveSymlinksTest(BaseCertManagerTest): for domain in self.domains: for kind in ALL_FOUR: os.chdir(os.path.dirname(self.config_files[domain][kind])) - self.assertEqual( - filesystem.realpath(filesystem.readlink(self.config_files[domain][kind])), - filesystem.realpath(archive_paths[domain][kind])) + assert filesystem.realpath(filesystem.readlink(self.config_files[domain][kind])) == \ + filesystem.realpath(archive_paths[domain][kind]) finally: os.chdir(prev_dir) @@ -135,7 +137,7 @@ class DeleteTest(storage_test.BaseRenewableCertTest): mock_util().yesno.return_value = False self.config.certname = "example.org" self._call() - self.assertEqual(mock_delete_files.call_count, 0) + assert mock_delete_files.call_count == 0 @test_util.patch_display_util() @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @@ -159,7 +161,7 @@ class DeleteTest(storage_test.BaseRenewableCertTest): mock_util().checklist.return_value = (display_util.OK, ["example.org"]) mock_util().yesno.return_value = False self._call() - self.assertEqual(mock_delete_files.call_count, 0) + assert mock_delete_files.call_count == 0 @test_util.patch_display_util() @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @@ -173,7 +175,7 @@ class DeleteTest(storage_test.BaseRenewableCertTest): self._call() mock_delete_files.assert_any_call(self.config, "example.org") mock_delete_files.assert_any_call(self.config, "other.org") - self.assertEqual(mock_delete_files.call_count, 2) + assert mock_delete_files.call_count == 2 @test_util.patch_display_util() @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @@ -185,7 +187,7 @@ class DeleteTest(storage_test.BaseRenewableCertTest): mock_util().checklist.return_value = (display_util.OK, ["example.org", "other.org"]) mock_util().yesno.return_value = False self._call() - self.assertEqual(mock_delete_files.call_count, 0) + assert mock_delete_files.call_count == 0 class CertificatesTest(BaseCertManagerTest): @@ -199,16 +201,16 @@ class CertificatesTest(BaseCertManagerTest): @test_util.patch_display_util() def test_certificates_parse_fail(self, mock_utility, mock_logger): self._certificates(self.config) - self.assertTrue(mock_logger.warning.called) #pylint: disable=no-member - self.assertTrue(mock_utility.called) + assert mock_logger.warning.called #pylint: disable=no-member + assert mock_utility.called @mock.patch('certbot._internal.cert_manager.logger') @test_util.patch_display_util() def test_certificates_quiet(self, mock_utility, mock_logger): self.config.quiet = True self._certificates(self.config) - self.assertIs(mock_utility.notification.called, False) - self.assertTrue(mock_logger.warning.called) #pylint: disable=no-member + assert mock_utility.notification.called is False + assert mock_logger.warning.called #pylint: disable=no-member @mock.patch('certbot.crypto_util.verify_renewable_cert') @mock.patch('certbot._internal.cert_manager.logger') @@ -220,10 +222,10 @@ class CertificatesTest(BaseCertManagerTest): mock_verifier.return_value = None mock_report.return_value = "" self._certificates(self.config) - self.assertIs(mock_logger.warning.called, False) - self.assertTrue(mock_report.called) - self.assertTrue(mock_utility.called) - self.assertTrue(mock_renewable_cert.called) + assert mock_logger.warning.called is False + assert mock_report.called + assert mock_utility.called + assert mock_renewable_cert.called @mock.patch('certbot._internal.cert_manager.logger') @test_util.patch_display_util() @@ -238,8 +240,8 @@ class CertificatesTest(BaseCertManagerTest): filesystem.makedirs(empty_config.renewal_configs_dir) self._certificates(empty_config) - self.assertIs(mock_logger.warning.called, False) - self.assertTrue(mock_utility.called) + assert mock_logger.warning.called is False + assert mock_utility.called shutil.rmtree(empty_tempdir) @mock.patch('certbot.crypto_util.get_serial_from_cert') @@ -247,9 +249,11 @@ class CertificatesTest(BaseCertManagerTest): def test_report_human_readable(self, mock_revoked, mock_serial): mock_revoked.return_value = None mock_serial.return_value = 1234567890 - from certbot._internal import cert_manager import datetime + import pytz + + from certbot._internal import cert_manager expiry = pytz.UTC.fromutc(datetime.datetime.utcnow()) cert = mock.MagicMock(lineagename="nameone") @@ -265,34 +269,34 @@ class CertificatesTest(BaseCertManagerTest): get_report = lambda: cert_manager._report_human_readable(mock_config, parsed_certs) out = get_report() - self.assertIn("INVALID: EXPIRED", out) + assert "INVALID: EXPIRED" in out cert.target_expiry += datetime.timedelta(hours=2) # pylint: disable=protected-access out = get_report() - self.assertIs('1 hour' in out or '2 hour(s)' in out, True) - self.assertIn('VALID', out) - self.assertNotIn('INVALID', out) + assert ('1 hour' in out or '2 hour(s)' in out) is True + assert 'VALID' in out + assert 'INVALID' not in out cert.target_expiry += datetime.timedelta(days=1) # pylint: disable=protected-access out = get_report() - self.assertIn('1 day', out) - self.assertNotIn('under', out) - self.assertIn('VALID', out) - self.assertNotIn('INVALID', out) + assert '1 day' in out + assert 'under' not in out + assert 'VALID' in out + assert 'INVALID' not in out cert.target_expiry += datetime.timedelta(days=2) # pylint: disable=protected-access out = get_report() - self.assertIn('3 days', out) - self.assertIn('VALID', out) - self.assertNotIn('INVALID', out) + assert '3 days' in out + assert 'VALID' in out + assert 'INVALID' not in out cert.is_test_cert = True mock_revoked.return_value = True out = get_report() - self.assertIn('INVALID: TEST_CERT, REVOKED', out) + assert 'INVALID: TEST_CERT, REVOKED' in out cert = mock.MagicMock(lineagename="indescribable") cert.target_expiry = expiry @@ -301,19 +305,19 @@ class CertificatesTest(BaseCertManagerTest): parsed_certs.append(cert) out = get_report() - self.assertEqual(len(re.findall("INVALID:", out)), 2) + assert len(re.findall("INVALID:", out)) == 2 mock_config.domains = ["thrice.named"] out = get_report() - self.assertEqual(len(re.findall("INVALID:", out)), 1) + assert len(re.findall("INVALID:", out)) == 1 mock_config.domains = ["nameone"] out = get_report() - self.assertEqual(len(re.findall("INVALID:", out)), 2) + assert len(re.findall("INVALID:", out)) == 2 mock_config.certname = "indescribable" out = get_report() - self.assertEqual(len(re.findall("INVALID:", out)), 1) + assert len(re.findall("INVALID:", out)) == 1 mock_config.certname = "horror" out = get_report() - self.assertEqual(len(re.findall("INVALID:", out)), 0) + assert len(re.findall("INVALID:", out)) == 0 class SearchLineagesTest(BaseCertManagerTest): @@ -327,9 +331,10 @@ class SearchLineagesTest(BaseCertManagerTest): mock_renewal_conf_files.return_value = ["badfile"] mock_renewable_cert.side_effect = errors.CertStorageError from certbot._internal import cert_manager + # pylint: disable=protected-access - self.assertEqual(cert_manager._search_lineages(self.config, lambda x: x, "check"), "check") - self.assertTrue(mock_make_or_verify_dir.called) + assert cert_manager._search_lineages(self.config, lambda x: x, "check") == "check" + assert mock_make_or_verify_dir.called class LineageForCertnameTest(BaseCertManagerTest): @@ -344,24 +349,24 @@ class LineageForCertnameTest(BaseCertManagerTest): mock_match = mock.Mock(lineagename="example.com") mock_renewable_cert.return_value = mock_match from certbot._internal import cert_manager - self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), mock_match) - self.assertTrue(mock_make_or_verify_dir.called) + assert cert_manager.lineage_for_certname(self.config, "example.com") == mock_match + assert mock_make_or_verify_dir.called @mock.patch('certbot.util.make_or_verify_dir') @mock.patch('certbot._internal.storage.renewal_file_for_certname') def test_no_match(self, mock_renewal_conf_file, mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "other.com.conf" from certbot._internal import cert_manager - self.assertIsNone(cert_manager.lineage_for_certname(self.config, "example.com")) - self.assertTrue(mock_make_or_verify_dir.called) + assert cert_manager.lineage_for_certname(self.config, "example.com") is None + assert mock_make_or_verify_dir.called @mock.patch('certbot.util.make_or_verify_dir') @mock.patch('certbot._internal.storage.renewal_file_for_certname') def test_no_renewal_file(self, mock_renewal_conf_file, mock_make_or_verify_dir): mock_renewal_conf_file.side_effect = errors.CertStorageError() from certbot._internal import cert_manager - self.assertIsNone(cert_manager.lineage_for_certname(self.config, "example.com")) - self.assertTrue(mock_make_or_verify_dir.called) + assert cert_manager.lineage_for_certname(self.config, "example.com") is None + assert mock_make_or_verify_dir.called class DomainsForCertnameTest(BaseCertManagerTest): @@ -378,17 +383,17 @@ class DomainsForCertnameTest(BaseCertManagerTest): mock_match.names.return_value = domains mock_renewable_cert.return_value = mock_match from certbot._internal import cert_manager - self.assertEqual(cert_manager.domains_for_certname(self.config, "example.com"), - domains) - self.assertTrue(mock_make_or_verify_dir.called) + assert cert_manager.domains_for_certname(self.config, "example.com") == \ + domains + assert mock_make_or_verify_dir.called @mock.patch('certbot.util.make_or_verify_dir') @mock.patch('certbot._internal.storage.renewal_file_for_certname') def test_no_match(self, mock_renewal_conf_file, mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "somefile.conf" from certbot._internal import cert_manager - self.assertIsNone(cert_manager.domains_for_certname(self.config, "other.com")) - self.assertTrue(mock_make_or_verify_dir.called) + assert cert_manager.domains_for_certname(self.config, "other.com") is None + assert mock_make_or_verify_dir.called class RenameLineageTest(BaseCertManagerTest): @@ -411,15 +416,18 @@ class RenameLineageTest(BaseCertManagerTest): # if not choices mock_renewal_conf_files.return_value = [] - self.assertRaises(errors.Error, self._call, self.config) + with pytest.raises(errors.Error): + self._call(self.config) mock_renewal_conf_files.return_value = ["one.conf"] util_mock = mock_get_utility() util_mock.menu.return_value = (display_util.CANCEL, 0) - self.assertRaises(errors.Error, self._call, self.config) + with pytest.raises(errors.Error): + self._call(self.config) util_mock.menu.return_value = (display_util.OK, -1) - self.assertRaises(errors.Error, self._call, self.config) + with pytest.raises(errors.Error): + self._call(self.config) @test_util.patch_display_util() def test_no_new_certname(self, mock_get_utility): @@ -428,10 +436,12 @@ class RenameLineageTest(BaseCertManagerTest): util_mock = mock_get_utility() util_mock.input.return_value = (display_util.CANCEL, "name") - self.assertRaises(errors.Error, self._call, self.config) + with pytest.raises(errors.Error): + self._call(self.config) util_mock.input.return_value = (display_util.OK, None) - self.assertRaises(errors.Error, self._call, self.config) + with pytest.raises(errors.Error): + self._call(self.config) @test_util.patch_display_util() @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @@ -439,8 +449,8 @@ class RenameLineageTest(BaseCertManagerTest): self.config.certname = "one" self.config.new_certname = "two" mock_lineage_for_certname.return_value = None - self.assertRaises(errors.ConfigurationError, - self._call, self.config) + with pytest.raises(errors.ConfigurationError): + self._call(self.config) @test_util.patch_display_util() @mock.patch("certbot._internal.storage.RenewableCert._check_symlinks") @@ -449,8 +459,8 @@ class RenameLineageTest(BaseCertManagerTest): self._call(self.config) from certbot._internal import cert_manager updated_lineage = cert_manager.lineage_for_certname(self.config, self.config.new_certname) - self.assertIsNotNone(updated_lineage) - self.assertEqual(updated_lineage.lineagename, self.config.new_certname) + assert updated_lineage is not None + assert updated_lineage.lineagename == self.config.new_certname @test_util.patch_display_util() @mock.patch("certbot._internal.storage.RenewableCert._check_symlinks") @@ -462,8 +472,8 @@ class RenameLineageTest(BaseCertManagerTest): self._call(self.config) from certbot._internal import cert_manager updated_lineage = cert_manager.lineage_for_certname(self.config, self.config.new_certname) - self.assertIsNotNone(updated_lineage) - self.assertEqual(updated_lineage.lineagename, self.config.new_certname) + assert updated_lineage is not None + assert updated_lineage.lineagename == self.config.new_certname @test_util.patch_display_util() @mock.patch("certbot._internal.storage.RenewableCert._check_symlinks") @@ -472,10 +482,12 @@ class RenameLineageTest(BaseCertManagerTest): # for example, don't rename to existing certname self.config.new_certname = "example.org" - self.assertRaises(errors.ConfigurationError, self._call, self.config) + with pytest.raises(errors.ConfigurationError): + self._call(self.config) self.config.new_certname = "one{0}two".format(os.path.sep) - self.assertRaises(errors.ConfigurationError, self._call, self.config) + with pytest.raises(errors.ConfigurationError): + self._call(self.config) class DuplicativeCertsTest(storage_test.BaseRenewableCertTest): @@ -496,24 +508,24 @@ class DuplicativeCertsTest(storage_test.BaseRenewableCertTest): # No overlap at all result = find_duplicative_certs( self.config, ['wow.net', 'hooray.org']) - self.assertEqual(result, (None, None)) + assert result == (None, None) # Totally identical result = find_duplicative_certs( self.config, ['example.com', 'www.example.com']) - self.assertTrue(result[0].configfile.filename.endswith('example.org.conf')) - self.assertIsNone(result[1]) + assert result[0].configfile.filename.endswith('example.org.conf') + assert result[1] is None # Superset result = find_duplicative_certs( self.config, ['example.com', 'www.example.com', 'something.new']) - self.assertIsNone(result[0]) - self.assertTrue(result[1].configfile.filename.endswith('example.org.conf')) + assert result[0] is None + assert result[1].configfile.filename.endswith('example.org.conf') # Partial overlap doesn't count result = find_duplicative_certs( self.config, ['example.com', 'something.new']) - self.assertEqual(result, (None, None)) + assert result == (None, None) class CertPathToLineageTest(storage_test.BaseRenewableCertTest): @@ -536,19 +548,20 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): return _archive_files(cli_config, filetype) def test_basic_match(self): - self.assertEqual('example.org', self._call(self.config)) + assert 'example.org' == self._call(self.config) def test_no_match_exists(self): bad_test_config = self.config bad_test_config.cert_path = os.path.join(self.config.config_dir, 'live', 'SailorMoon', 'fullchain.pem') - self.assertRaises(errors.Error, self._call, bad_test_config) + with pytest.raises(errors.Error): + self._call(bad_test_config) @mock.patch('certbot._internal.cert_manager._acceptable_matches') def test_options_fullchain(self, mock_acceptable_matches): mock_acceptable_matches.return_value = [lambda x: x.fullchain_path] self.config.fullchain_path = self.fullchain - self.assertEqual('example.org', self._call(self.config)) + assert 'example.org' == self._call(self.config) @mock.patch('certbot._internal.cert_manager._acceptable_matches') def test_options_cert_path(self, mock_acceptable_matches): @@ -556,7 +569,7 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): test_cert_path = os.path.join(self.config.config_dir, 'live', 'example.org', 'cert.pem') self.config.cert_path = test_cert_path - self.assertEqual('example.org', self._call(self.config)) + assert 'example.org' == self._call(self.config) @mock.patch('certbot._internal.cert_manager._acceptable_matches') def test_options_archive_cert(self, mock_acceptable_matches): @@ -564,7 +577,7 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): self.config.cert_path = os.path.join(self.config.config_dir, 'archive', 'example.org', 'cert11.pem') mock_acceptable_matches.return_value = [lambda x: self._archive_files(x, 'cert')] - self.assertEqual('example.org', self._call(self.config)) + assert 'example.org' == self._call(self.config) @mock.patch('certbot._internal.cert_manager._acceptable_matches') def test_options_archive_fullchain(self, mock_acceptable_matches): @@ -572,11 +585,11 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): 'example.org', 'fullchain11.pem') mock_acceptable_matches.return_value = [lambda x: self._archive_files(x, 'fullchain')] - self.assertEqual('example.org', self._call(self.config)) + assert 'example.org' == self._call(self.config) def test_only_path(self): self.config.cert_path = self.fullchain - self.assertEqual('example.org', self._call(self.config)) + assert 'example.org' == self._call(self.config) class MatchAndCheckOverlaps(storage_test.BaseRenewableCertTest): @@ -597,18 +610,20 @@ class MatchAndCheckOverlaps(storage_test.BaseRenewableCertTest): def test_basic_match(self): from certbot._internal.cert_manager import _acceptable_matches - self.assertEqual(['example.org'], self._call(self.config, _acceptable_matches(), - lambda x: self.config.cert_path, lambda x: x.lineagename)) + assert ['example.org'] == self._call(self.config, _acceptable_matches(), + lambda x: self.config.cert_path, lambda x: x.lineagename) @mock.patch('certbot._internal.cert_manager._search_lineages') def test_no_matches(self, mock_search_lineages): mock_search_lineages.return_value = [] - self.assertRaises(errors.Error, self._call, self.config, None, None, None) + with pytest.raises(errors.Error): + self._call(self.config, None, None, None) @mock.patch('certbot._internal.cert_manager._search_lineages') def test_too_many_matches(self, mock_search_lineages): mock_search_lineages.return_value = ['spider', 'dance'] - self.assertRaises(errors.OverlappingMatchFound, self._call, self.config, None, None, None) + with pytest.raises(errors.OverlappingMatchFound): + self._call(self.config, None, None, None) class GetCertnameTest(unittest.TestCase): @@ -629,10 +644,9 @@ class GetCertnameTest(unittest.TestCase): from certbot._internal import cert_manager prompt = "Which certificate would you" self.mock_get_utility().menu.return_value = (display_util.OK, 0) - self.assertEqual( - cert_manager.get_certnames( - self.config, "verb", allow_multiple=False), ['example.com']) - self.assertIn(prompt, self.mock_get_utility().menu.call_args[0][0]) + assert cert_manager.get_certnames( + self.config, "verb", allow_multiple=False) == ['example.com'] + assert prompt in self.mock_get_utility().menu.call_args[0][0] @mock.patch('certbot._internal.storage.renewal_conf_files') @mock.patch('certbot._internal.storage.lineagename_for_filename') @@ -642,12 +656,11 @@ class GetCertnameTest(unittest.TestCase): from certbot._internal import cert_manager prompt = "custom prompt" self.mock_get_utility().menu.return_value = (display_util.OK, 0) - self.assertEqual( - cert_manager.get_certnames( - self.config, "verb", allow_multiple=False, custom_prompt=prompt), - ['example.com']) - self.assertEqual(self.mock_get_utility().menu.call_args[0][0], - prompt) + assert cert_manager.get_certnames( + self.config, "verb", allow_multiple=False, custom_prompt=prompt) == \ + ['example.com'] + assert self.mock_get_utility().menu.call_args[0][0] == \ + prompt @mock.patch('certbot._internal.storage.renewal_conf_files') @mock.patch('certbot._internal.storage.lineagename_for_filename') @@ -656,10 +669,8 @@ class GetCertnameTest(unittest.TestCase): mock_name.return_value = 'example.com' from certbot._internal import cert_manager self.mock_get_utility().menu.return_value = (display_util.CANCEL, 0) - self.assertRaises( - errors.Error, - cert_manager.get_certnames, - self.config, "erroring_anyway", allow_multiple=False) + with pytest.raises(errors.Error): + cert_manager.get_certnames(self.config, "erroring_anyway", allow_multiple=False) @mock.patch('certbot._internal.storage.renewal_conf_files') @mock.patch('certbot._internal.storage.lineagename_for_filename') @@ -670,10 +681,9 @@ class GetCertnameTest(unittest.TestCase): prompt = "Which certificate(s) would you" self.mock_get_utility().checklist.return_value = (display_util.OK, ['example.com']) - self.assertEqual( - cert_manager.get_certnames( - self.config, "verb", allow_multiple=True), ['example.com']) - self.assertIn(prompt, self.mock_get_utility().checklist.call_args[0][0]) + assert cert_manager.get_certnames( + self.config, "verb", allow_multiple=True) == ['example.com'] + assert prompt in self.mock_get_utility().checklist.call_args[0][0] @mock.patch('certbot._internal.storage.renewal_conf_files') @mock.patch('certbot._internal.storage.lineagename_for_filename') @@ -684,13 +694,11 @@ class GetCertnameTest(unittest.TestCase): prompt = "custom prompt" self.mock_get_utility().checklist.return_value = (display_util.OK, ['example.com']) - self.assertEqual( - cert_manager.get_certnames( - self.config, "verb", allow_multiple=True, custom_prompt=prompt), - ['example.com']) - self.assertEqual( - self.mock_get_utility().checklist.call_args[0][0], - prompt) + assert cert_manager.get_certnames( + self.config, "verb", allow_multiple=True, custom_prompt=prompt) == \ + ['example.com'] + assert self.mock_get_utility().checklist.call_args[0][0] == \ + prompt @mock.patch('certbot._internal.storage.renewal_conf_files') @mock.patch('certbot._internal.storage.lineagename_for_filename') @@ -699,11 +707,9 @@ class GetCertnameTest(unittest.TestCase): mock_name.return_value = 'example.com' from certbot._internal import cert_manager self.mock_get_utility().checklist.return_value = (display_util.CANCEL, []) - self.assertRaises( - errors.Error, - cert_manager.get_certnames, - self.config, "erroring_anyway", allow_multiple=True) + with pytest.raises(errors.Error): + cert_manager.get_certnames(self.config, "erroring_anyway", allow_multiple=True) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 54abe2594..16e1f7d75 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -3,10 +3,13 @@ import argparse import copy from importlib import reload as reload_module import io +import sys import tempfile import unittest from unittest import mock +import pytest + from acme import challenges from certbot import errors from certbot._internal import cli @@ -17,7 +20,6 @@ from certbot.compat import os import certbot.tests.util as test_util from certbot.tests.util import TempDirTestCase - PLUGINS = disco.PluginsRegistry.find_all() @@ -39,16 +41,16 @@ class TestReadFile(TempDirTestCase): # POSIX systems. real_path = filesystem.realpath(os.path.join(self.tempdir, 'foo')) relative_path = os.path.relpath(real_path) - self.assertRaises( - argparse.ArgumentTypeError, cli.read_file, relative_path) + with pytest.raises(argparse.ArgumentTypeError): + cli.read_file(relative_path) test_contents = b'bar\n' with open(relative_path, 'wb') as f: f.write(test_contents) path, contents = cli.read_file(relative_path) - self.assertEqual(path, os.path.abspath(path)) - self.assertEqual(contents, test_contents) + assert path == os.path.abspath(path) + assert contents == test_contents finally: os.chdir(curr_dir) @@ -58,13 +60,13 @@ class FlagDefaultTest(unittest.TestCase): def test_default_directories(self): if os.name != 'nt': - self.assertEqual(cli.flag_default('config_dir'), '/etc/letsencrypt') - self.assertEqual(cli.flag_default('work_dir'), '/var/lib/letsencrypt') - self.assertEqual(cli.flag_default('logs_dir'), '/var/log/letsencrypt') + assert cli.flag_default('config_dir') == '/etc/letsencrypt' + assert cli.flag_default('work_dir') == '/var/lib/letsencrypt' + assert cli.flag_default('logs_dir') == '/var/log/letsencrypt' else: - self.assertEqual(cli.flag_default('config_dir'), 'C:\\Certbot') - self.assertEqual(cli.flag_default('work_dir'), 'C:\\Certbot\\lib') - self.assertEqual(cli.flag_default('logs_dir'), 'C:\\Certbot\\log') + assert cli.flag_default('config_dir') == 'C:\\Certbot' + assert cli.flag_default('work_dir') == 'C:\\Certbot\\lib' + assert cli.flag_default('logs_dir') == 'C:\\Certbot\\log' class ParseTest(unittest.TestCase): @@ -97,7 +99,8 @@ class ParseTest(unittest.TestCase): with test_util.patch_display_util() as mock_get_utility: mock_get_utility().notification.side_effect = write_msg with mock.patch('certbot._internal.main.sys.stderr'): - self.assertRaises(SystemExit, self._unmocked_parse, args, output) + with pytest.raises(SystemExit): + self._unmocked_parse(args, output) return output.getvalue() @@ -114,18 +117,18 @@ class ParseTest(unittest.TestCase): mock_flag_default.side_effect = shim namespace = self.parse(["certonly"]) - self.assertEqual(namespace.domains, []) + assert namespace.domains == [] with open(tmp_config.name, 'w') as file_h: file_h.write("domains = example.com") namespace = self.parse(["certonly"]) - self.assertEqual(namespace.domains, ["example.com"]) + assert namespace.domains == ["example.com"] namespace = self.parse(["renew"]) - self.assertEqual(namespace.domains, []) + assert namespace.domains == [] def test_no_args(self): namespace = self.parse([]) for d in ('config_dir', 'logs_dir', 'work_dir'): - self.assertEqual(getattr(namespace, d), cli.flag_default(d)) + assert getattr(namespace, d) == cli.flag_default(d) def test_install_abspath(self): cert = 'cert' @@ -138,136 +141,137 @@ class ParseTest(unittest.TestCase): '--key-path', 'key', '--chain-path', 'chain', '--fullchain-path', 'fullchain']) - self.assertEqual(namespace.cert_path, os.path.abspath(cert)) - self.assertEqual(namespace.key_path, os.path.abspath(key)) - self.assertEqual(namespace.chain_path, os.path.abspath(chain)) - self.assertEqual(namespace.fullchain_path, os.path.abspath(fullchain)) + assert namespace.cert_path == os.path.abspath(cert) + assert namespace.key_path == os.path.abspath(key) + assert namespace.chain_path == os.path.abspath(chain) + assert namespace.fullchain_path == os.path.abspath(fullchain) def test_help(self): self._help_output(['--help']) # assert SystemExit is raised here out = self._help_output(['--help', 'all']) - self.assertIn("--configurator", out) - self.assertIn("how a certificate is deployed", out) - self.assertIn("--webroot-path", out) - self.assertNotIn("--text", out) - self.assertNotIn("%s", out) - self.assertNotIn("{0}", out) - self.assertNotIn("--renew-hook", out) + assert "--configurator" in out + assert "how a certificate is deployed" in out + assert "--webroot-path" in out + assert "--text" not in out + assert "%s" not in out + assert "{0}" not in out + assert "--renew-hook" not in out out = self._help_output(['-h', 'nginx']) if "nginx" in PLUGINS: # may be false while building distributions without plugins - self.assertIn("--nginx-ctl", out) - self.assertNotIn("--webroot-path", out) - self.assertNotIn("--checkpoints", out) + assert "--nginx-ctl" in out + assert "--webroot-path" not in out + assert "--checkpoints" not in out out = self._help_output(['-h']) if "nginx" in PLUGINS: - self.assertIn("Use the Nginx plugin", out) + assert "Use the Nginx plugin" in out else: - self.assertIn("(the certbot nginx plugin is not", out) + assert "(the certbot nginx plugin is not" in out out = self._help_output(['--help', 'plugins']) - self.assertNotIn("--webroot-path", out) - self.assertIn("--prepare", out) - self.assertIn('"plugins" subcommand', out) + assert "--webroot-path" not in out + assert "--prepare" in out + assert '"plugins" subcommand' in out # test multiple topics out = self._help_output(['-h', 'renew']) - self.assertIn("--keep", out) + assert "--keep" in out out = self._help_output(['-h', 'automation']) - self.assertIn("--keep", out) + assert "--keep" in out out = self._help_output(['-h', 'revoke']) - self.assertNotIn("--keep", out) + assert "--keep" not in out out = self._help_output(['--help', 'install']) - self.assertIn("--cert-path", out) - self.assertIn("--key-path", out) + assert "--cert-path" in out + assert "--key-path" in out out = self._help_output(['--help', 'revoke']) - self.assertIn("--cert-path", out) - self.assertIn("--key-path", out) - self.assertIn("--reason", out) - self.assertIn("--delete-after-revoke", out) - self.assertIn("--no-delete-after-revoke", out) + assert "--cert-path" in out + assert "--key-path" in out + assert "--reason" in out + assert "--delete-after-revoke" in out + assert "--no-delete-after-revoke" in out out = self._help_output(['-h', 'register']) - self.assertNotIn("--cert-path", out) - self.assertNotIn("--key-path", out) + assert "--cert-path" not in out + assert "--key-path" not in out out = self._help_output(['-h']) - self.assertIn(cli.SHORT_USAGE, out) - self.assertIn(cli.COMMAND_OVERVIEW[:100], out) - self.assertNotIn("%s", out) - self.assertNotIn("{0}", out) + assert cli.SHORT_USAGE in out + assert cli.COMMAND_OVERVIEW[:100] in out + assert "%s" not in out + assert "{0}" not in out def test_help_no_dashes(self): self._help_output(['help']) # assert SystemExit is raised here out = self._help_output(['help', 'all']) - self.assertIn("--configurator", out) - self.assertIn("how a certificate is deployed", out) - self.assertIn("--webroot-path", out) - self.assertNotIn("--text", out) - self.assertNotIn("%s", out) - self.assertNotIn("{0}", out) + assert "--configurator" in out + assert "how a certificate is deployed" in out + assert "--webroot-path" in out + assert "--text" not in out + assert "%s" not in out + assert "{0}" not in out out = self._help_output(['help', 'install']) - self.assertIn("--cert-path", out) - self.assertIn("--key-path", out) + assert "--cert-path" in out + assert "--key-path" in out out = self._help_output(['help', 'revoke']) - self.assertIn("--cert-path", out) - self.assertIn("--key-path", out) + assert "--cert-path" in out + assert "--key-path" in out def test_parse_domains(self): short_args = ['-d', 'example.com'] namespace = self.parse(short_args) - self.assertEqual(namespace.domains, ['example.com']) + assert namespace.domains == ['example.com'] short_args = ['-d', 'trailing.period.com.'] namespace = self.parse(short_args) - self.assertEqual(namespace.domains, ['trailing.period.com']) + assert namespace.domains == ['trailing.period.com'] short_args = ['-d', 'example.com,another.net,third.org,example.com'] namespace = self.parse(short_args) - self.assertEqual(namespace.domains, ['example.com', 'another.net', - 'third.org']) + assert namespace.domains == ['example.com', 'another.net', + 'third.org'] long_args = ['--domains', 'example.com'] namespace = self.parse(long_args) - self.assertEqual(namespace.domains, ['example.com']) + assert namespace.domains == ['example.com'] long_args = ['--domains', 'trailing.period.com.'] namespace = self.parse(long_args) - self.assertEqual(namespace.domains, ['trailing.period.com']) + assert namespace.domains == ['trailing.period.com'] long_args = ['--domains', 'example.com,another.net,example.com'] namespace = self.parse(long_args) - self.assertEqual(namespace.domains, ['example.com', 'another.net']) + assert namespace.domains == ['example.com', 'another.net'] def test_preferred_challenges(self): short_args = ['--preferred-challenges', 'http, dns'] namespace = self.parse(short_args) expected = [challenges.HTTP01.typ, challenges.DNS01.typ] - self.assertEqual(namespace.pref_challs, expected) + assert namespace.pref_challs == expected short_args = ['--preferred-challenges', 'jumping-over-the-moon'] # argparse.ArgumentError makes argparse print more information # to stderr and call sys.exit() with mock.patch('sys.stderr'): - self.assertRaises(SystemExit, self.parse, short_args) + with pytest.raises(SystemExit): + self.parse(short_args) def test_server_flag(self): namespace = self.parse('--server example.com'.split()) - self.assertEqual(namespace.server, 'example.com') + assert namespace.server == 'example.com' def test_must_staple_flag(self): short_args = ['--must-staple'] namespace = self.parse(short_args) - self.assertIs(namespace.must_staple, True) - self.assertIs(namespace.staple, True) + assert namespace.must_staple is True + assert namespace.staple is True def _check_server_conflict_message(self, parser_args, conflicting_args): try: @@ -276,36 +280,37 @@ class ParseTest(unittest.TestCase): "The following flags didn't conflict with " '--server: {0}'.format(', '.join(conflicting_args))) except errors.Error as error: - self.assertIn('--server', str(error)) + assert '--server' in str(error) for arg in conflicting_args: - self.assertIn(arg, str(error)) + assert arg in str(error) def test_staging_flag(self): short_args = ['--staging'] namespace = self.parse(short_args) - self.assertIs(namespace.staging, True) - self.assertEqual(namespace.server, constants.STAGING_URI) + assert namespace.staging is True + assert namespace.server == constants.STAGING_URI short_args += '--server example.com'.split() self._check_server_conflict_message(short_args, '--staging') def _assert_dry_run_flag_worked(self, namespace, existing_account): - self.assertIs(namespace.dry_run, True) - self.assertIs(namespace.break_my_certs, True) - self.assertIs(namespace.staging, True) - self.assertEqual(namespace.server, constants.STAGING_URI) + assert namespace.dry_run is True + assert namespace.break_my_certs is True + assert namespace.staging is True + assert namespace.server == constants.STAGING_URI if existing_account: - self.assertIs(namespace.tos, True) - self.assertIs(namespace.register_unsafely_without_email, True) + assert namespace.tos is True + assert namespace.register_unsafely_without_email is True else: - self.assertIs(namespace.tos, False) - self.assertIs(namespace.register_unsafely_without_email, False) + assert namespace.tos is False + assert namespace.register_unsafely_without_email is False def test_dry_run_flag(self): config_dir = tempfile.mkdtemp() short_args = '--dry-run --config-dir {0}'.format(config_dir).split() - self.assertRaises(errors.Error, self.parse, short_args) + with pytest.raises(errors.Error): + self.parse(short_args) self._assert_dry_run_flag_worked( self.parse(short_args + ['auth']), False) @@ -325,16 +330,16 @@ class ParseTest(unittest.TestCase): short_args += ['certonly'] # `--dry-run --server example.com` should emit example.com - self.assertEqual(self.parse(short_args + ['--server', 'example.com']).server, - 'example.com') + assert self.parse(short_args + ['--server', 'example.com']).server == \ + 'example.com' # `--dry-run --server STAGING_URI` should emit STAGING_URI - self.assertEqual(self.parse(short_args + ['--server', constants.STAGING_URI]).server, - constants.STAGING_URI) + assert self.parse(short_args + ['--server', constants.STAGING_URI]).server == \ + constants.STAGING_URI # `--dry-run --server LIVE` should emit STAGING_URI - self.assertEqual(self.parse(short_args + ['--server', cli.flag_default("server")]).server, - constants.STAGING_URI) + assert self.parse(short_args + ['--server', cli.flag_default("server")]).server == \ + constants.STAGING_URI # `--dry-run --server example.com --staging` should emit an error conflicts = ['--staging'] @@ -346,134 +351,134 @@ class ParseTest(unittest.TestCase): key_size_value = cli.flag_default(key_size_option) self.parse('--rsa-key-size {0}'.format(key_size_value).split()) - self.assertIs(cli.option_was_set(key_size_option, key_size_value), True) - self.assertIs(cli.option_was_set('no_verify_ssl', True), True) + assert cli.option_was_set(key_size_option, key_size_value) is True + assert cli.option_was_set('no_verify_ssl', True) is True config_dir_option = 'config_dir' - self.assertFalse(cli.option_was_set( - config_dir_option, cli.flag_default(config_dir_option))) - self.assertFalse(cli.option_was_set( - 'authenticator', cli.flag_default('authenticator'))) + assert not cli.option_was_set( + config_dir_option, cli.flag_default(config_dir_option)) + assert not cli.option_was_set( + 'authenticator', cli.flag_default('authenticator')) def test_ecdsa_key_option(self): elliptic_curve_option = 'elliptic_curve' elliptic_curve_option_value = cli.flag_default(elliptic_curve_option) self.parse('--elliptic-curve {0}'.format(elliptic_curve_option_value).split()) - self.assertIs(cli.option_was_set(elliptic_curve_option, elliptic_curve_option_value), True) + assert cli.option_was_set(elliptic_curve_option, elliptic_curve_option_value) is True def test_invalid_key_type(self): key_type_option = 'key_type' key_type_value = cli.flag_default(key_type_option) self.parse('--key-type {0}'.format(key_type_value).split()) - self.assertIs(cli.option_was_set(key_type_option, key_type_value), True) + assert cli.option_was_set(key_type_option, key_type_value) is True - with self.assertRaises(SystemExit): + with pytest.raises(SystemExit): self.parse("--key-type foo") def test_encode_revocation_reason(self): for reason, code in constants.REVOCATION_REASONS.items(): namespace = self.parse(['--reason', reason]) - self.assertEqual(namespace.reason, code) + assert namespace.reason == code for reason, code in constants.REVOCATION_REASONS.items(): namespace = self.parse(['--reason', reason.upper()]) - self.assertEqual(namespace.reason, code) + assert namespace.reason == code def test_force_interactive(self): - self.assertRaises( - errors.Error, self.parse, "renew --force-interactive".split()) - self.assertRaises( - errors.Error, self.parse, "-n --force-interactive".split()) + with pytest.raises(errors.Error): + self.parse("renew --force-interactive".split()) + with pytest.raises(errors.Error): + self.parse("-n --force-interactive".split()) def test_deploy_hook_conflict(self): with mock.patch("certbot._internal.cli.sys.stderr"): - self.assertRaises(SystemExit, self.parse, - "--renew-hook foo --deploy-hook bar".split()) + with pytest.raises(SystemExit): + self.parse("--renew-hook foo --deploy-hook bar".split()) def test_deploy_hook_matches_renew_hook(self): value = "foo" namespace = self.parse(["--renew-hook", value, "--deploy-hook", value, "--disable-hook-validation"]) - self.assertEqual(namespace.deploy_hook, value) - self.assertEqual(namespace.renew_hook, value) + assert namespace.deploy_hook == value + assert namespace.renew_hook == value def test_deploy_hook_sets_renew_hook(self): value = "foo" namespace = self.parse( ["--deploy-hook", value, "--disable-hook-validation"]) - self.assertEqual(namespace.deploy_hook, value) - self.assertEqual(namespace.renew_hook, value) + assert namespace.deploy_hook == value + assert namespace.renew_hook == value def test_renew_hook_conflict(self): with mock.patch("certbot._internal.cli.sys.stderr"): - self.assertRaises(SystemExit, self.parse, - "--deploy-hook foo --renew-hook bar".split()) + with pytest.raises(SystemExit): + self.parse("--deploy-hook foo --renew-hook bar".split()) def test_renew_hook_matches_deploy_hook(self): value = "foo" namespace = self.parse(["--deploy-hook", value, "--renew-hook", value, "--disable-hook-validation"]) - self.assertEqual(namespace.deploy_hook, value) - self.assertEqual(namespace.renew_hook, value) + assert namespace.deploy_hook == value + assert namespace.renew_hook == value def test_renew_hook_does_not_set_renew_hook(self): value = "foo" namespace = self.parse( ["--renew-hook", value, "--disable-hook-validation"]) - self.assertIsNone(namespace.deploy_hook) - self.assertEqual(namespace.renew_hook, value) + assert namespace.deploy_hook is None + assert namespace.renew_hook == value def test_max_log_backups_error(self): with mock.patch('certbot._internal.cli.sys.stderr'): - self.assertRaises( - SystemExit, self.parse, "--max-log-backups foo".split()) - self.assertRaises( - SystemExit, self.parse, "--max-log-backups -42".split()) + with pytest.raises(SystemExit): + self.parse("--max-log-backups foo".split()) + with pytest.raises(SystemExit): + self.parse("--max-log-backups -42".split()) def test_max_log_backups_success(self): value = "42" namespace = self.parse(["--max-log-backups", value]) - self.assertEqual(namespace.max_log_backups, int(value)) + assert namespace.max_log_backups == int(value) def test_unchanging_defaults(self): namespace = self.parse([]) - self.assertEqual(namespace.domains, []) - self.assertEqual(namespace.pref_challs, []) + assert namespace.domains == [] + assert namespace.pref_challs == [] namespace.pref_challs = [challenges.HTTP01.typ] namespace.domains = ['example.com'] namespace = self.parse([]) - self.assertEqual(namespace.domains, []) - self.assertEqual(namespace.pref_challs, []) + assert namespace.domains == [] + assert namespace.pref_challs == [] def test_no_directory_hooks_set(self): - self.assertFalse(self.parse(["--no-directory-hooks"]).directory_hooks) + assert not self.parse(["--no-directory-hooks"]).directory_hooks def test_no_directory_hooks_unset(self): - self.assertIs(self.parse([]).directory_hooks, True) + assert self.parse([]).directory_hooks is True def test_delete_after_revoke(self): namespace = self.parse(["--delete-after-revoke"]) - self.assertIs(namespace.delete_after_revoke, True) + assert namespace.delete_after_revoke is True def test_delete_after_revoke_default(self): namespace = self.parse([]) - self.assertIsNone(namespace.delete_after_revoke) + assert namespace.delete_after_revoke is None def test_no_delete_after_revoke(self): namespace = self.parse(["--no-delete-after-revoke"]) - self.assertIs(namespace.delete_after_revoke, False) + assert namespace.delete_after_revoke is False def test_allow_subset_with_wildcard(self): - self.assertRaises(errors.Error, self.parse, - "--allow-subset-of-names -d *.example.org".split()) + with pytest.raises(errors.Error): + self.parse("--allow-subset-of-names -d *.example.org".split()) def test_route53_no_revert(self): for help_flag in ['-h', '--help']: for topic in ['all', 'plugins', 'dns-route53']: - self.assertNotIn('certbot-route53:auth', self._help_output([help_flag, topic])) + assert 'certbot-route53:auth' not in self._help_output([help_flag, topic]) class DefaultTest(unittest.TestCase): @@ -486,14 +491,14 @@ class DefaultTest(unittest.TestCase): self.default2 = cli._Default() def test_boolean(self): - self.assertIs(bool(self.default1), False) - self.assertIs(bool(self.default2), False) + assert bool(self.default1) is False + assert bool(self.default2) is False def test_equality(self): - self.assertEqual(self.default1, self.default2) + assert self.default1 == self.default2 def test_hash(self): - self.assertEqual(hash(self.default1), hash(self.default2)) + assert hash(self.default1) == hash(self.default2) class SetByCliTest(unittest.TestCase): @@ -504,13 +509,13 @@ class SetByCliTest(unittest.TestCase): reload_module(cli) def test_deploy_hook(self): - self.assertTrue(_call_set_by_cli( - 'renew_hook', '--deploy-hook foo'.split(), 'renew')) + assert _call_set_by_cli( + 'renew_hook', '--deploy-hook foo'.split(), 'renew') def test_webroot_map(self): args = '-w /var/www/html -d example.com'.split() verb = 'renew' - self.assertIs(_call_set_by_cli('webroot_map', args, verb), True) + assert _call_set_by_cli('webroot_map', args, verb) is True def _call_set_by_cli(var, args, verb): @@ -522,4 +527,4 @@ def _call_set_by_cli(var, args, verb): if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index 6b430831f..1e1b0219c 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -1,24 +1,25 @@ """Tests for certbot._internal.client.""" -import datetime import contextlib +import datetime import platform import shutil +import sys import tempfile import unittest from unittest import mock from unittest.mock import MagicMock from josepy import interfaces +import pytest from certbot import errors from certbot import util -from certbot._internal.display import obj as display_obj from certbot._internal import account from certbot._internal import constants +from certbot._internal.display import obj as display_obj from certbot.compat import os import certbot.tests.util as test_util - KEY = test_util.load_vector("rsa512_key.pem") CSR_SAN = test_util.load_vector("csr-san_512.pem") @@ -102,12 +103,13 @@ class RegisterTest(test_util.ConfigTestCase): mock_client().external_account_required.side_effect = self._false_mock with mock.patch("certbot._internal.eff.prepare_subscription") as mock_prepare: mock_client().new_account.side_effect = errors.Error - self.assertRaises(errors.Error, self._call) - self.assertIs(mock_prepare.called, False) + with pytest.raises(errors.Error): + self._call() + assert mock_prepare.called is False mock_client().new_account.side_effect = None self._call() - self.assertIs(mock_prepare.called, True) + assert mock_prepare.called is True @mock.patch('certbot._internal.eff.prepare_subscription') def test_empty_meta(self, unused_mock_prepare): @@ -120,7 +122,7 @@ class RegisterTest(test_util.ConfigTestCase): mock_client().external_account_required.side_effect = self._false_mock self._call() - self.assertIs(self.tos_cb.called, False) + assert self.tos_cb.called is False @test_util.patch_display_util() def test_it(self, unused_mock_get_utility): @@ -128,7 +130,7 @@ class RegisterTest(test_util.ConfigTestCase): mock_client().external_account_required.side_effect = self._false_mock with mock.patch("certbot._internal.eff.handle_subscription"): self._call() - self.assertIs(self.tos_cb.called, True) + assert self.tos_cb.called is True @mock.patch("certbot._internal.client.display_ops.get_email") def test_email_retry(self, mock_get_email): @@ -141,8 +143,8 @@ class RegisterTest(test_util.ConfigTestCase): with mock.patch("certbot._internal.eff.prepare_subscription") as mock_prepare: mock_client().new_account.side_effect = [mx_err, mock.MagicMock()] self._call() - self.assertEqual(mock_get_email.call_count, 1) - self.assertIs(mock_prepare.called, True) + assert mock_get_email.call_count == 1 + assert mock_prepare.called is True def test_email_invalid_noninteractive(self): from acme import messages @@ -153,11 +155,13 @@ class RegisterTest(test_util.ConfigTestCase): mock_client().external_account_required.side_effect = self._false_mock with mock.patch("certbot._internal.eff.handle_subscription"): mock_client().new_account.side_effect = [mx_err, mock.MagicMock()] - self.assertRaises(errors.Error, self._call) + with pytest.raises(errors.Error): + self._call() def test_needs_email(self): self.config.email = None - self.assertRaises(errors.Error, self._call) + with pytest.raises(errors.Error): + self._call() @mock.patch("certbot._internal.client.logger") def test_without_email(self, mock_logger): @@ -169,7 +173,7 @@ class RegisterTest(test_util.ConfigTestCase): self.config.dry_run = False self._call() mock_logger.debug.assert_called_once_with(mock.ANY) - self.assertIs(mock_prepare.called, True) + assert mock_prepare.called is True @mock.patch("certbot._internal.client.display_ops.get_email") def test_dry_run_no_staging_account(self, mock_get_email): @@ -180,9 +184,9 @@ class RegisterTest(test_util.ConfigTestCase): self.config.dry_run = True self._call() # check Certbot did not ask the user to provide an email - self.assertIs(mock_get_email.called, False) + assert mock_get_email.called is False # check Certbot created an account with no email. Contact should return empty - self.assertFalse(mock_client().new_account.call_args[0][0].contact) + assert not mock_client().new_account.call_args[0][0].contact @test_util.patch_display_util() def test_with_eab_arguments(self, unused_mock_get_utility): @@ -198,7 +202,7 @@ class RegisterTest(test_util.ConfigTestCase): self.config.eab_hmac_key = "J2OAqW4MHXsrHVa_PVg0Y-L_R4SYw0_aL1le6mfblbE" self._call() - self.assertIs(mock_eab_from_data.called, True) + assert mock_eab_from_data.called is True @test_util.patch_display_util() def test_without_eab_arguments(self, unused_mock_get_utility): @@ -211,7 +215,7 @@ class RegisterTest(test_util.ConfigTestCase): self.config.eab_hmac_key = None self._call() - self.assertIs(mock_eab_from_data.called, False) + assert mock_eab_from_data.called is False def test_external_account_required_without_eab_arguments(self): with self._patched_acme_client() as mock_client: @@ -222,7 +226,8 @@ class RegisterTest(test_util.ConfigTestCase): self.config.eab_kid = None self.config.eab_hmac_key = None - self.assertRaises(errors.Error, self._call) + with pytest.raises(errors.Error): + self._call() def test_unsupported_error(self): from acme import messages @@ -235,8 +240,9 @@ class RegisterTest(test_util.ConfigTestCase): mock_client().external_account_required.side_effect = self._false_mock with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle: mock_client().new_account.side_effect = [mx_err, mock.MagicMock()] - self.assertRaises(messages.Error, self._call) - self.assertIs(mock_handle.called, False) + with pytest.raises(messages.Error): + self._call() + assert mock_handle.called is False class ClientTestCommon(test_util.ConfigTestCase): @@ -274,7 +280,7 @@ class ClientTest(ClientTestCommon): csr_pem=mock.sentinel.csr_pem) def test_init_acme_verify_ssl(self): - self.assertIs(self.client_network.call_args[1]['verify_ssl'], True) + assert self.client_network.call_args[1]['verify_ssl'] is True def _mock_obtain_certificate(self): self.client.auth_handler = mock.MagicMock() @@ -291,7 +297,7 @@ class ClientTest(ClientTestCommon): self.config, self.config.allow_subset_of_names) else: - self.assertEqual(self.client.auth_handler.handle_authorizations.call_count, auth_count) + assert self.client.auth_handler.handle_authorizations.call_count == auth_count self.acme.finalize_order.assert_called_once_with( self.eg_order, mock.ANY, @@ -307,22 +313,20 @@ class ClientTest(ClientTestCommon): orderr = self.acme.new_order(test_csr.data) auth_handler.handle_authorizations(orderr, self.config, False) - self.assertEqual( - (mock.sentinel.cert, mock.sentinel.chain), + assert (mock.sentinel.cert, mock.sentinel.chain) == \ self.client.obtain_certificate_from_csr( test_csr, - orderr=orderr)) + orderr=orderr) mock_crypto_util.find_chain_with_issuer.assert_not_called() # and that the cert was obtained correctly self._check_obtain_certificate() # Test that --preferred-chain results in chain selection self.config.preferred_chain = "some issuer" - self.assertEqual( - (mock.sentinel.cert, mock.sentinel.chain), + assert (mock.sentinel.cert, mock.sentinel.chain) == \ self.client.obtain_certificate_from_csr( test_csr, - orderr=orderr)) + orderr=orderr) mock_crypto_util.find_chain_with_issuer.assert_called_once_with( [orderr.fullchain_pem] + orderr.alternative_fullchains_pem, "some issuer", True) @@ -334,8 +338,7 @@ class ClientTest(ClientTestCommon): seconds=constants.CLI_DEFAULTS["issuance_timeout"]) self.client.obtain_certificate_from_csr(test_csr, orderr=orderr) ((_, deadline), _) = self.client.acme.finalize_order.call_args - self.assertTrue( - abs(expected_deadline - deadline) <= datetime.timedelta(seconds=1)) + assert abs(expected_deadline - deadline) <= datetime.timedelta(seconds=1) # Test for specific issuance_timeout (300 seconds) expected_deadline = \ @@ -343,23 +346,19 @@ class ClientTest(ClientTestCommon): self.config.issuance_timeout = 300 self.client.obtain_certificate_from_csr(test_csr, orderr=orderr) ((_, deadline), _) = self.client.acme.finalize_order.call_args - self.assertTrue( - abs(expected_deadline - deadline) <= datetime.timedelta(seconds=1)) + assert abs(expected_deadline - deadline) <= datetime.timedelta(seconds=1) # Test for orderr=None - self.assertEqual( - (mock.sentinel.cert, mock.sentinel.chain), + assert (mock.sentinel.cert, mock.sentinel.chain) == \ self.client.obtain_certificate_from_csr( test_csr, - orderr=None)) + orderr=None) auth_handler.handle_authorizations.assert_called_with(self.eg_order, self.config, False) # Test for no auth_handler self.client.auth_handler = None - self.assertRaises( - errors.Error, - self.client.obtain_certificate_from_csr, - test_csr) + with pytest.raises(errors.Error): + self.client.obtain_certificate_from_csr(test_csr) mock_logger.error.assert_called_once_with(mock.ANY) @mock.patch("certbot._internal.client.crypto_util") @@ -373,19 +372,18 @@ class ClientTest(ClientTestCommon): mock_crypto_util.generate_key.assert_called_once_with( key_size=self.config.rsa_key_size, - key_dir=self.config.key_dir, + key_dir=None, key_type=self.config.key_type, elliptic_curve="secp256r1", strict_permissions=True, ) mock_crypto_util.generate_csr.assert_called_once_with( - mock.sentinel.key, self.eg_domains, self.config.csr_dir, False, True) + mock.sentinel.key, self.eg_domains, None, False, True) mock_crypto_util.cert_and_chain_from_fullchain.assert_called_once_with( self.eg_order.fullchain_pem) @mock.patch("certbot._internal.client.crypto_util") - @mock.patch("certbot.compat.os.remove") - def test_obtain_certificate_partial_success(self, mock_remove, mock_crypto_util): + def test_obtain_certificate_partial_success(self, mock_crypto_util): csr = util.CSR(form="pem", file=mock.sentinel.csr_file, data=CSR_SAN) key = util.CSR(form="pem", file=mock.sentinel.key_file, data=CSR_SAN) mock_crypto_util.generate_csr.return_value = csr @@ -396,14 +394,12 @@ class ClientTest(ClientTestCommon): self.config.allow_subset_of_names = True self._test_obtain_certificate_common(key, csr, authzr_ret=authzr, auth_count=2) - self.assertEqual(mock_crypto_util.generate_key.call_count, 2) - self.assertEqual(mock_crypto_util.generate_csr.call_count, 2) - self.assertEqual(mock_remove.call_count, 2) - self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 1) + assert mock_crypto_util.generate_key.call_count == 2 + assert mock_crypto_util.generate_csr.call_count == 2 + assert mock_crypto_util.cert_and_chain_from_fullchain.call_count == 1 @mock.patch("certbot._internal.client.crypto_util") - @mock.patch("certbot.compat.os.remove") - def test_obtain_certificate_finalize_order_partial_success(self, mock_remove, mock_crypto_util): + def test_obtain_certificate_finalize_order_partial_success(self, mock_crypto_util): from acme import messages csr = util.CSR(form="pem", file=mock.sentinel.csr_file, data=CSR_SAN) key = util.CSR(form="pem", file=mock.sentinel.key_file, data=CSR_SAN) @@ -426,19 +422,17 @@ class ClientTest(ClientTestCommon): with test_util.patch_display_util(): result = self.client.obtain_certificate(self.eg_domains) - self.assertEqual( - result, - (mock.sentinel.cert, mock.sentinel.chain, key, csr)) - self.assertEqual(self.client.auth_handler.handle_authorizations.call_count, 2) - self.assertEqual(self.acme.finalize_order.call_count, 2) + assert result == \ + (mock.sentinel.cert, mock.sentinel.chain, key, csr) + assert self.client.auth_handler.handle_authorizations.call_count == 2 + assert self.acme.finalize_order.call_count == 2 successful_domains = [d for d in self.eg_domains if d != 'example.com'] - self.assertEqual(mock_crypto_util.generate_key.call_count, 2) + assert mock_crypto_util.generate_key.call_count == 2 mock_crypto_util.generate_csr.assert_has_calls([ - mock.call(key, self.eg_domains, self.config.csr_dir, self.config.must_staple, self.config.strict_permissions), - mock.call(key, successful_domains, self.config.csr_dir, self.config.must_staple, self.config.strict_permissions)]) - self.assertEqual(mock_remove.call_count, 2) - self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 1) + mock.call(key, self.eg_domains, None, self.config.must_staple, self.config.strict_permissions), + mock.call(key, successful_domains, None, self.config.must_staple, self.config.strict_permissions)]) + assert mock_crypto_util.cert_and_chain_from_fullchain.call_count == 1 @mock.patch("certbot._internal.client.crypto_util") def test_obtain_certificate_finalize_order_no_retryable_domains(self, mock_crypto_util): @@ -463,11 +457,12 @@ class ClientTest(ClientTestCommon): self.config.allow_subset_of_names = True - self.assertRaises(messages.Error, self.client.obtain_certificate, self.eg_domains) - self.assertEqual(self.client.auth_handler.handle_authorizations.call_count, 1) - self.assertEqual(self.acme.finalize_order.call_count, 1) - self.assertEqual(mock_crypto_util.generate_key.call_count, 1) - self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 0) + with pytest.raises(messages.Error): + self.client.obtain_certificate(self.eg_domains) + assert self.client.auth_handler.handle_authorizations.call_count == 1 + assert self.acme.finalize_order.call_count == 1 + assert mock_crypto_util.generate_key.call_count == 1 + assert mock_crypto_util.cert_and_chain_from_fullchain.call_count == 0 @mock.patch("certbot._internal.client.crypto_util") def test_obtain_certificate_finalize_order_rejected_identifier_no_subproblems(self, mock_crypto_util): @@ -488,16 +483,15 @@ class ClientTest(ClientTestCommon): self.config.allow_subset_of_names = True - self.assertRaises(messages.Error, self.client.obtain_certificate, - self.eg_domains) - self.assertEqual(self.client.auth_handler.handle_authorizations.call_count, 1) - self.assertEqual(self.acme.finalize_order.call_count, 1) - self.assertEqual(mock_crypto_util.generate_key.call_count, 1) - self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 0) + with pytest.raises(messages.Error): + self.client.obtain_certificate(self.eg_domains) + assert self.client.auth_handler.handle_authorizations.call_count == 1 + assert self.acme.finalize_order.call_count == 1 + assert mock_crypto_util.generate_key.call_count == 1 + assert mock_crypto_util.cert_and_chain_from_fullchain.call_count == 0 @mock.patch("certbot._internal.client.crypto_util") - @mock.patch("certbot.compat.os.remove") - def test_obtain_certificate_get_order_partial_success(self, mock_remove, mock_crypto_util): + def test_obtain_certificate_get_order_partial_success(self, mock_crypto_util): from acme import messages csr = util.CSR(form="pem", file=mock.sentinel.csr_file, data=CSR_SAN) key = util.CSR(form="pem", file=mock.sentinel.key_file, data=CSR_SAN) @@ -520,19 +514,17 @@ class ClientTest(ClientTestCommon): with test_util.patch_display_util(): result = self.client.obtain_certificate(self.eg_domains) - self.assertEqual( - result, - (mock.sentinel.cert, mock.sentinel.chain, key, csr)) - self.assertEqual(self.client.auth_handler.handle_authorizations.call_count, 1) - self.assertEqual(self.acme.new_order.call_count, 2) + assert result == \ + (mock.sentinel.cert, mock.sentinel.chain, key, csr) + assert self.client.auth_handler.handle_authorizations.call_count == 1 + assert self.acme.new_order.call_count == 2 successful_domains = [d for d in self.eg_domains if d != 'example.com'] - self.assertEqual(mock_crypto_util.generate_key.call_count, 2) + assert mock_crypto_util.generate_key.call_count == 2 mock_crypto_util.generate_csr.assert_has_calls([ - mock.call(key, self.eg_domains, self.config.csr_dir, self.config.must_staple, self.config.strict_permissions), - mock.call(key, successful_domains, self.config.csr_dir, self.config.must_staple, self.config.strict_permissions)]) - self.assertEqual(mock_remove.call_count, 2) - self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 1) + mock.call(key, self.eg_domains, None, self.config.must_staple, self.config.strict_permissions), + mock.call(key, successful_domains, None, self.config.must_staple, self.config.strict_permissions)]) + assert mock_crypto_util.cert_and_chain_from_fullchain.call_count == 1 @mock.patch("certbot._internal.client.crypto_util") def test_obtain_certificate_get_order_no_retryable_domains(self, mock_crypto_util): @@ -557,11 +549,12 @@ class ClientTest(ClientTestCommon): self.config.allow_subset_of_names = True - self.assertRaises(messages.Error, self.client.obtain_certificate, self.eg_domains) - self.assertEqual(self.client.auth_handler.handle_authorizations.call_count, 0) - self.assertEqual(self.acme.new_order.call_count, 1) - self.assertEqual(mock_crypto_util.generate_key.call_count, 1) - self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 0) + with pytest.raises(messages.Error): + self.client.obtain_certificate(self.eg_domains) + assert self.client.auth_handler.handle_authorizations.call_count == 0 + assert self.acme.new_order.call_count == 1 + assert mock_crypto_util.generate_key.call_count == 1 + assert mock_crypto_util.cert_and_chain_from_fullchain.call_count == 0 @mock.patch("certbot._internal.client.crypto_util") def test_obtain_certificate_get_order_rejected_identifier_no_subproblems(self, mock_crypto_util): @@ -582,11 +575,12 @@ class ClientTest(ClientTestCommon): self.config.allow_subset_of_names = True - self.assertRaises(messages.Error, self.client.obtain_certificate, self.eg_domains) - self.assertEqual(self.client.auth_handler.handle_authorizations.call_count, 0) - self.assertEqual(self.acme.new_order.call_count, 1) - self.assertEqual(mock_crypto_util.generate_key.call_count, 1) - self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 0) + with pytest.raises(messages.Error): + self.client.obtain_certificate(self.eg_domains) + assert self.client.auth_handler.handle_authorizations.call_count == 0 + assert self.acme.new_order.call_count == 1 + assert mock_crypto_util.generate_key.call_count == 1 + assert mock_crypto_util.cert_and_chain_from_fullchain.call_count == 0 @mock.patch("certbot._internal.client.crypto_util") @mock.patch("certbot._internal.client.acme_crypto_util") @@ -609,7 +603,7 @@ class ClientTest(ClientTestCommon): mock.sentinel.key_pem, self.eg_domains, self.config.must_staple) mock_crypto.generate_key.assert_not_called() mock_crypto.generate_csr.assert_not_called() - self.assertEqual(mock_crypto.cert_and_chain_from_fullchain.call_count, 1) + assert mock_crypto.cert_and_chain_from_fullchain.call_count == 1 @mock.patch("certbot._internal.client.logger") @mock.patch("certbot._internal.client.crypto_util") @@ -640,14 +634,14 @@ class ClientTest(ClientTestCommon): self.client.auth_handler.handle_authorizations.return_value = authzrs with test_util.patch_display_util(): result = self.client.obtain_certificate(self.eg_domains) - self.assertEqual(result, (mock.sentinel.cert, mock.sentinel.chain, key, csr)) + assert result == (mock.sentinel.cert, mock.sentinel.chain, key, csr) self._check_obtain_certificate(1) # Deactivation success/failure should have been handled properly - self.assertEqual(auth_handler.deactivate_valid_authorizations.call_count, 1, - "Deactivate authorizations should be called") - self.assertEqual(self.acme.new_order.call_count, 2, - "Order should be recreated due to successfully deactivated authorizations") + assert auth_handler.deactivate_valid_authorizations.call_count == 1, \ + "Deactivate authorizations should be called" + assert self.acme.new_order.call_count == 2, \ + "Order should be recreated due to successfully deactivated authorizations" mock_log.warning.assert_called_with("Certbot was unable to obtain fresh authorizations for" " every domain. The dry run will continue, but results" " may not be accurate.") @@ -685,9 +679,8 @@ class ClientTest(ClientTestCommon): with test_util.patch_display_util(): result = self.client.obtain_certificate(self.eg_domains) - self.assertEqual( - result, - (mock.sentinel.cert, mock.sentinel.chain, key, csr)) + assert result == \ + (mock.sentinel.cert, mock.sentinel.chain, key, csr) self._check_obtain_certificate(auth_count) @mock.patch('certbot._internal.client.Client.obtain_certificate') @@ -699,17 +692,17 @@ class ClientTest(ClientTestCommon): mock.MagicMock(), mock.MagicMock(), None) self.client.config.dry_run = False - self.assertTrue(self.client.obtain_and_enroll_certificate(domains, "example_cert")) + assert self.client.obtain_and_enroll_certificate(domains, "example_cert") - self.assertTrue(self.client.obtain_and_enroll_certificate(domains, None)) - self.assertTrue(self.client.obtain_and_enroll_certificate(domains[1:], None)) + assert self.client.obtain_and_enroll_certificate(domains, None) + assert self.client.obtain_and_enroll_certificate(domains[1:], None) self.client.config.dry_run = True - self.assertFalse(self.client.obtain_and_enroll_certificate(domains, None)) + assert not self.client.obtain_and_enroll_certificate(domains, None) names = [call[0][0] for call in mock_storage.call_args_list] - self.assertEqual(names, ["example_cert", "example.com", "example.com"]) + assert names == ["example_cert", "example.com", "example.com"] @mock.patch("certbot._internal.cli.helpful_parser") def test_save_certificate(self, mock_parser): @@ -730,28 +723,28 @@ class ClientTest(ClientTestCommon): cert_pem, chain_pem, candidate_cert_path, candidate_chain_path, candidate_fullchain_path) - self.assertEqual(os.path.dirname(cert_path), - os.path.dirname(candidate_cert_path)) - self.assertEqual(os.path.dirname(chain_path), - os.path.dirname(candidate_chain_path)) - self.assertEqual(os.path.dirname(fullchain_path), - os.path.dirname(candidate_fullchain_path)) + assert os.path.dirname(cert_path) == \ + os.path.dirname(candidate_cert_path) + assert os.path.dirname(chain_path) == \ + os.path.dirname(candidate_chain_path) + assert os.path.dirname(fullchain_path) == \ + os.path.dirname(candidate_fullchain_path) with open(cert_path, "rb") as cert_file: cert_contents = cert_file.read() - self.assertEqual(cert_contents, test_util.load_vector(certs[0])) + assert cert_contents == test_util.load_vector(certs[0]) with open(chain_path, "rb") as chain_file: chain_contents = chain_file.read() - self.assertEqual(chain_contents, test_util.load_vector(certs[0]) + - test_util.load_vector(certs[1])) + assert chain_contents == test_util.load_vector(certs[0]) + \ + test_util.load_vector(certs[1]) shutil.rmtree(tmp_path) @test_util.patch_display_util() def test_deploy_certificate_success(self, mock_util): - self.assertRaises(errors.Error, self.client.deploy_certificate, - ["foo.bar"], "key", "cert", "chain", "fullchain") + with pytest.raises(errors.Error): + self.client.deploy_certificate(["foo.bar"], "key", "cert", "chain", "fullchain") installer = mock.MagicMock() self.client.installer = installer @@ -763,7 +756,7 @@ class ClientTest(ClientTestCommon): domain='foo.bar', fullchain_path='fullchain', key_path=os.path.abspath("key")) - self.assertEqual(installer.save.call_count, 2) + assert installer.save.call_count == 2 installer.restart.assert_called_once_with() @mock.patch('certbot._internal.client.display_util.notify') @@ -774,8 +767,8 @@ class ClientTest(ClientTestCommon): self.config.installer = "foobar" installer.deploy_cert.side_effect = errors.PluginError - self.assertRaises(errors.PluginError, self.client.deploy_certificate, - ["foo.bar"], "key", "cert", "chain", "fullchain") + with pytest.raises(errors.PluginError): + self.client.deploy_certificate(["foo.bar"], "key", "cert", "chain", "fullchain") installer.recovery_routine.assert_called_once_with() mock_notify.assert_any_call('Deploying certificate') @@ -787,8 +780,8 @@ class ClientTest(ClientTestCommon): self.client.installer = installer installer.save.side_effect = errors.PluginError - self.assertRaises(errors.PluginError, self.client.deploy_certificate, - ["foo.bar"], "key", "cert", "chain", "fullchain") + with pytest.raises(errors.PluginError): + self.client.deploy_certificate(["foo.bar"], "key", "cert", "chain", "fullchain") installer.recovery_routine.assert_called_once_with() @mock.patch('certbot._internal.client.display_util.notify') @@ -798,13 +791,13 @@ class ClientTest(ClientTestCommon): installer.restart.side_effect = [errors.PluginError, None] self.client.installer = installer - self.assertRaises(errors.PluginError, self.client.deploy_certificate, - ["foo.bar"], "key", "cert", "chain", "fullchain") + with pytest.raises(errors.PluginError): + self.client.deploy_certificate(["foo.bar"], "key", "cert", "chain", "fullchain") mock_notify.assert_called_with( 'We were unable to install your certificate, however, we successfully restored ' 'your server to its prior configuration.') installer.rollback_checkpoints.assert_called_once_with() - self.assertEqual(installer.restart.call_count, 2) + assert installer.restart.call_count == 2 @mock.patch('certbot._internal.client.logger') @test_util.patch_display_util() @@ -814,14 +807,13 @@ class ClientTest(ClientTestCommon): installer.rollback_checkpoints.side_effect = errors.ReverterError self.client.installer = installer - self.assertRaises(errors.PluginError, self.client.deploy_certificate, - ["foo.bar"], "key", "cert", "chain", "fullchain") - self.assertEqual(mock_logger.error.call_count, 1) - self.assertIn( - 'An error occurred and we failed to restore your config', - mock_logger.error.call_args[0][0]) + with pytest.raises(errors.PluginError): + self.client.deploy_certificate(["foo.bar"], "key", "cert", "chain", "fullchain") + assert mock_logger.error.call_count == 1 + assert 'An error occurred and we failed to restore your config' in \ + mock_logger.error.call_args[0][0] installer.rollback_checkpoints.assert_called_once_with() - self.assertEqual(installer.restart.call_count, 1) + assert installer.restart.call_count == 1 class EnhanceConfigTest(ClientTestCommon): @@ -837,8 +829,8 @@ class EnhanceConfigTest(ClientTestCommon): self.domain = "example.org" def test_no_installer(self): - self.assertRaises( - errors.Error, self.client.enhance_config, [self.domain], None) + with pytest.raises(errors.Error): + self.client.enhance_config([self.domain], None) def test_unsupported(self): self.client.installer = mock.MagicMock() @@ -848,36 +840,36 @@ class EnhanceConfigTest(ClientTestCommon): self.config.hsts = True with mock.patch("certbot._internal.client.logger") as mock_logger: self.client.enhance_config([self.domain], None) - self.assertEqual(mock_logger.error.call_count, 1) + assert mock_logger.error.call_count == 1 self.client.installer.enhance.assert_not_called() @mock.patch("certbot._internal.client.logger") def test_already_exists_header(self, mock_log): self.config.hsts = True self._test_with_already_existing() - self.assertIs(mock_log.info.called, True) - self.assertEqual(mock_log.info.call_args[0][1], - 'Strict-Transport-Security') + assert mock_log.info.called is True + assert mock_log.info.call_args[0][1] == \ + 'Strict-Transport-Security' @mock.patch("certbot._internal.client.logger") def test_already_exists_redirect(self, mock_log): self.config.redirect = True self._test_with_already_existing() - self.assertIs(mock_log.info.called, True) - self.assertEqual(mock_log.info.call_args[0][1], - 'redirect') + assert mock_log.info.called is True + assert mock_log.info.call_args[0][1] == \ + 'redirect' @mock.patch("certbot._internal.client.logger") def test_config_set_no_warning_redirect(self, mock_log): self.config.redirect = False self._test_with_already_existing() - self.assertIs(mock_log.warning.called, False) + assert mock_log.warning.called is False @mock.patch("certbot._internal.client.logger") def test_no_warn_redirect(self, mock_log): self.config.redirect = None self._test_with_all_supported() - self.assertIs(mock_log.warning.called, False) + assert mock_log.warning.called is False def test_no_ask_hsts(self): self.config.hsts = True @@ -930,18 +922,18 @@ class EnhanceConfigTest(ClientTestCommon): def _test_error_with_rollback(self): self._test_error() - self.assertIs(self.client.installer.restart.called, True) + assert self.client.installer.restart.called is True def _test_error(self, enhance_error=False, restart_error=False): self.config.redirect = True with mock.patch('certbot._internal.client.logger') as mock_logger, \ test_util.patch_display_util() as mock_gu: - self.assertRaises( - errors.PluginError, self._test_with_all_supported) + with pytest.raises(errors.PluginError): + self._test_with_all_supported() if enhance_error: - self.assertEqual(mock_logger.error.call_count, 1) - self.assertEqual('Unable to set the %s enhancement for %s.', mock_logger.error.call_args_list[0][0][0]) + assert mock_logger.error.call_count == 1 + assert 'Unable to set the %s enhancement for %s.' == mock_logger.error.call_args_list[0][0][0] if restart_error: mock_logger.critical.assert_called_with( 'Rolling back to previous server configuration...') @@ -952,8 +944,8 @@ class EnhanceConfigTest(ClientTestCommon): self.client.installer.supported_enhancements.return_value = [ "ensure-http-header", "redirect", "staple-ocsp"] self.client.enhance_config([self.domain], None) - self.assertEqual(self.client.installer.save.call_count, 1) - self.assertEqual(self.client.installer.restart.call_count, 1) + assert self.client.installer.save.call_count == 1 + assert self.client.installer.restart.call_count == 1 def _test_with_already_existing(self): self.client.installer = mock.MagicMock() @@ -978,12 +970,12 @@ class RollbackTest(unittest.TestCase): def test_no_problems(self): self._call(1, self.m_install) - self.assertEqual(self.m_install().rollback_checkpoints.call_count, 1) - self.assertEqual(self.m_install().restart.call_count, 1) + assert self.m_install().rollback_checkpoints.call_count == 1 + assert self.m_install().restart.call_count == 1 def test_no_installer(self): self._call(1, None) # Just make sure no exceptions are raised if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index e94068d4e..303fbe92b 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -1,9 +1,12 @@ """Tests for certbot.compat.filesystem""" import contextlib import errno +import sys import unittest from unittest import mock +import pytest + from certbot import util from certbot._internal import lock from certbot.compat import filesystem @@ -12,9 +15,9 @@ import certbot.tests.util as test_util from certbot.tests.util import TempDirTestCase try: + import ntsecuritycon import win32api import win32security - import ntsecuritycon POSIX_MODE = False except ImportError: POSIX_MODE = True @@ -45,8 +48,8 @@ class WindowsChmodTests(TempDirTestCase): # Assert the real file is impacted, not the link. cur_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() cur_dacl_link = _get_security_dacl(link_path).GetSecurityDescriptorDacl() - self.assertFalse(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe)) # pylint: disable=protected-access - self.assertTrue(filesystem._compare_dacls(ref_dacl_link, cur_dacl_link)) # pylint: disable=protected-access + assert not filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe) # pylint: disable=protected-access + assert filesystem._compare_dacls(ref_dacl_link, cur_dacl_link) # pylint: disable=protected-access def test_world_permission(self): everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) @@ -54,14 +57,14 @@ class WindowsChmodTests(TempDirTestCase): filesystem.chmod(self.probe_path, 0o700) dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() - self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) - if dacl.GetAce(index)[2] == everybody]) + assert not [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody] filesystem.chmod(self.probe_path, 0o704) dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() - self.assertTrue([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) - if dacl.GetAce(index)[2] == everybody]) + assert [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody] def test_group_permissions_noop(self): filesystem.chmod(self.probe_path, 0o700) @@ -70,7 +73,7 @@ class WindowsChmodTests(TempDirTestCase): filesystem.chmod(self.probe_path, 0o740) cur_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() - self.assertTrue(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe)) # pylint: disable=protected-access + assert filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe) # pylint: disable=protected-access def test_admin_permissions(self): system = win32security.ConvertStringSidToSid(SYSTEM_SID) @@ -84,11 +87,11 @@ class WindowsChmodTests(TempDirTestCase): admin_aces = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) if dacl.GetAce(index)[2] == admins] - self.assertEqual(len(system_aces), 1) - self.assertEqual(len(admin_aces), 1) + assert len(system_aces) == 1 + assert len(admin_aces) == 1 - self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS) - self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS) + assert system_aces[0][1] == ntsecuritycon.FILE_ALL_ACCESS + assert admin_aces[0][1] == ntsecuritycon.FILE_ALL_ACCESS def test_read_flag(self): self._test_flag(4, ntsecuritycon.FILE_GENERIC_READ) @@ -115,11 +118,11 @@ class WindowsChmodTests(TempDirTestCase): acls_everybody = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) if dacl.GetAce(index)[2] == everybody] - self.assertEqual(len(acls_everybody), 1) + assert len(acls_everybody) == 1 acls_everybody = acls_everybody[0] - self.assertEqual(acls_everybody[1], windows_flag) + assert acls_everybody[1] == windows_flag def test_user_admin_dacl_consistency(self): # Set ownership of target to authenticated user @@ -131,7 +134,7 @@ class WindowsChmodTests(TempDirTestCase): security_dacl = _get_security_dacl(self.probe_path) # We expect three ACE: one for admins, one for system, and one for the user - self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 3) + assert security_dacl.GetSecurityDescriptorDacl().GetAceCount() == 3 # Set ownership of target to Administrators user group admin_user = win32security.ConvertStringSidToSid(ADMINS_SID) @@ -143,7 +146,7 @@ class WindowsChmodTests(TempDirTestCase): security_dacl = _get_security_dacl(self.probe_path) # We expect only two ACE: one for admins, one for system, # since the user is also the admins group - self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 2) + assert security_dacl.GetSecurityDescriptorDacl().GetAceCount() == 2 class UmaskTest(TempDirTestCase): @@ -153,17 +156,17 @@ class UmaskTest(TempDirTestCase): try: dir1 = os.path.join(self.tempdir, 'probe1') filesystem.mkdir(dir1) - self.assertIs(filesystem.check_mode(dir1, 0o755), True) + assert filesystem.check_mode(dir1, 0o755) is True filesystem.umask(0o077) dir2 = os.path.join(self.tempdir, 'dir2') filesystem.mkdir(dir2) - self.assertIs(filesystem.check_mode(dir2, 0o700), True) + assert filesystem.check_mode(dir2, 0o700) is True dir3 = os.path.join(self.tempdir, 'dir3') filesystem.mkdir(dir3, mode=0o777) - self.assertIs(filesystem.check_mode(dir3, 0o700), True) + assert filesystem.check_mode(dir3, 0o700) is True finally: filesystem.umask(previous_umask) @@ -173,17 +176,17 @@ class UmaskTest(TempDirTestCase): try: file1 = os.path.join(self.tempdir, 'probe1') UmaskTest._create_file(file1) - self.assertIs(filesystem.check_mode(file1, 0o755), True) + assert filesystem.check_mode(file1, 0o755) is True filesystem.umask(0o077) file2 = os.path.join(self.tempdir, 'probe2') UmaskTest._create_file(file2) - self.assertIs(filesystem.check_mode(file2, 0o700), True) + assert filesystem.check_mode(file2, 0o700) is True file3 = os.path.join(self.tempdir, 'probe3') UmaskTest._create_file(file3) - self.assertIs(filesystem.check_mode(file3, 0o700), True) + assert filesystem.check_mode(file3, 0o700) is True finally: filesystem.umask(previous_umask) @@ -209,10 +212,10 @@ class ComputePrivateKeyModeTest(TempDirTestCase): if POSIX_MODE: # On Linux RWX permissions for group and R permission for world # are persisted from the existing moe - self.assertEqual(new_mode, 0o674) + assert new_mode == 0o674 else: # On Windows no permission is persisted - self.assertEqual(new_mode, 0o600) + assert new_mode == 0o600 @unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows security') @@ -226,8 +229,8 @@ class WindowsOpenTest(TempDirTestCase): dacl = _get_security_dacl(path).GetSecurityDescriptorDacl() everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) - self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) - if dacl.GetAce(index)[2] == everybody]) + assert not [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody] def test_existing_file_correct_permissions(self): path = os.path.join(self.tempdir, 'file') @@ -239,17 +242,17 @@ class WindowsOpenTest(TempDirTestCase): dacl = _get_security_dacl(path).GetSecurityDescriptorDacl() everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) - self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) - if dacl.GetAce(index)[2] == everybody]) + assert not [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody] def test_create_file_on_open(self): # os.O_CREAT | os.O_EXCL + file not exists = OK self._test_one_creation(1, file_exist=False, flags=(os.O_CREAT | os.O_EXCL)) # os.O_CREAT | os.O_EXCL + file exists = EEXIST OS exception - with self.assertRaises(OSError) as raised: + with pytest.raises(OSError) as exc_info: self._test_one_creation(2, file_exist=True, flags=(os.O_CREAT | os.O_EXCL)) - self.assertEqual(raised.exception.errno, errno.EEXIST) + assert exc_info.value.errno == errno.EEXIST # os.O_CREAT + file not exists = OK self._test_one_creation(3, file_exist=False, flags=os.O_CREAT) @@ -262,14 +265,14 @@ class WindowsOpenTest(TempDirTestCase): open(path, 'w').close() filelock = lock.LockFile(path) try: - with self.assertRaises(OSError) as raised: + with pytest.raises(OSError) as exc_info: self._test_one_creation(5, file_exist=True, flags=os.O_CREAT) - self.assertEqual(raised.exception.errno, errno.EACCES) + assert exc_info.value.errno == errno.EACCES finally: filelock.release() # os.O_CREAT not set + file not exists = OS exception - with self.assertRaises(OSError): + with pytest.raises(OSError): self._test_one_creation(6, file_exist=False, flags=os.O_RDONLY) def _test_one_creation(self, num, file_exist, flags): @@ -295,20 +298,20 @@ class TempUmaskTests(test_util.TempDirTestCase): def test_works_normally(self): filesystem.umask(0o0022) - self.assertEqual(self._check_umask(), 0o0022) + assert self._check_umask() == 0o0022 with filesystem.temp_umask(0o0077): - self.assertEqual(self._check_umask(), 0o0077) - self.assertEqual(self._check_umask(), 0o0022) + assert self._check_umask() == 0o0077 + assert self._check_umask() == 0o0022 def test_resets_umask_after_exception(self): filesystem.umask(0o0022) - self.assertEqual(self._check_umask(), 0o0022) + assert self._check_umask() == 0o0022 try: with filesystem.temp_umask(0o0077): - self.assertEqual(self._check_umask(), 0o0077) + assert self._check_umask() == 0o0077 raise Exception() except: - self.assertEqual(self._check_umask(), 0o0022) + assert self._check_umask() == 0o0022 @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') @@ -322,8 +325,8 @@ class WindowsMkdirTests(test_util.TempDirTestCase): everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) dacl = _get_security_dacl(path).GetSecurityDescriptorDacl() - self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) - if dacl.GetAce(index)[2] == everybody]) + assert not [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody] def test_makedirs_correct_permissions(self): path = os.path.join(self.tempdir, 'dir') @@ -334,8 +337,8 @@ class WindowsMkdirTests(test_util.TempDirTestCase): everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) dacl = _get_security_dacl(subpath).GetSecurityDescriptorDacl() - self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) - if dacl.GetAce(index)[2] == everybody]) + assert not [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody] def test_makedirs_switch_os_mkdir(self): path = os.path.join(self.tempdir, 'dir') @@ -343,13 +346,13 @@ class WindowsMkdirTests(test_util.TempDirTestCase): original_mkdir = std_os.mkdir filesystem.makedirs(path) - self.assertEqual(original_mkdir, std_os.mkdir) + assert original_mkdir == std_os.mkdir try: filesystem.makedirs(path) # Will fail because path already exists except OSError: pass - self.assertEqual(original_mkdir, std_os.mkdir) + assert original_mkdir == std_os.mkdir class MakedirsTests(test_util.TempDirTestCase): @@ -387,19 +390,19 @@ class CopyOwnershipAndModeTest(test_util.TempDirTestCase): filesystem.copy_ownership_and_apply_mode( 'dummy', self.probe_path, 0o700, copy_user=True, copy_group=False) - self.assertEqual(mock_set.call_count, 2) + assert mock_set.call_count == 2 first_call = mock_set.call_args_list[0] security = first_call[0][2] - self.assertEqual(system, security.GetSecurityDescriptorOwner()) + assert system == security.GetSecurityDescriptorOwner() second_call = mock_set.call_args_list[1] security = second_call[0][2] dacl = security.GetSecurityDescriptorDacl() everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) - self.assertTrue(dacl.GetAceCount()) - self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) - if dacl.GetAce(index)[2] == everybody]) + assert dacl.GetAceCount() + assert not [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody] @unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security') def test_copy_ownership_and_apply_mode_linux(self): @@ -421,7 +424,7 @@ class CopyOwnershipAndModeTest(test_util.TempDirTestCase): util.safe_open(path1, 'w').close() util.safe_open(path2, 'w').close() - self.assertIs(filesystem.has_same_ownership(path1, path2), True) + assert filesystem.has_same_ownership(path1, path2) is True @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') def test_copy_ownership_and_mode_windows(self): @@ -429,8 +432,8 @@ class CopyOwnershipAndModeTest(test_util.TempDirTestCase): dst = _create_probe(self.tempdir, name='dst') filesystem.chmod(src, 0o700) - self.assertIs(filesystem.check_mode(src, 0o700), True) - self.assertIs(filesystem.check_mode(dst, 0o744), True) + assert filesystem.check_mode(src, 0o700) is True + assert filesystem.check_mode(dst, 0o744) is True # Checking an actual change of owner is tricky during a unit test, since we do not know # if any user exists beside the current one. So we mock _copy_win_ownership. It's behavior @@ -439,7 +442,7 @@ class CopyOwnershipAndModeTest(test_util.TempDirTestCase): filesystem.copy_ownership_and_mode(src, dst) mock_copy_owner.assert_called_once_with(src, dst) - self.assertIs(filesystem.check_mode(dst, 0o700), True) + assert filesystem.check_mode(dst, 0o700) is True class CheckPermissionsTest(test_util.TempDirTestCase): @@ -449,14 +452,14 @@ class CheckPermissionsTest(test_util.TempDirTestCase): self.probe_path = _create_probe(self.tempdir) def test_check_mode(self): - self.assertIs(filesystem.check_mode(self.probe_path, 0o744), True) + assert filesystem.check_mode(self.probe_path, 0o744) is True filesystem.chmod(self.probe_path, 0o700) - self.assertFalse(filesystem.check_mode(self.probe_path, 0o744)) + assert not filesystem.check_mode(self.probe_path, 0o744) @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') def test_check_owner_windows(self): - self.assertIs(filesystem.check_owner(self.probe_path), True) + assert filesystem.check_owner(self.probe_path) is True system = win32security.ConvertStringSidToSid(SYSTEM_SID) security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR @@ -464,48 +467,49 @@ class CheckPermissionsTest(test_util.TempDirTestCase): with mock.patch('win32security.GetFileSecurity') as mock_get: mock_get.return_value = security - self.assertFalse(filesystem.check_owner(self.probe_path)) + assert not filesystem.check_owner(self.probe_path) @unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security') def test_check_owner_linux(self): - self.assertIs(filesystem.check_owner(self.probe_path), True) + assert filesystem.check_owner(self.probe_path) is True import os as std_os # pylint: disable=os-module-forbidden + # See related inline comment in certbot.compat.filesystem.check_owner method # that explains why MyPy/PyLint check disable is needed here. uid = std_os.getuid() with mock.patch('os.getuid') as mock_uid: mock_uid.return_value = uid + 1 - self.assertFalse(filesystem.check_owner(self.probe_path)) + assert not filesystem.check_owner(self.probe_path) def test_check_permissions(self): - self.assertIs(filesystem.check_permissions(self.probe_path, 0o744), True) + assert filesystem.check_permissions(self.probe_path, 0o744) is True with mock.patch('certbot.compat.filesystem.check_mode') as mock_mode: mock_mode.return_value = False - self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744)) + assert not filesystem.check_permissions(self.probe_path, 0o744) with mock.patch('certbot.compat.filesystem.check_owner') as mock_owner: mock_owner.return_value = False - self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744)) + assert not filesystem.check_permissions(self.probe_path, 0o744) def test_check_min_permissions(self): filesystem.chmod(self.probe_path, 0o744) - self.assertIs(filesystem.has_min_permissions(self.probe_path, 0o744), True) + assert filesystem.has_min_permissions(self.probe_path, 0o744) is True filesystem.chmod(self.probe_path, 0o700) - self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744)) + assert not filesystem.has_min_permissions(self.probe_path, 0o744) filesystem.chmod(self.probe_path, 0o741) - self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744)) + assert not filesystem.has_min_permissions(self.probe_path, 0o744) def test_is_world_reachable(self): filesystem.chmod(self.probe_path, 0o744) - self.assertIs(filesystem.has_world_permissions(self.probe_path), True) + assert filesystem.has_world_permissions(self.probe_path) is True filesystem.chmod(self.probe_path, 0o700) - self.assertFalse(filesystem.has_world_permissions(self.probe_path)) + assert not filesystem.has_world_permissions(self.probe_path) class OsReplaceTest(test_util.TempDirTestCase): @@ -520,8 +524,8 @@ class OsReplaceTest(test_util.TempDirTestCase): # On Windows, a direct call to os.rename would fail because dst already exists. filesystem.replace(src, dst) - self.assertFalse(os.path.exists(src)) - self.assertIs(os.path.exists(dst), True) + assert not os.path.exists(src) + assert os.path.exists(dst) is True class RealpathTest(test_util.TempDirTestCase): @@ -537,8 +541,8 @@ class RealpathTest(test_util.TempDirTestCase): link_path = os.path.join(self.tempdir, 'link_abs') os.symlink(self.probe_path, link_path) - self.assertEqual(self.probe_path, filesystem.realpath(self.probe_path)) - self.assertEqual(self.probe_path, filesystem.realpath(link_path)) + assert self.probe_path == filesystem.realpath(self.probe_path) + assert self.probe_path == filesystem.realpath(link_path) # Relative resolution curdir = os.getcwd() @@ -548,8 +552,8 @@ class RealpathTest(test_util.TempDirTestCase): os.chdir(os.path.dirname(self.probe_path)) os.symlink(probe_name, link_path) - self.assertEqual(self.probe_path, filesystem.realpath(probe_name)) - self.assertEqual(self.probe_path, filesystem.realpath(link_path)) + assert self.probe_path == filesystem.realpath(probe_name) + assert self.probe_path == filesystem.realpath(link_path) finally: os.chdir(curdir) @@ -561,9 +565,8 @@ class RealpathTest(test_util.TempDirTestCase): os.symlink(link2_path, link3_path) os.symlink(link3_path, link1_path) - with self.assertRaises(RuntimeError) as error: + with pytest.raises(RuntimeError, match='link1 is a loop!') as error: filesystem.realpath(link1_path) - self.assertIn('link1 is a loop!', str(error.exception)) class IsExecutableTest(test_util.TempDirTestCase): @@ -591,7 +594,7 @@ class IsExecutableTest(test_util.TempDirTestCase): with mock.patch("certbot.compat.filesystem._generate_dacl", side_effect=_execute_mock): os.close(filesystem.open(file_path, os.O_CREAT | os.O_WRONLY, 0o666)) - self.assertFalse(filesystem.is_executable(file_path)) + assert not filesystem.is_executable(file_path) @mock.patch("certbot.compat.filesystem.os.path.isfile") @mock.patch("certbot.compat.filesystem.os.access") @@ -599,7 +602,7 @@ class IsExecutableTest(test_util.TempDirTestCase): with _fix_windows_runtime(): mock_access.return_value = True mock_isfile.return_value = True - self.assertIs(filesystem.is_executable("/path/to/exe"), True) + assert filesystem.is_executable("/path/to/exe") is True @mock.patch("certbot.compat.filesystem.os.path.isfile") @mock.patch("certbot.compat.filesystem.os.access") @@ -607,7 +610,7 @@ class IsExecutableTest(test_util.TempDirTestCase): with _fix_windows_runtime(): mock_access.return_value = True mock_isfile.return_value = True - self.assertIs(filesystem.is_executable("exe"), True) + assert filesystem.is_executable("exe") is True @mock.patch("certbot.compat.filesystem.os.path.isfile") @mock.patch("certbot.compat.filesystem.os.access") @@ -615,7 +618,7 @@ class IsExecutableTest(test_util.TempDirTestCase): with _fix_windows_runtime(): mock_access.return_value = True mock_isfile.return_value = False - self.assertFalse(filesystem.is_executable("exe")) + assert not filesystem.is_executable("exe") class ReadlinkTest(unittest.TestCase): @@ -623,25 +626,25 @@ class ReadlinkTest(unittest.TestCase): @mock.patch("certbot.compat.filesystem.os.readlink") def test_path_posix(self, mock_readlink): mock_readlink.return_value = "/normal/path" - self.assertEqual(filesystem.readlink("dummy"), "/normal/path") + assert filesystem.readlink("dummy") == "/normal/path" @unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows') @mock.patch("certbot.compat.filesystem.os.readlink") def test_normal_path_windows(self, mock_readlink): # Python <3.8 mock_readlink.return_value = "C:\\short\\path" - self.assertEqual(filesystem.readlink("dummy"), "C:\\short\\path") + assert filesystem.readlink("dummy") == "C:\\short\\path" # Python >=3.8 (os.readlink always returns the extended form) mock_readlink.return_value = "\\\\?\\C:\\short\\path" - self.assertEqual(filesystem.readlink("dummy"), "C:\\short\\path") + assert filesystem.readlink("dummy") == "C:\\short\\path" @unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows') @mock.patch("certbot.compat.filesystem.os.readlink") def test_extended_path_windows(self, mock_readlink): # Following path is largely over the 260 characters permitted in the normal form. mock_readlink.return_value = "\\\\?\\C:\\long" + 1000 * "\\path" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): filesystem.readlink("dummy") @contextlib.contextmanager @@ -678,4 +681,4 @@ def _create_probe(tempdir, name='probe'): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/compat/misc_test.py b/certbot/tests/compat/misc_test.py index 5cb8167b6..cf76f36cb 100644 --- a/certbot/tests/compat/misc_test.py +++ b/certbot/tests/compat/misc_test.py @@ -1,11 +1,13 @@ """Tests for certbot.compat.misc""" -import unittest +import sys from unittest import mock +import pytest + from certbot.compat import os -class ExecuteStatusTest(unittest.TestCase): +class ExecuteStatusTest: """Tests for certbot.compat.misc.execute_command_status.""" @classmethod @@ -21,7 +23,7 @@ class ExecuteStatusTest(unittest.TestCase): mock_run.return_value.stderr = stderr mock_run.return_value.returncode = returncode with mock.patch("certbot.compat.misc.logger") as mock_logger: - self.assertEqual(self._call(given_name, given_command), (returncode, stderr, stdout)) + assert self._call(given_name, given_command) == (returncode, stderr, stdout) executed_command = mock_run.call_args[1].get( "args", mock_run.call_args[0][0]) @@ -29,8 +31,8 @@ class ExecuteStatusTest(unittest.TestCase): expected_command = ['powershell.exe', '-Command', given_command] else: expected_command = given_command - self.assertEqual(executed_command, expected_command) - self.assertEqual(executed_command, expected_command) + assert executed_command == expected_command + assert executed_command == expected_command mock_logger.info.assert_any_call("Running %s command: %s", given_name, given_command) @@ -43,4 +45,4 @@ class ExecuteStatusTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/compat/os_test.py b/certbot/tests/compat/os_test.py index 2fe23f700..5bbc70fed 100644 --- a/certbot/tests/compat/os_test.py +++ b/certbot/tests/compat/os_test.py @@ -1,20 +1,22 @@ """Unit test for os module.""" -import unittest +import sys + +import pytest from certbot.compat import os -class OsTest(unittest.TestCase): - """Unit tests for os module.""" - def test_forbidden_methods(self): - # Checks for os module - for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename', - 'replace', 'access', 'stat', 'fstat']: - self.assertRaises(RuntimeError, getattr(os, method)) - # Checks for os.path module - for method in ['realpath']: - self.assertRaises(RuntimeError, getattr(os.path, method)) +def test_forbidden_methods(): + # Checks for os module + for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename', + 'replace', 'access', 'stat', 'fstat']: + with pytest.raises(RuntimeError): + getattr(os, method)() + # Checks for os.path module + for method in ['realpath']: + with pytest.raises(RuntimeError): + getattr(os.path, method)() if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/configuration_test.py b/certbot/tests/configuration_test.py index 61c902bc9..9482aec2f 100644 --- a/certbot/tests/configuration_test.py +++ b/certbot/tests/configuration_test.py @@ -1,6 +1,10 @@ """Tests for certbot.configuration.""" +import sys import unittest from unittest import mock +import warnings + +import pytest from certbot import errors from certbot._internal import constants @@ -22,20 +26,21 @@ class NamespaceConfigTest(test_util.ConfigTestCase): def test_init_same_ports(self): self.config.namespace.https_port = 4321 from certbot.configuration import NamespaceConfig - self.assertRaises(errors.Error, NamespaceConfig, self.config.namespace) + with pytest.raises(errors.Error): + NamespaceConfig(self.config.namespace) def test_proxy_getattr(self): - self.assertEqual(self.config.foo, 'bar') - self.assertEqual(self.config.work_dir, os.path.join(self.tempdir, 'work')) + assert self.config.foo == 'bar' + assert self.config.work_dir == os.path.join(self.tempdir, 'work') def test_server_path(self): - self.assertEqual(['acme-server.org:443', 'new'], - self.config.server_path.split(os.path.sep)) + assert ['acme-server.org:443', 'new'] == \ + self.config.server_path.split(os.path.sep) self.config.namespace.server = ('http://user:pass@acme.server:443' '/p/a/t/h;parameters?query#fragment') - self.assertEqual(['user:pass@acme.server:443', 'p', 'a', 't', 'h'], - self.config.server_path.split(os.path.sep)) + assert ['user:pass@acme.server:443', 'p', 'a', 't', 'h'] == \ + self.config.server_path.split(os.path.sep) @mock.patch('certbot.configuration.constants') def test_dynamic_dirs(self, mock_constants): @@ -49,24 +54,20 @@ class NamespaceConfigTest(test_util.ConfigTestCase): ref_path = misc.underscores_for_unsupported_characters_in_path( 'acc/acme-server.org:443/new') - self.assertEqual( - os.path.normpath(self.config.accounts_dir), - os.path.normpath(os.path.join(self.config.config_dir, ref_path))) - self.assertEqual( - os.path.normpath(self.config.backup_dir), - os.path.normpath(os.path.join(self.config.work_dir, 'backups'))) - self.assertEqual( - os.path.normpath(self.config.csr_dir), - os.path.normpath(os.path.join(self.config.config_dir, 'csr'))) - self.assertEqual( - os.path.normpath(self.config.in_progress_dir), - os.path.normpath(os.path.join(self.config.work_dir, '../p'))) - self.assertEqual( - os.path.normpath(self.config.key_dir), - os.path.normpath(os.path.join(self.config.config_dir, 'keys'))) - self.assertEqual( - os.path.normpath(self.config.temp_checkpoint_dir), - os.path.normpath(os.path.join(self.config.work_dir, 't'))) + assert os.path.normpath(self.config.accounts_dir) == \ + os.path.normpath(os.path.join(self.config.config_dir, ref_path)) + assert os.path.normpath(self.config.backup_dir) == \ + os.path.normpath(os.path.join(self.config.work_dir, 'backups')) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + assert os.path.normpath(self.config.csr_dir) == \ + os.path.normpath(os.path.join(self.config.config_dir, 'csr')) + assert os.path.normpath(self.config.key_dir) == \ + os.path.normpath(os.path.join(self.config.config_dir, 'keys')) + assert os.path.normpath(self.config.in_progress_dir) == \ + os.path.normpath(os.path.join(self.config.work_dir, '../p')) + assert os.path.normpath(self.config.temp_checkpoint_dir) == \ + os.path.normpath(os.path.join(self.config.work_dir, 't')) def test_absolute_paths(self): from certbot.configuration import NamespaceConfig @@ -86,21 +87,23 @@ class NamespaceConfigTest(test_util.ConfigTestCase): mock_namespace.server = server config = NamespaceConfig(mock_namespace) - self.assertTrue(os.path.isabs(config.config_dir)) - self.assertEqual(config.config_dir, - os.path.join(os.getcwd(), config_base)) - self.assertTrue(os.path.isabs(config.work_dir)) - self.assertEqual(config.work_dir, - os.path.join(os.getcwd(), work_base)) - self.assertTrue(os.path.isabs(config.logs_dir)) - self.assertEqual(config.logs_dir, - os.path.join(os.getcwd(), logs_base)) - self.assertTrue(os.path.isabs(config.accounts_dir)) - self.assertTrue(os.path.isabs(config.backup_dir)) - self.assertTrue(os.path.isabs(config.csr_dir)) - self.assertTrue(os.path.isabs(config.in_progress_dir)) - self.assertTrue(os.path.isabs(config.key_dir)) - self.assertTrue(os.path.isabs(config.temp_checkpoint_dir)) + assert os.path.isabs(config.config_dir) + assert config.config_dir == \ + os.path.join(os.getcwd(), config_base) + assert os.path.isabs(config.work_dir) + assert config.work_dir == \ + os.path.join(os.getcwd(), work_base) + assert os.path.isabs(config.logs_dir) + assert config.logs_dir == \ + os.path.join(os.getcwd(), logs_base) + assert os.path.isabs(config.accounts_dir) + assert os.path.isabs(config.backup_dir) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + assert os.path.isabs(config.csr_dir) + assert os.path.isabs(config.key_dir) + assert os.path.isabs(config.in_progress_dir) + assert os.path.isabs(config.temp_checkpoint_dir) @mock.patch('certbot.configuration.constants') def test_renewal_dynamic_dirs(self, mock_constants): @@ -108,13 +111,10 @@ class NamespaceConfigTest(test_util.ConfigTestCase): mock_constants.LIVE_DIR = 'l' mock_constants.RENEWAL_CONFIGS_DIR = 'renewal_configs' - self.assertEqual( - self.config.default_archive_dir, os.path.join(self.config.config_dir, 'a')) - self.assertEqual( - self.config.live_dir, os.path.join(self.config.config_dir, 'l')) - self.assertEqual( - self.config.renewal_configs_dir, os.path.join( - self.config.config_dir, 'renewal_configs')) + assert self.config.default_archive_dir == os.path.join(self.config.config_dir, 'a') + assert self.config.live_dir == os.path.join(self.config.config_dir, 'l') + assert self.config.renewal_configs_dir == os.path.join( + self.config.config_dir, 'renewal_configs') def test_renewal_absolute_paths(self): from certbot.configuration import NamespaceConfig @@ -132,30 +132,30 @@ class NamespaceConfigTest(test_util.ConfigTestCase): mock_namespace.logs_dir = logs_base config = NamespaceConfig(mock_namespace) - self.assertTrue(os.path.isabs(config.default_archive_dir)) - self.assertTrue(os.path.isabs(config.live_dir)) - self.assertTrue(os.path.isabs(config.renewal_configs_dir)) + assert os.path.isabs(config.default_archive_dir) + assert os.path.isabs(config.live_dir) + assert os.path.isabs(config.renewal_configs_dir) def test_get_and_set_attr(self): self.config.foo = 42 - self.assertEqual(self.config.namespace.foo, 42) + assert self.config.namespace.foo == 42 self.config.namespace.bar = 1337 - self.assertEqual(self.config.bar, 1337) + assert self.config.bar == 1337 def test_hook_directories(self): - self.assertEqual(self.config.renewal_hooks_dir, + assert self.config.renewal_hooks_dir == \ os.path.join(self.config.config_dir, - constants.RENEWAL_HOOKS_DIR)) - self.assertEqual(self.config.renewal_pre_hooks_dir, + constants.RENEWAL_HOOKS_DIR) + assert self.config.renewal_pre_hooks_dir == \ os.path.join(self.config.renewal_hooks_dir, - constants.RENEWAL_PRE_HOOKS_DIR)) - self.assertEqual(self.config.renewal_deploy_hooks_dir, + constants.RENEWAL_PRE_HOOKS_DIR) + assert self.config.renewal_deploy_hooks_dir == \ os.path.join(self.config.renewal_hooks_dir, - constants.RENEWAL_DEPLOY_HOOKS_DIR)) - self.assertEqual(self.config.renewal_post_hooks_dir, + constants.RENEWAL_DEPLOY_HOOKS_DIR) + assert self.config.renewal_post_hooks_dir == \ os.path.join(self.config.renewal_hooks_dir, - constants.RENEWAL_POST_HOOKS_DIR)) + constants.RENEWAL_POST_HOOKS_DIR) if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/conftest.py b/certbot/tests/conftest.py new file mode 100644 index 000000000..64ae64983 --- /dev/null +++ b/certbot/tests/conftest.py @@ -0,0 +1,8 @@ +import pytest + +from certbot._internal import cli + + +@pytest.fixture(autouse=True) +def reset_cli_global(): + cli.set_by_cli.detector = None diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index 3031cf531..49efef4ea 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -1,9 +1,12 @@ """Tests for certbot.crypto_util.""" import logging +import re +import sys import unittest from unittest import mock import OpenSSL +import pytest from certbot import errors from certbot import util @@ -52,14 +55,15 @@ class GenerateKeyTest(test_util.TempDirTestCase): def test_success(self, mock_make): mock_make.return_value = b'key_pem' key = self._call(1024, self.workdir) - self.assertEqual(key.pem, b'key_pem') - self.assertIn('key-certbot.pem', key.file) - self.assertTrue(os.path.exists(os.path.join(self.workdir, key.file))) + assert key.pem == b'key_pem' + assert 'key-certbot.pem' in key.file + assert os.path.exists(os.path.join(self.workdir, key.file)) @mock.patch('certbot.crypto_util.make_key') def test_key_failure(self, mock_make): mock_make.side_effect = ValueError - self.assertRaises(ValueError, self._call, 431, self.workdir) + with pytest.raises(ValueError): + self._call(431, self.workdir) class GenerateCSRTest(test_util.TempDirTestCase): @@ -74,8 +78,8 @@ class GenerateCSRTest(test_util.TempDirTestCase): csr = generate_csr( mock.Mock(pem='dummy_key'), 'example.com', self.tempdir, strict_permissions=True) - self.assertEqual(csr.data, b'csr_pem') - self.assertIn('csr-certbot.pem', csr.file) + assert csr.data == b'csr_pem' + assert 'csr-certbot.pem' in csr.file class ValidCSRTest(unittest.TestCase): @@ -87,19 +91,19 @@ class ValidCSRTest(unittest.TestCase): return valid_csr(csr) def test_valid_pem_true(self): - self.assertTrue(self._call(test_util.load_vector('csr_512.pem'))) + assert self._call(test_util.load_vector('csr_512.pem')) def test_valid_pem_san_true(self): - self.assertTrue(self._call(test_util.load_vector('csr-san_512.pem'))) + assert self._call(test_util.load_vector('csr-san_512.pem')) def test_valid_der_false(self): - self.assertFalse(self._call(test_util.load_vector('csr_512.der'))) + assert not self._call(test_util.load_vector('csr_512.der')) def test_empty_false(self): - self.assertFalse(self._call('')) + assert not self._call('') def test_random_false(self): - self.assertFalse(self._call('foo bar')) + assert not self._call('foo bar') class CSRMatchesPubkeyTest(unittest.TestCase): @@ -111,12 +115,12 @@ class CSRMatchesPubkeyTest(unittest.TestCase): return csr_matches_pubkey(*args, **kwargs) def test_valid_true(self): - self.assertTrue(self._call( - test_util.load_vector('csr_512.pem'), RSA512_KEY)) + assert self._call( + test_util.load_vector('csr_512.pem'), RSA512_KEY) def test_invalid_false(self): - self.assertFalse(self._call( - test_util.load_vector('csr_512.pem'), RSA256_KEY)) + assert not self._call( + test_util.load_vector('csr_512.pem'), RSA256_KEY) class ImportCSRFileTest(unittest.TestCase): @@ -132,29 +136,27 @@ class ImportCSRFileTest(unittest.TestCase): data = test_util.load_vector('csr_512.der') data_pem = test_util.load_vector('csr_512.pem') - self.assertEqual( - (OpenSSL.crypto.FILETYPE_PEM, + assert (OpenSSL.crypto.FILETYPE_PEM, util.CSR(file=csrfile, data=data_pem, form="pem"), - ["Example.com"]), - self._call(csrfile, data)) + ["Example.com"]) == \ + self._call(csrfile, data) def test_pem_csr(self): csrfile = test_util.vector_path('csr_512.pem') data = test_util.load_vector('csr_512.pem') - self.assertEqual( - (OpenSSL.crypto.FILETYPE_PEM, + assert (OpenSSL.crypto.FILETYPE_PEM, util.CSR(file=csrfile, data=data, form="pem"), - ["Example.com"],), - self._call(csrfile, data)) + ["Example.com"],) == \ + self._call(csrfile, data) def test_bad_csr(self): - self.assertRaises(errors.Error, self._call, - test_util.vector_path('cert_512.pem'), + with pytest.raises(errors.Error): + self._call(test_util.vector_path('cert_512.pem'), test_util.load_vector('cert_512.pem')) @@ -164,6 +166,7 @@ class MakeKeyTest(unittest.TestCase): def test_rsa(self): # pylint: disable=no-self-use # RSA Key Type Test from certbot.crypto_util import make_key + # Do not test larger keys as it takes too long. OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, make_key(1024)) @@ -176,41 +179,28 @@ class MakeKeyTest(unittest.TestCase): OpenSSL.crypto.FILETYPE_PEM, make_key(elliptic_curve=name, key_type='ecdsa') ) - self.assertEqual(pkey.bits(), bits) + assert pkey.bits() == bits def test_bad_key_sizes(self): from certbot.crypto_util import make_key + # Try a bad key size for RSA and ECDSA - with self.assertRaises(errors.Error) as e: + with pytest.raises(errors.Error, match='Unsupported RSA key length: 512'): make_key(bits=512, key_type='rsa') - self.assertEqual( - "Unsupported RSA key length: 512", - str(e.exception), - "Unsupported RSA key length: 512" - ) def test_bad_elliptic_curve_name(self): from certbot.crypto_util import make_key - with self.assertRaises(errors.Error) as e: + with pytest.raises(errors.Error, match='Unsupported elliptic curve: nothere'): make_key(elliptic_curve="nothere", key_type='ecdsa') - self.assertEqual( - "Unsupported elliptic curve: nothere", - str(e.exception), - "Unsupported elliptic curve: nothere" - ) def test_bad_key_type(self): from certbot.crypto_util import make_key # Try a bad --key-type - with self.assertRaises(errors.Error) as e: + with pytest.raises(errors.Error, + match=re.escape('Invalid key_type specified: unf. Use [rsa|ecdsa]')): OpenSSL.crypto.load_privatekey( OpenSSL.crypto.FILETYPE_PEM, make_key(1024, key_type='unf')) - self.assertEqual( - "Invalid key_type specified: unf. Use [rsa|ecdsa]", - str(e.exception), - "Invalid key_type specified: unf. Use [rsa|ecdsa]", - ) class VerifyCertSetup(unittest.TestCase): @@ -237,11 +227,12 @@ class VerifyRenewableCertTest(VerifyCertSetup): return verify_renewable_cert(renewable_cert) def test_verify_renewable_cert(self): - self.assertIsNone(self._call(self.renewable_cert)) + assert self._call(self.renewable_cert) is None @mock.patch('certbot.crypto_util.verify_renewable_cert_sig', side_effect=errors.Error("")) def test_verify_renewable_cert_failure(self, unused_verify_renewable_cert_sign): - self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + with pytest.raises(errors.Error): + self._call(self.bad_renewable_cert) class VerifyRenewableCertSigTest(VerifyCertSetup): @@ -252,18 +243,19 @@ class VerifyRenewableCertSigTest(VerifyCertSetup): return verify_renewable_cert_sig(renewable_cert) def test_cert_sig_match(self): - self.assertIsNone(self._call(self.renewable_cert)) + assert self._call(self.renewable_cert) is None def test_cert_sig_match_ec(self): renewable_cert = mock.MagicMock() renewable_cert.cert_path = P256_CERT_PATH renewable_cert.chain_path = P256_CERT_PATH renewable_cert.key_path = P256_KEY - self.assertIsNone(self._call(renewable_cert)) + assert self._call(renewable_cert) is None def test_cert_sig_mismatch(self): self.bad_renewable_cert.cert_path = test_util.vector_path('cert_512_bad.pem') - self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + with pytest.raises(errors.Error): + self._call(self.bad_renewable_cert) class VerifyFullchainTest(VerifyCertSetup): @@ -274,14 +266,16 @@ class VerifyFullchainTest(VerifyCertSetup): return verify_fullchain(renewable_cert) def test_fullchain_matches(self): - self.assertIsNone(self._call(self.renewable_cert)) + assert self._call(self.renewable_cert) is None def test_fullchain_mismatch(self): - self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + with pytest.raises(errors.Error): + self._call(self.bad_renewable_cert) def test_fullchain_ioerror(self): self.bad_renewable_cert.chain = "dog" - self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + with pytest.raises(errors.Error): + self._call(self.bad_renewable_cert) class VerifyCertMatchesPrivKeyTest(VerifyCertSetup): @@ -294,13 +288,14 @@ class VerifyCertMatchesPrivKeyTest(VerifyCertSetup): def test_cert_priv_key_match(self): self.renewable_cert.cert = SS_CERT_PATH self.renewable_cert.privkey = RSA2048_KEY_PATH - self.assertIsNone(self._call(self.renewable_cert)) + assert self._call(self.renewable_cert) is None def test_cert_priv_key_mismatch(self): self.bad_renewable_cert.privkey = RSA256_KEY_PATH self.bad_renewable_cert.cert = SS_CERT_PATH - self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + with pytest.raises(errors.Error): + self._call(self.bad_renewable_cert) class ValidPrivkeyTest(unittest.TestCase): @@ -312,13 +307,13 @@ class ValidPrivkeyTest(unittest.TestCase): return valid_privkey(privkey) def test_valid_true(self): - self.assertTrue(self._call(RSA512_KEY)) + assert self._call(RSA512_KEY) def test_empty_false(self): - self.assertFalse(self._call('')) + assert not self._call('') def test_random_false(self): - self.assertFalse(self._call('foo bar')) + assert not self._call('foo bar') class GetSANsFromCertTest(unittest.TestCase): @@ -330,12 +325,11 @@ class GetSANsFromCertTest(unittest.TestCase): return get_sans_from_cert(*args, **kwargs) def test_single(self): - self.assertEqual([], self._call(test_util.load_vector('cert_512.pem'))) + assert [] == self._call(test_util.load_vector('cert_512.pem')) def test_san(self): - self.assertEqual( - ['example.com', 'www.example.com'], - self._call(test_util.load_vector('cert-san_512.pem'))) + assert ['example.com', 'www.example.com'] == \ + self._call(test_util.load_vector('cert-san_512.pem')) class GetNamesFromCertTest(unittest.TestCase): @@ -347,24 +341,22 @@ class GetNamesFromCertTest(unittest.TestCase): return get_names_from_cert(*args, **kwargs) def test_single(self): - self.assertEqual( - ['example.com'], - self._call(test_util.load_vector('cert_512.pem'))) + assert ['example.com'] == \ + self._call(test_util.load_vector('cert_512.pem')) def test_san(self): - self.assertEqual( - ['example.com', 'www.example.com'], - self._call(test_util.load_vector('cert-san_512.pem'))) + assert ['example.com', 'www.example.com'] == \ + self._call(test_util.load_vector('cert-san_512.pem')) def test_common_name_sans_order(self): # Tests that the common name comes first # followed by the SANS in alphabetical order - self.assertEqual( - ['example.com'] + ['{0}.example.com'.format(c) for c in 'abcd'], - self._call(test_util.load_vector('cert-5sans_512.pem'))) + assert ['example.com'] + ['{0}.example.com'.format(c) for c in 'abcd'] == \ + self._call(test_util.load_vector('cert-5sans_512.pem')) def test_parse_non_cert(self): - self.assertRaises(OpenSSL.crypto.Error, self._call, "hello there") + with pytest.raises(OpenSSL.crypto.Error): + self._call("hello there") class GetNamesFromReqTest(unittest.TestCase): @@ -376,26 +368,22 @@ class GetNamesFromReqTest(unittest.TestCase): return get_names_from_req(*args, **kwargs) def test_nonames(self): - self.assertEqual( - [], - self._call(test_util.load_vector('csr-nonames_512.pem'))) + assert [] == \ + self._call(test_util.load_vector('csr-nonames_512.pem')) def test_nosans(self): - self.assertEqual( - ['example.com'], - self._call(test_util.load_vector('csr-nosans_512.pem'))) + assert ['example.com'] == \ + self._call(test_util.load_vector('csr-nosans_512.pem')) def test_sans(self): - self.assertEqual( - ['example.com', 'example.org', 'example.net', 'example.info', - 'subdomain.example.com', 'other.subdomain.example.com'], - self._call(test_util.load_vector('csr-6sans_512.pem'))) + assert ['example.com', 'example.org', 'example.net', 'example.info', + 'subdomain.example.com', 'other.subdomain.example.com'] == \ + self._call(test_util.load_vector('csr-6sans_512.pem')) def test_der(self): from OpenSSL.crypto import FILETYPE_ASN1 - self.assertEqual( - ['Example.com'], - self._call(test_util.load_vector('csr_512.der'), typ=FILETYPE_ASN1)) + assert ['Example.com'] == \ + self._call(test_util.load_vector('csr_512.der'), typ=FILETYPE_ASN1) class CertLoaderTest(unittest.TestCase): @@ -405,14 +393,14 @@ class CertLoaderTest(unittest.TestCase): from certbot.crypto_util import pyopenssl_load_certificate cert, file_type = pyopenssl_load_certificate(CERT) - self.assertEqual(cert.digest('sha256'), - OpenSSL.crypto.load_certificate(file_type, CERT).digest('sha256')) + assert cert.digest('sha256') == \ + OpenSSL.crypto.load_certificate(file_type, CERT).digest('sha256') def test_load_invalid_cert(self): from certbot.crypto_util import pyopenssl_load_certificate bad_cert_data = CERT.replace(b"BEGIN CERTIFICATE", b"ASDFASDFASDF!!!") - self.assertRaises( - errors.Error, pyopenssl_load_certificate, bad_cert_data) + with pytest.raises(errors.Error): + pyopenssl_load_certificate(bad_cert_data) class NotBeforeTest(unittest.TestCase): @@ -420,8 +408,8 @@ class NotBeforeTest(unittest.TestCase): def test_notBefore(self): from certbot.crypto_util import notBefore - self.assertEqual(notBefore(CERT_PATH).isoformat(), - '2014-12-11T22:34:45+00:00') + assert notBefore(CERT_PATH).isoformat() == \ + '2014-12-11T22:34:45+00:00' class NotAfterTest(unittest.TestCase): @@ -429,16 +417,16 @@ class NotAfterTest(unittest.TestCase): def test_notAfter(self): from certbot.crypto_util import notAfter - self.assertEqual(notAfter(CERT_PATH).isoformat(), - '2014-12-18T22:34:45+00:00') + assert notAfter(CERT_PATH).isoformat() == \ + '2014-12-18T22:34:45+00:00' class Sha256sumTest(unittest.TestCase): """Tests for certbot.crypto_util.notAfter""" def test_sha256sum(self): from certbot.crypto_util import sha256sum - self.assertEqual(sha256sum(CERT_PATH), - '914ffed8daf9e2c99d90ac95c77d54f32cbd556672facac380f0c063498df84e') + assert sha256sum(CERT_PATH) == \ + '914ffed8daf9e2c99d90ac95c77d54f32cbd556672facac380f0c063498df84e' class CertAndChainFromFullchainTest(unittest.TestCase): @@ -466,10 +454,11 @@ class CertAndChainFromFullchainTest(unittest.TestCase): for fullchain in (fullchain_pem, spacey_fullchain_pem, crlf_fullchain_pem, acmev1_fullchain_pem): cert_out, chain_out = cert_and_chain_from_fullchain(fullchain) - self.assertEqual(cert_out, cert_pem) - self.assertEqual(chain_out, chain_pem) + assert cert_out == cert_pem + assert chain_out == chain_pem - self.assertRaises(errors.Error, cert_and_chain_from_fullchain, cert_pem) + with pytest.raises(errors.Error): + cert_and_chain_from_fullchain(cert_pem) class FindChainWithIssuerTest(unittest.TestCase): @@ -488,7 +477,7 @@ class FindChainWithIssuerTest(unittest.TestCase): """Correctly pick the chain based on the root's CN""" fullchains = self._all_fullchains() matched = self._call(fullchains, "Pebble Root CA 0cc6f0") - self.assertEqual(matched, fullchains[1]) + assert matched == fullchains[1] @mock.patch('certbot.crypto_util.logger.info') def test_intermediate_match(self, mock_info): @@ -500,14 +489,14 @@ class FindChainWithIssuerTest(unittest.TestCase): # function under test here doesn't care about that. fullchains[1] = fullchains[1] + CERT_ISSUER.decode() matched = self._call(fullchains, "Pebble Root CA 0cc6f0") - self.assertEqual(matched, fullchains[0]) + assert matched == fullchains[0] mock_info.assert_not_called() @mock.patch('certbot.crypto_util.logger.info') def test_no_match(self, mock_info): fullchains = self._all_fullchains() matched = self._call(fullchains, "non-existent issuer") - self.assertEqual(matched, fullchains[0]) + assert matched == fullchains[0] mock_info.assert_not_called() @mock.patch('certbot.crypto_util.logger.warning') @@ -515,7 +504,7 @@ class FindChainWithIssuerTest(unittest.TestCase): fullchains = self._all_fullchains() matched = self._call(fullchains, "non-existent issuer", warn_on_no_match=True) - self.assertEqual(matched, fullchains[0]) + assert matched == fullchains[0] mock_warning.assert_called_once_with("Certbot has been configured to prefer " "certificate chains with issuer '%s', but no chain from the CA matched " "this issuer. Using the default certificate chain instead.", @@ -523,4 +512,4 @@ class FindChainWithIssuerTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/display/completer_test.py b/certbot/tests/display/completer_test.py index 73722151a..ef013d520 100644 --- a/certbot/tests/display/completer_test.py +++ b/certbot/tests/display/completer_test.py @@ -1,19 +1,22 @@ """Test certbot._internal.display.completer.""" +from importlib import reload as reload_module +import string +import sys from typing import List +import unittest +from unittest import mock + +import pytest + +from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util try: import readline # pylint: disable=import-error except ImportError: import certbot._internal.display.dummy_readline as readline # type: ignore -from importlib import reload as reload_module -import string -import sys -import unittest -from unittest import mock -from certbot.compat import filesystem -from certbot.compat import os -import certbot.tests.util as test_util class CompleterTest(test_util.TempDirTestCase): @@ -45,12 +48,12 @@ class CompleterTest(test_util.TempDirTestCase): for i in range(num_paths): completion = my_completer.complete(self.tempdir, i) - self.assertIn(completion, self.paths) + assert completion in self.paths self.paths.remove(completion) - self.assertEqual(len(self.paths), 0) + assert len(self.paths) == 0 completion = my_completer.complete(self.tempdir, num_paths) - self.assertIsNone(completion) + assert completion is None @unittest.skipIf('readline' not in sys.modules, reason='Not relevant if readline is not available.') @@ -72,8 +75,8 @@ class CompleterTest(test_util.TempDirTestCase): with completer.Completer(): pass - self.assertEqual(readline.get_completer(), original_completer) - self.assertEqual(readline.get_completer_delims(), original_delims) + assert readline.get_completer() == original_completer + assert readline.get_completer_delims() == original_delims @mock.patch('certbot._internal.display.completer.readline', autospec=True) def test_context_manager_libedit(self, mock_readline): @@ -93,7 +96,7 @@ class CompleterTest(test_util.TempDirTestCase): with completer.Completer(): pass - self.assertIs(mock_readline.parse_and_bind.called, True) + assert mock_readline.parse_and_bind.called is True def enable_tab_completion(unused_command): @@ -104,4 +107,4 @@ def enable_tab_completion(unused_command): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/display/internal_util_test.py b/certbot/tests/display/internal_util_test.py index b29396c41..7af3d5d2f 100644 --- a/certbot/tests/display/internal_util_test.py +++ b/certbot/tests/display/internal_util_test.py @@ -1,10 +1,13 @@ """Test :mod:`certbot._internal.display.util`.""" import io import socket +import sys import tempfile import unittest from unittest import mock +import pytest + from acme import messages as acme_messages from certbot import errors @@ -18,7 +21,7 @@ class WrapLinesTest(unittest.TestCase): "really really really really long line...".format('\n')) text = wrap_lines(msg) - self.assertEqual(text.count('\n'), 3) + assert text.count('\n') == 3 class PlaceParensTest(unittest.TestCase): @@ -28,11 +31,11 @@ class PlaceParensTest(unittest.TestCase): return parens_around_char(label) def test_single_letter(self): - self.assertEqual("(a)", self._call("a")) + assert "(a)" == self._call("a") def test_multiple(self): - self.assertEqual("(L)abel", self._call("Label")) - self.assertEqual("(y)es please", self._call("yes please")) + assert "(L)abel" == self._call("Label") + assert "(y)es please" == self._call("yes please") class InputWithTimeoutTest(unittest.TestCase): @@ -45,14 +48,15 @@ class InputWithTimeoutTest(unittest.TestCase): def test_eof(self): with tempfile.TemporaryFile("r+") as f: with mock.patch("certbot._internal.display.util.sys.stdin", new=f): - self.assertRaises(EOFError, self._call) + with pytest.raises(EOFError): + self._call() def test_input(self, prompt=None): expected = "foo bar" stdin = io.StringIO(expected + "\n") with mock.patch("certbot.compat.misc.select.select") as mock_select: mock_select.return_value = ([stdin], [], [],) - self.assertEqual(self._call(prompt), expected) + assert self._call(prompt) == expected @mock.patch("certbot._internal.display.util.sys.stdout") def test_input_with_prompt(self, mock_stdout): @@ -66,7 +70,8 @@ class InputWithTimeoutTest(unittest.TestCase): stdin.bind(('', 0)) stdin.listen(1) with mock.patch("certbot._internal.display.util.sys.stdin", stdin): - self.assertRaises(errors.Error, self._call, timeout=0.001) + with pytest.raises(errors.Error): + self._call(timeout=0.001) stdin.close() @@ -81,13 +86,13 @@ class SeparateListInputTest(unittest.TestCase): return separate_list_input(input_) def test_commas(self): - self.assertEqual(self._call("a,b,c,test"), self.exp) + assert self._call("a,b,c,test") == self.exp def test_spaces(self): - self.assertEqual(self._call("a b c test"), self.exp) + assert self._call("a b c test") == self.exp def test_both(self): - self.assertEqual(self._call("a, b, c, test"), self.exp) + assert self._call("a, b, c, test") == self.exp def test_mess(self): actual = [ @@ -97,7 +102,7 @@ class SeparateListInputTest(unittest.TestCase): ] for act in actual: - self.assertEqual(act, self.exp) + assert act == self.exp class SummarizeDomainListTest(unittest.TestCase): @@ -107,18 +112,18 @@ class SummarizeDomainListTest(unittest.TestCase): return summarize_domain_list(domains) def test_single_domain(self): - self.assertEqual("example.com", self._call(["example.com"])) + assert "example.com" == self._call(["example.com"]) def test_two_domains(self): - self.assertEqual("example.com and example.org", - self._call(["example.com", "example.org"])) + assert "example.com and example.org" == \ + self._call(["example.com", "example.org"]) def test_many_domains(self): - self.assertEqual("example.com and 2 more domains", - self._call(["example.com", "example.org", "a.example.com"])) + assert "example.com and 2 more domains" == \ + self._call(["example.com", "example.org", "a.example.com"]) def test_empty_domains(self): - self.assertEqual("", self._call([])) + assert "" == self._call([]) class DescribeACMEErrorTest(unittest.TestCase): @@ -131,19 +136,18 @@ class DescribeACMEErrorTest(unittest.TestCase): acme_messages.Error(typ=typ, title=title, detail=detail)) def test_title_and_detail(self): - self.assertEqual("Unacceptable CSR :: CSR contained unknown extensions", self._call()) + assert "Unacceptable CSR :: CSR contained unknown extensions" == self._call() def test_detail(self): - self.assertEqual("CSR contained unknown extensions", self._call(title=None)) + assert "CSR contained unknown extensions" == self._call(title=None) def test_description(self): - self.assertEqual(acme_messages.ERROR_CODES["badCSR"], self._call(title=None, detail=None)) + assert acme_messages.ERROR_CODES["badCSR"] == self._call(title=None, detail=None) def test_unknown_type(self): - self.assertEqual( - "urn:ietf:params:acme:error:unknownErrorType", - self._call(typ="urn:ietf:params:acme:error:unknownErrorType", title=None, detail=None)) + assert "urn:ietf:params:acme:error:unknownErrorType" == \ + self._call(typ="urn:ietf:params:acme:error:unknownErrorType", title=None, detail=None) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/display/obj_test.py b/certbot/tests/display/obj_test.py index 4da2c3b3b..be9bd3242 100644 --- a/certbot/tests/display/obj_test.py +++ b/certbot/tests/display/obj_test.py @@ -1,7 +1,10 @@ """Test :mod:`certbot._internal.display.obj`.""" +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot._internal.display import obj as display_obj from certbot.display import util as display_util @@ -27,7 +30,7 @@ class FileOutputDisplayTest(unittest.TestCase): self.displayer.notification("message", False) string = self.mock_stdout.write.call_args[0][0] - self.assertIn("message", string) + assert "message" in string mock_logger.debug.assert_called_with("Notifying user: %s", "message") def test_notification_pause(self): @@ -35,146 +38,144 @@ class FileOutputDisplayTest(unittest.TestCase): with mock.patch(input_with_timeout, return_value="enter"): self.displayer.notification("message", force_interactive=True) - self.assertIn("message", self.mock_stdout.write.call_args[0][0]) + assert "message" in self.mock_stdout.write.call_args[0][0] def test_notification_noninteractive(self): self._force_noninteractive(self.displayer.notification, "message") string = self.mock_stdout.write.call_args[0][0] - self.assertIn("message", string) + assert "message" in string def test_notification_noninteractive2(self): # The main purpose of this test is to make sure we only call # logger.warning once which _force_noninteractive checks internally self._force_noninteractive(self.displayer.notification, "message") string = self.mock_stdout.write.call_args[0][0] - self.assertIn("message", string) + assert "message" in string - self.assertTrue(self.displayer.skipped_interaction) + assert self.displayer.skipped_interaction self._force_noninteractive(self.displayer.notification, "message2") string = self.mock_stdout.write.call_args[0][0] - self.assertIn("message2", string) + assert "message2" in string def test_notification_decoration(self): from certbot.compat import os self.displayer.notification("message", pause=False, decorate=False) string = self.mock_stdout.write.call_args[0][0] - self.assertEqual(string, "message" + os.linesep) + assert string == "message" + os.linesep self.displayer.notification("message2", pause=False) string = self.mock_stdout.write.call_args[0][0] - self.assertIn("- - - ", string) - self.assertIn("message2" + os.linesep, string) + assert "- - - " in string + assert "message2" + os.linesep in string @mock.patch("certbot._internal.display.obj." "FileDisplay._get_valid_int_ans") def test_menu(self, mock_ans): mock_ans.return_value = (display_util.OK, 1) ret = self.displayer.menu("message", CHOICES, force_interactive=True) - self.assertEqual(ret, (display_util.OK, 0)) + assert ret == (display_util.OK, 0) def test_menu_noninteractive(self): default = 0 result = self._force_noninteractive( self.displayer.menu, "msg", CHOICES, default=default) - self.assertEqual(result, (display_util.OK, default)) + assert result == (display_util.OK, default) def test_input_cancel(self): input_with_timeout = "certbot._internal.display.util.input_with_timeout" with mock.patch(input_with_timeout, return_value="c"): code, _ = self.displayer.input("message", force_interactive=True) - self.assertTrue(code, display_util.CANCEL) + assert code, display_util.CANCEL def test_input_normal(self): input_with_timeout = "certbot._internal.display.util.input_with_timeout" with mock.patch(input_with_timeout, return_value="domain.com"): code, input_ = self.displayer.input("message", force_interactive=True) - self.assertEqual(code, display_util.OK) - self.assertEqual(input_, "domain.com") + assert code == display_util.OK + assert input_ == "domain.com" def test_input_noninteractive(self): default = "foo" code, input_ = self._force_noninteractive( self.displayer.input, "message", default=default) - self.assertEqual(code, display_util.OK) - self.assertEqual(input_, default) + assert code == display_util.OK + assert input_ == default def test_input_assertion_fail(self): # If the call to util.assert_valid_call is commented out, an # error.Error is raised, otherwise, an AssertionError is raised. - self.assertRaises(Exception, self._force_noninteractive, - self.displayer.input, "message", cli_flag="--flag") + with pytest.raises(Exception): + self._force_noninteractive(self.displayer.input, "message", cli_flag="--flag") def test_input_assertion_fail2(self): with mock.patch("certbot.display.util.assert_valid_call"): - self.assertRaises(errors.Error, self._force_noninteractive, - self.displayer.input, "msg", cli_flag="--flag") + with pytest.raises(errors.Error): + self._force_noninteractive(self.displayer.input, "msg", cli_flag="--flag") def test_yesno(self): input_with_timeout = "certbot._internal.display.util.input_with_timeout" with mock.patch(input_with_timeout, return_value="Yes"): - self.assertTrue(self.displayer.yesno( - "message", force_interactive=True)) + assert self.displayer.yesno( + "message", force_interactive=True) with mock.patch(input_with_timeout, return_value="y"): - self.assertTrue(self.displayer.yesno( - "message", force_interactive=True)) + assert self.displayer.yesno( + "message", force_interactive=True) with mock.patch(input_with_timeout, side_effect=["maybe", "y"]): - self.assertTrue(self.displayer.yesno( - "message", force_interactive=True)) + assert self.displayer.yesno( + "message", force_interactive=True) with mock.patch(input_with_timeout, return_value="No"): - self.assertFalse(self.displayer.yesno( - "message", force_interactive=True)) + assert not self.displayer.yesno( + "message", force_interactive=True) with mock.patch(input_with_timeout, side_effect=["cancel", "n"]): - self.assertFalse(self.displayer.yesno( - "message", force_interactive=True)) + assert not self.displayer.yesno( + "message", force_interactive=True) with mock.patch(input_with_timeout, return_value="a"): - self.assertTrue(self.displayer.yesno( - "msg", yes_label="Agree", force_interactive=True)) + assert self.displayer.yesno( + "msg", yes_label="Agree", force_interactive=True) def test_yesno_noninteractive(self): - self.assertTrue(self._force_noninteractive( - self.displayer.yesno, "message", default=True)) + assert self._force_noninteractive( + self.displayer.yesno, "message", default=True) @mock.patch("certbot._internal.display.util.input_with_timeout") def test_checklist_valid(self, mock_input): mock_input.return_value = "2 1" code, tag_list = self.displayer.checklist( "msg", TAGS, force_interactive=True) - self.assertEqual( - (code, set(tag_list)), (display_util.OK, {"tag1", "tag2"})) + assert (code, set(tag_list)) == (display_util.OK, {"tag1", "tag2"}) @mock.patch("certbot._internal.display.util.input_with_timeout") def test_checklist_empty(self, mock_input): mock_input.return_value = "" code, tag_list = self.displayer.checklist("msg", TAGS, force_interactive=True) - self.assertEqual( - (code, set(tag_list)), (display_util.OK, {"tag1", "tag2", "tag3"})) + assert (code, set(tag_list)) == (display_util.OK, {"tag1", "tag2", "tag3"}) @mock.patch("certbot._internal.display.util.input_with_timeout") def test_checklist_miss_valid(self, mock_input): mock_input.side_effect = ["10", "tag1 please", "1"] ret = self.displayer.checklist("msg", TAGS, force_interactive=True) - self.assertEqual(ret, (display_util.OK, ["tag1"])) + assert ret == (display_util.OK, ["tag1"]) @mock.patch("certbot._internal.display.util.input_with_timeout") def test_checklist_miss_quit(self, mock_input): mock_input.side_effect = ["10", "c"] ret = self.displayer.checklist("msg", TAGS, force_interactive=True) - self.assertEqual(ret, (display_util.CANCEL, [])) + assert ret == (display_util.CANCEL, []) def test_checklist_noninteractive(self): default = TAGS code, input_ = self._force_noninteractive( self.displayer.checklist, "msg", TAGS, default=default) - self.assertEqual(code, display_util.OK) - self.assertEqual(input_, default) + assert code == display_util.OK + assert input_ == default def test_scrub_checklist_input_valid(self): # pylint: disable=protected-access @@ -191,7 +192,7 @@ class FileOutputDisplayTest(unittest.TestCase): for i, list_ in enumerate(indices): set_tags = set( self.displayer._scrub_checklist_input(list_, TAGS)) - self.assertEqual(set_tags, exp[i]) + assert set_tags == exp[i] @mock.patch("certbot._internal.display.util.input_with_timeout") def test_directory_select(self, mock_input): @@ -200,15 +201,15 @@ class FileOutputDisplayTest(unittest.TestCase): mock_input.return_value = user_input returned = self.displayer.directory_select(*args) - self.assertEqual(returned, (display_util.OK, user_input)) + assert returned == (display_util.OK, user_input) def test_directory_select_noninteractive(self): default = "/var/www/html" code, input_ = self._force_noninteractive( self.displayer.directory_select, "msg", default=default) - self.assertEqual(code, display_util.OK) - self.assertEqual(input_, default) + assert code == display_util.OK + assert input_ == default def _force_noninteractive(self, func, *args, **kwargs): skipped_interaction = self.displayer.skipped_interaction @@ -219,9 +220,9 @@ class FileOutputDisplayTest(unittest.TestCase): result = func(*args, **kwargs) if skipped_interaction: - self.assertIs(mock_logger.warning.called, False) + assert mock_logger.warning.called is False else: - self.assertEqual(mock_logger.warning.call_count, 1) + assert mock_logger.warning.call_count == 1 return result @@ -235,8 +236,7 @@ class FileOutputDisplayTest(unittest.TestCase): ["2", "o"] ] for list_ in indices: - self.assertEqual( - self.displayer._scrub_checklist_input(list_, TAGS), []) + assert self.displayer._scrub_checklist_input(list_, TAGS) == [] def test_print_menu(self): # pylint: disable=protected-access @@ -248,13 +248,11 @@ class FileOutputDisplayTest(unittest.TestCase): # pylint: disable=protected-access input_with_timeout = "certbot._internal.display.util.input_with_timeout" with mock.patch(input_with_timeout, return_value="1"): - self.assertEqual( - self.displayer._get_valid_int_ans(1), (display_util.OK, 1)) + assert self.displayer._get_valid_int_ans(1) == (display_util.OK, 1) ans = "2" with mock.patch(input_with_timeout, return_value=ans): - self.assertEqual( - self.displayer._get_valid_int_ans(3), - (display_util.OK, int(ans))) + assert self.displayer._get_valid_int_ans(3) == \ + (display_util.OK, int(ans)) def test_get_valid_int_ans_invalid(self): # pylint: disable=protected-access @@ -266,9 +264,8 @@ class FileOutputDisplayTest(unittest.TestCase): input_with_timeout = "certbot._internal.display.util.input_with_timeout" for ans in answers: with mock.patch(input_with_timeout, side_effect=ans): - self.assertEqual( - self.displayer._get_valid_int_ans(3), - (display_util.CANCEL, -1)) + assert self.displayer._get_valid_int_ans(3) == \ + (display_util.CANCEL, -1) class NoninteractiveDisplayTest(unittest.TestCase): @@ -282,52 +279,56 @@ class NoninteractiveDisplayTest(unittest.TestCase): self.displayer.notification("message", 10) string = self.mock_stdout.write.call_args[0][0] - self.assertIn("message", string) + assert "message" in string mock_logger.debug.assert_called_with("Notifying user: %s", "message") def test_notification_decoration(self): from certbot.compat import os self.displayer.notification("message", pause=False, decorate=False) string = self.mock_stdout.write.call_args[0][0] - self.assertEqual(string, "message" + os.linesep) + assert string == "message" + os.linesep self.displayer.notification("message2", pause=False) string = self.mock_stdout.write.call_args[0][0] - self.assertIn("- - - ", string) - self.assertIn("message2" + os.linesep, string) + assert "- - - " in string + assert "message2" + os.linesep in string def test_input(self): d = "an incomputable value" ret = self.displayer.input("message", default=d) - self.assertEqual(ret, (display_util.OK, d)) - self.assertRaises(errors.MissingCommandlineFlag, self.displayer.input, "message") + assert ret == (display_util.OK, d) + with pytest.raises(errors.MissingCommandlineFlag): + self.displayer.input("message") def test_menu(self): ret = self.displayer.menu("message", CHOICES, default=1) - self.assertEqual(ret, (display_util.OK, 1)) - self.assertRaises(errors.MissingCommandlineFlag, self.displayer.menu, "message", CHOICES) + assert ret == (display_util.OK, 1) + with pytest.raises(errors.MissingCommandlineFlag): + self.displayer.menu("message", CHOICES) def test_yesno(self): d = False ret = self.displayer.yesno("message", default=d) - self.assertEqual(ret, d) - self.assertRaises(errors.MissingCommandlineFlag, self.displayer.yesno, "message") + assert ret == d + with pytest.raises(errors.MissingCommandlineFlag): + self.displayer.yesno("message") def test_checklist(self): d = [1, 3] ret = self.displayer.checklist("message", TAGS, default=d) - self.assertEqual(ret, (display_util.OK, d)) - self.assertRaises(errors.MissingCommandlineFlag, self.displayer.checklist, "message", TAGS) + assert ret == (display_util.OK, d) + with pytest.raises(errors.MissingCommandlineFlag): + self.displayer.checklist("message", TAGS) def test_directory_select(self): default = "/var/www/html" expected = (display_util.OK, default) actual = self.displayer.directory_select("msg", default) - self.assertEqual(expected, actual) + assert expected == actual - self.assertRaises( - errors.MissingCommandlineFlag, self.displayer.directory_select, "msg") + with pytest.raises(errors.MissingCommandlineFlag): + self.displayer.directory_select("msg") if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/display/ops_test.py b/certbot/tests/display/ops_test.py index 1235190a7..3363bfe2b 100644 --- a/certbot/tests/display/ops_test.py +++ b/certbot/tests/display/ops_test.py @@ -5,18 +5,18 @@ import unittest from unittest import mock import josepy as jose +import pytest from acme import messages from certbot import errors -from certbot._internal.display import obj as display_obj from certbot._internal import account +from certbot._internal.display import obj as display_obj from certbot.compat import filesystem from certbot.compat import os from certbot.display import ops from certbot.display import util as display_util import certbot.tests.util as test_util - KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) @@ -32,8 +32,10 @@ class GetEmailTest(unittest.TestCase): def test_cancel_none(self, mock_get_utility): mock_input = mock_get_utility().input mock_input.return_value = (display_util.CANCEL, "foo@bar.baz") - self.assertRaises(errors.Error, self._call) - self.assertRaises(errors.Error, self._call, optional=False) + with pytest.raises(errors.Error): + self._call() + with pytest.raises(errors.Error): + self._call(optional=False) @test_util.patch_display_util() def test_ok_safe(self, mock_get_utility): @@ -41,7 +43,7 @@ class GetEmailTest(unittest.TestCase): mock_input.return_value = (display_util.OK, "foo@bar.baz") with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email: mock_safe_email.return_value = True - self.assertEqual(self._call(), "foo@bar.baz") + assert self._call() == "foo@bar.baz" @test_util.patch_display_util() def test_ok_not_safe(self, mock_get_utility): @@ -49,7 +51,7 @@ class GetEmailTest(unittest.TestCase): mock_input.return_value = (display_util.OK, "foo@bar.baz") with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email: mock_safe_email.side_effect = [False, True] - self.assertEqual(self._call(), "foo@bar.baz") + assert self._call() == "foo@bar.baz" @test_util.patch_display_util() def test_invalid_flag(self, mock_get_utility): @@ -59,9 +61,9 @@ class GetEmailTest(unittest.TestCase): with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email: mock_safe_email.return_value = True self._call() - self.assertNotIn(invalid_txt, mock_input.call_args[0][0]) + assert invalid_txt not in mock_input.call_args[0][0] self._call(invalid=True) - self.assertIn(invalid_txt, mock_input.call_args[0][0]) + assert invalid_txt in mock_input.call_args[0][0] @test_util.patch_display_util() def test_optional_flag(self, mock_get_utility): @@ -71,7 +73,7 @@ class GetEmailTest(unittest.TestCase): mock_safe_email.side_effect = [False, True] self._call(optional=False) for call in mock_input.call_args_list: - self.assertNotIn("--register-unsafely-without-email", call[0][0]) + assert "--register-unsafely-without-email" not in call[0][0] @test_util.patch_display_util() def test_optional_invalid_unsafe(self, mock_get_utility): @@ -81,7 +83,7 @@ class GetEmailTest(unittest.TestCase): with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email: mock_safe_email.side_effect = [False, True] self._call(invalid=True) - self.assertIn(invalid_txt, mock_input.call_args[0][0]) + assert invalid_txt in mock_input.call_args[0][0] class ChooseAccountTest(test_util.TempDirTestCase): @@ -114,17 +116,17 @@ class ChooseAccountTest(test_util.TempDirTestCase): @test_util.patch_display_util() def test_one(self, mock_util): mock_util().menu.return_value = (display_util.OK, 0) - self.assertEqual(self._call([self.acc1]), self.acc1) + assert self._call([self.acc1]) == self.acc1 @test_util.patch_display_util() def test_two(self, mock_util): mock_util().menu.return_value = (display_util.OK, 1) - self.assertEqual(self._call([self.acc1, self.acc2]), self.acc2) + assert self._call([self.acc1, self.acc2]) == self.acc2 @test_util.patch_display_util() def test_cancel(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, 1) - self.assertIsNone(self._call([self.acc1, self.acc2])) + assert self._call([self.acc1, self.acc2]) is None class GenHttpsNamesTest(unittest.TestCase): @@ -138,7 +140,7 @@ class GenHttpsNamesTest(unittest.TestCase): return _gen_https_names(domains) def test_zero(self): - self.assertEqual(self._call([]), "") + assert self._call([]) == "" def test_one(self): doms = [ @@ -146,7 +148,7 @@ class GenHttpsNamesTest(unittest.TestCase): "asllkjsadfljasdf.c", ] for dom in doms: - self.assertEqual(self._call([dom]), "https://%s" % dom) + assert self._call([dom]) == "https://%s" % dom def test_two(self): domains_list = [ @@ -154,24 +156,22 @@ class GenHttpsNamesTest(unittest.TestCase): ["paypal.google.facebook.live.com", "*.zombo.example.com"], ] for doms in domains_list: - self.assertEqual( - self._call(doms), - "https://{dom[0]} and https://{dom[1]}".format(dom=doms)) + assert self._call(doms) == \ + "https://{dom[0]} and https://{dom[1]}".format(dom=doms) def test_three(self): doms = ["a.org", "b.org", "c.org"] # We use an oxford comma - self.assertEqual( - self._call(doms), + assert self._call(doms) == \ "https://{dom[0]}, https://{dom[1]}, and https://{dom[2]}".format( - dom=doms)) + dom=doms) def test_four(self): doms = ["a.org", "b.org", "c.org", "d.org"] exp = ("https://{dom[0]}, https://{dom[1]}, https://{dom[2]}, " "and https://{dom[3]}".format(dom=doms)) - self.assertEqual(self._call(doms), exp) + assert self._call(doms) == exp class ChooseNamesTest(unittest.TestCase): @@ -188,12 +188,12 @@ class ChooseNamesTest(unittest.TestCase): @mock.patch("certbot.display.ops._choose_names_manually") def test_no_installer(self, mock_manual): self._call(None) - self.assertEqual(mock_manual.call_count, 1) + assert mock_manual.call_count == 1 @test_util.patch_display_util() def test_no_installer_cancel(self, mock_util): mock_util().input.return_value = (display_util.CANCEL, []) - self.assertEqual(self._call(None), []) + assert self._call(None) == [] @test_util.patch_display_util() def test_no_names_choose(self, mock_util): @@ -202,18 +202,18 @@ class ChooseNamesTest(unittest.TestCase): mock_util().input.return_value = (display_util.OK, domain) actual_doms = self._call(self.mock_install) - self.assertEqual(mock_util().input.call_count, 1) - self.assertEqual(actual_doms, [domain]) + assert mock_util().input.call_count == 1 + assert actual_doms == [domain] def test_sort_names_trivial(self): from certbot.display.ops import _sort_names #sort an empty list - self.assertEqual(_sort_names([]), []) + assert _sort_names([]) == [] #sort simple domains some_domains = ["ex.com", "zx.com", "ax.com"] - self.assertEqual(_sort_names(some_domains), ["ax.com", "ex.com", "zx.com"]) + assert _sort_names(some_domains) == ["ax.com", "ex.com", "zx.com"] #Sort subdomains of a single domain domain = ".ex.com" @@ -223,7 +223,7 @@ class ChooseNamesTest(unittest.TestCase): sorted_short = sorted(unsorted_short) sorted_long = [us + domain for us in sorted_short] - self.assertEqual(_sort_names(unsorted_long), sorted_long) + assert _sort_names(unsorted_long) == sorted_long def test_sort_names_many(self): from certbot.display.ops import _sort_names @@ -241,7 +241,7 @@ class ChooseNamesTest(unittest.TestCase): for domain in sorted(unsorted_domains): for short in sorted_short: sortd.append(short+domain) - self.assertEqual(_sort_names(to_sort), sortd) + assert _sort_names(to_sort) == sortd @test_util.patch_display_util() @@ -250,24 +250,24 @@ class ChooseNamesTest(unittest.TestCase): mock_util().checklist.return_value = (display_util.OK, ["example.com"]) names = self._call(self.mock_install) - self.assertEqual(names, ["example.com"]) - self.assertEqual(mock_util().checklist.call_count, 1) + assert names == ["example.com"] + assert mock_util().checklist.call_count == 1 @test_util.patch_display_util() def test_filter_namees_override_question(self, mock_util): self.mock_install.get_all_names.return_value = {"example.com"} mock_util().checklist.return_value = (display_util.OK, ["example.com"]) names = self._call(self.mock_install, "Custom") - self.assertEqual(names, ["example.com"]) - self.assertEqual(mock_util().checklist.call_count, 1) - self.assertEqual(mock_util().checklist.call_args[0][0], "Custom") + assert names == ["example.com"] + assert mock_util().checklist.call_count == 1 + assert mock_util().checklist.call_args[0][0] == "Custom" @test_util.patch_display_util() def test_filter_names_nothing_selected(self, mock_util): self.mock_install.get_all_names.return_value = {"example.com"} mock_util().checklist.return_value = (display_util.OK, []) - self.assertEqual(self._call(self.mock_install), []) + assert self._call(self.mock_install) == [] @test_util.patch_display_util() def test_filter_names_cancel(self, mock_util): @@ -275,7 +275,7 @@ class ChooseNamesTest(unittest.TestCase): mock_util().checklist.return_value = ( display_util.CANCEL, ["example.com"]) - self.assertEqual(self._call(self.mock_install), []) + assert self._call(self.mock_install) == [] def test_get_valid_domains(self): from certbot.display.ops import get_valid_domains @@ -284,9 +284,9 @@ class ChooseNamesTest(unittest.TestCase): "justtld", "*.wildcard.com"] all_invalid = ["öóòps.net", "uniçodé.com"] two_valid = ["example.com", "úniçøde.com", "also.example.com"] - self.assertEqual(get_valid_domains(all_valid), all_valid) - self.assertEqual(get_valid_domains(all_invalid), []) - self.assertEqual(len(get_valid_domains(two_valid)), 2) + assert get_valid_domains(all_valid) == all_valid + assert get_valid_domains(all_invalid) == [] + assert len(get_valid_domains(two_valid)) == 2 @test_util.patch_display_util() def test_choose_manually(self, mock_util): @@ -297,23 +297,23 @@ class ChooseNamesTest(unittest.TestCase): # IDN and no retry utility_mock.input.return_value = (display_util.OK, "uniçodé.com") - self.assertEqual(_choose_names_manually(), []) + assert _choose_names_manually() == [] # IDN exception with previous mocks with mock.patch( "certbot.display.ops.internal_display_util.separate_list_input" ) as mock_sli: unicode_error = UnicodeEncodeError('mock', u'', 0, 1, 'mock') mock_sli.side_effect = unicode_error - self.assertEqual(_choose_names_manually(), []) + assert _choose_names_manually() == [] # Valid domains utility_mock.input.return_value = (display_util.OK, ("example.com," "under_score.example.com," "justtld," "valid.example.com")) - self.assertEqual(_choose_names_manually(), + assert _choose_names_manually() == \ ["example.com", "under_score.example.com", - "justtld", "valid.example.com"]) + "justtld", "valid.example.com"] @test_util.patch_display_util() def test_choose_manually_retry(self, mock_util): @@ -324,7 +324,7 @@ class ChooseNamesTest(unittest.TestCase): "uniçodé.com") utility_mock.yesno.side_effect = [True, True, False] _choose_names_manually() - self.assertEqual(utility_mock.yesno.call_count, 3) + assert utility_mock.yesno.call_count == 3 class SuccessInstallationTest(unittest.TestCase): @@ -342,11 +342,11 @@ class SuccessInstallationTest(unittest.TestCase): self._call(names) - self.assertEqual(mock_notify.call_count, 1) + assert mock_notify.call_count == 1 arg = mock_notify.call_args_list[0][0][0] for name in names: - self.assertIn(name, arg) + assert name in arg class SuccessRenewalTest(unittest.TestCase): @@ -364,7 +364,7 @@ class SuccessRenewalTest(unittest.TestCase): self._call(names) - self.assertEqual(mock_notify.call_count, 1) + assert mock_notify.call_count == 1 class SuccessRevocationTest(unittest.TestCase): @@ -406,30 +406,29 @@ class ValidatorTests(unittest.TestCase): (display_util.OK, self.valid_input)] returned = ops.validated_input(self.__validator, "message", force_interactive=True) - self.assertEqual(ValidatorTests.__ERROR, mock_util().notification.call_args[0][0]) - self.assertEqual(returned, (display_util.OK, self.valid_input)) + assert ValidatorTests.__ERROR == mock_util().notification.call_args[0][0] + assert returned == (display_util.OK, self.valid_input) @test_util.patch_display_util() def test_input_validation_with_default(self, mock_util): mock_util().input.side_effect = [(display_util.OK, self.valid_input)] returned = ops.validated_input(self.__validator, "msg", default="other") - self.assertEqual(returned, (display_util.OK, self.valid_input)) + assert returned == (display_util.OK, self.valid_input) @test_util.patch_display_util() def test_input_validation_with_bad_default(self, mock_util): mock_util().input.side_effect = [(display_util.OK, self.valid_input)] - self.assertRaises(AssertionError, - ops.validated_input, - self.__validator, "msg", default="") + with pytest.raises(AssertionError): + ops.validated_input(self.__validator, "msg", default="") @test_util.patch_display_util() def test_input_cancel_with_validator(self, mock_util): mock_util().input.side_effect = [(display_util.CANCEL, "")] code, unused_raw = ops.validated_input(self.__validator, "message", force_interactive=True) - self.assertEqual(code, display_util.CANCEL) + assert code == display_util.CANCEL @test_util.patch_display_util() def test_directory_select_validation(self, mock_util): @@ -437,23 +436,22 @@ class ValidatorTests(unittest.TestCase): (display_util.OK, self.valid_directory)] returned = ops.validated_directory(self.__validator, "msg", force_interactive=True) - self.assertEqual(ValidatorTests.__ERROR, mock_util().notification.call_args[0][0]) - self.assertEqual(returned, (display_util.OK, self.valid_directory)) + assert ValidatorTests.__ERROR == mock_util().notification.call_args[0][0] + assert returned == (display_util.OK, self.valid_directory) @test_util.patch_display_util() def test_directory_select_validation_with_default(self, mock_util): mock_util().directory_select.side_effect = [(display_util.OK, self.valid_directory)] returned = ops.validated_directory(self.__validator, "msg", default="other") - self.assertEqual(returned, (display_util.OK, self.valid_directory)) + assert returned == (display_util.OK, self.valid_directory) @test_util.patch_display_util() def test_directory_select_validation_with_bad_default(self, mock_util): mock_util().directory_select.side_effect = [(display_util.OK, self.valid_directory)] - self.assertRaises(AssertionError, - ops.validated_directory, - self.__validator, "msg", default="") + with pytest.raises(AssertionError): + ops.validated_directory(self.__validator, "msg", default="") class ChooseValuesTest(unittest.TestCase): @@ -468,9 +466,9 @@ class ChooseValuesTest(unittest.TestCase): items = ["first", "second", "third"] mock_util().checklist.return_value = (display_util.OK, [items[2]]) result = self._call(items, None) - self.assertEqual(result, [items[2]]) - self.assertIs(mock_util().checklist.called, True) - self.assertEqual(mock_util().checklist.call_args[0][0], "") + assert result == [items[2]] + assert mock_util().checklist.called is True + assert mock_util().checklist.call_args[0][0] == "" @test_util.patch_display_util() def test_choose_names_success_question(self, mock_util): @@ -478,9 +476,9 @@ class ChooseValuesTest(unittest.TestCase): question = "Which one?" mock_util().checklist.return_value = (display_util.OK, [items[1]]) result = self._call(items, question) - self.assertEqual(result, [items[1]]) - self.assertIs(mock_util().checklist.called, True) - self.assertEqual(mock_util().checklist.call_args[0][0], question) + assert result == [items[1]] + assert mock_util().checklist.called is True + assert mock_util().checklist.call_args[0][0] == question @test_util.patch_display_util() def test_choose_names_user_cancel(self, mock_util): @@ -488,9 +486,9 @@ class ChooseValuesTest(unittest.TestCase): question = "Want to cancel?" mock_util().checklist.return_value = (display_util.CANCEL, []) result = self._call(items, question) - self.assertEqual(result, []) - self.assertIs(mock_util().checklist.called, True) - self.assertEqual(mock_util().checklist.call_args[0][0], question) + assert result == [] + assert mock_util().checklist.called is True + assert mock_util().checklist.call_args[0][0] == question @mock.patch('certbot.display.ops.logger') @@ -504,18 +502,18 @@ class ReportExecutedCommand(unittest.TestCase): def test_mixed_success(self, mock_notify, mock_logger): self._call("some-hook", 0, "Did a thing", "Some warning") - self.assertEqual(mock_logger.warning.call_count, 1) - self.assertEqual(mock_notify.call_count, 1) + assert mock_logger.warning.call_count == 1 + assert mock_notify.call_count == 1 def test_mixed_error(self, mock_notify, mock_logger): self._call("some-hook", -127, "Did a thing", "Some warning") - self.assertEqual(mock_logger.warning.call_count, 2) - self.assertEqual(mock_notify.call_count, 1) + assert mock_logger.warning.call_count == 2 + assert mock_notify.call_count == 1 def test_empty_success(self, mock_notify, mock_logger): self._call("some-hook", 0, "\n", " ") - self.assertEqual(mock_logger.warning.call_count, 0) - self.assertEqual(mock_notify.call_count, 0) + assert mock_logger.warning.call_count == 0 + assert mock_notify.call_count == 0 if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/display/util_test.py b/certbot/tests/display/util_test.py index 7eb45653c..a055554a1 100644 --- a/certbot/tests/display/util_test.py +++ b/certbot/tests/display/util_test.py @@ -1,98 +1,79 @@ """Test :mod:`certbot.display.util`.""" import io import socket +import sys import tempfile -import unittest from unittest import mock +import pytest + from certbot import errors import certbot.tests.util as test_util -class NotifyTest(unittest.TestCase): - """Tests for certbot.display.util.notify""" - - @test_util.patch_display_util() - def test_notify(self, mock_util): - from certbot.display.util import notify - notify("Hello World") - mock_util().notification.assert_called_with( - "Hello World", pause=False, decorate=False, wrap=False - ) +@test_util.patch_display_util() +def test_notify(mock_util): + from certbot.display.util import notify + notify("Hello World") + mock_util().notification.assert_called_with( + "Hello World", pause=False, decorate=False, wrap=False + ) -class NotificationTest(unittest.TestCase): - """Tests for certbot.display.util.notification""" - - @test_util.patch_display_util() - def test_notification(self, mock_util): - from certbot.display.util import notification - notification("Hello World") - mock_util().notification.assert_called_with( - "Hello World", pause=True, decorate=True, wrap=True, force_interactive=False - ) +@test_util.patch_display_util() +def test_notification(mock_util): + from certbot.display.util import notification + notification("Hello World") + mock_util().notification.assert_called_with( + "Hello World", pause=True, decorate=True, wrap=True, force_interactive=False + ) -class MenuTest(unittest.TestCase): - """Tests for certbot.display.util.menu""" - - @test_util.patch_display_util() - def test_menu(self, mock_util): - from certbot.display.util import menu - menu("Hello World", ["one", "two"], default=0) - mock_util().menu.assert_called_with( - "Hello World", ["one", "two"], default=0, cli_flag=None, force_interactive=False - ) +@test_util.patch_display_util() +def test_menu(mock_util): + from certbot.display.util import menu + menu("Hello World", ["one", "two"], default=0) + mock_util().menu.assert_called_with( + "Hello World", ["one", "two"], default=0, cli_flag=None, force_interactive=False + ) -class InputTextTest(unittest.TestCase): - """Tests for certbot.display.util.input_text""" - - @test_util.patch_display_util() - def test_input_text(self, mock_util): - from certbot.display.util import input_text - input_text("Hello World", default="something") - mock_util().input.assert_called_with( - "Hello World", default='something', cli_flag=None, force_interactive=False - ) +@test_util.patch_display_util() +def test_input_text(mock_util): + from certbot.display.util import input_text + input_text("Hello World", default="something") + mock_util().input.assert_called_with( + "Hello World", default='something', cli_flag=None, force_interactive=False + ) -class YesNoTest(unittest.TestCase): - """Tests for certbot.display.util.yesno""" - - @test_util.patch_display_util() - def test_yesno(self, mock_util): - from certbot.display.util import yesno - yesno("Hello World", default=True) - mock_util().yesno.assert_called_with( - "Hello World", yes_label='Yes', no_label='No', default=True, cli_flag=None, - force_interactive=False - ) +@test_util.patch_display_util() +def test_yesno(mock_util): + from certbot.display.util import yesno + yesno("Hello World", default=True) + mock_util().yesno.assert_called_with( + "Hello World", yes_label='Yes', no_label='No', default=True, cli_flag=None, + force_interactive=False + ) -class ChecklistTest(unittest.TestCase): - """Tests for certbot.display.util.checklist""" - - @test_util.patch_display_util() - def test_checklist(self, mock_util): - from certbot.display.util import checklist - checklist("Hello World", ["one", "two"], default="one") - mock_util().checklist.assert_called_with( - "Hello World", ['one', 'two'], default='one', cli_flag=None, force_interactive=False - ) +@test_util.patch_display_util() +def test_checklist(mock_util): + from certbot.display.util import checklist + checklist("Hello World", ["one", "two"], default="one") + mock_util().checklist.assert_called_with( + "Hello World", ['one', 'two'], default='one', cli_flag=None, force_interactive=False + ) -class DirectorySelectTest(unittest.TestCase): - """Tests for certbot.display.util.directory_select""" - - @test_util.patch_display_util() - def test_directory_select(self, mock_util): - from certbot.display.util import directory_select - directory_select("Hello World", default="something") - mock_util().directory_select.assert_called_with( - "Hello World", default='something', cli_flag=None, force_interactive=False - ) +@test_util.patch_display_util() +def test_directory_select(mock_util): + from certbot.display.util import directory_select + directory_select("Hello World", default="something") + mock_util().directory_select.assert_called_with( + "Hello World", default='something', cli_flag=None, force_interactive=False + ) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/eff_test.py b/certbot/tests/eff_test.py index 6a8ac2c61..33694e17e 100644 --- a/certbot/tests/eff_test.py +++ b/certbot/tests/eff_test.py @@ -1,9 +1,11 @@ """Tests for certbot._internal.eff.""" import datetime +import sys import unittest from unittest import mock import josepy +import pytest import pytz import requests @@ -12,7 +14,6 @@ from certbot._internal import account from certbot._internal import constants import certbot.tests.util as test_util - _KEY = josepy.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) @@ -47,50 +48,50 @@ class PrepareSubscriptionTest(SubscriptionTest): self._call() actual = mock_notify.call_args[0][0] expected_part = "because you didn't provide an e-mail address" - self.assertIn(expected_part, actual) - self.assertIsNone(self.account.meta.register_to_eff) + assert expected_part in actual + assert self.account.meta.register_to_eff is None @test_util.patch_display_util() def test_will_not_subscribe_with_no_prompt(self, mock_get_utility): self.config.eff_email = False self._call() self._assert_no_get_utility_calls(mock_get_utility) - self.assertIsNone(self.account.meta.register_to_eff) + assert self.account.meta.register_to_eff is None @test_util.patch_display_util() def test_will_subscribe_with_no_prompt(self, mock_get_utility): self.config.eff_email = True self._call() self._assert_no_get_utility_calls(mock_get_utility) - self.assertEqual(self.account.meta.register_to_eff, self.config.email) + assert self.account.meta.register_to_eff == self.config.email @test_util.patch_display_util() def test_will_not_subscribe_with_prompt(self, mock_get_utility): mock_get_utility().yesno.return_value = False self._call() - self.assertFalse(mock_get_utility().add_message.called) + assert not mock_get_utility().add_message.called self._assert_correct_yesno_call(mock_get_utility) - self.assertIsNone(self.account.meta.register_to_eff) + assert self.account.meta.register_to_eff is None @test_util.patch_display_util() def test_will_subscribe_with_prompt(self, mock_get_utility): mock_get_utility().yesno.return_value = True self._call() - self.assertFalse(mock_get_utility().add_message.called) + assert not mock_get_utility().add_message.called self._assert_correct_yesno_call(mock_get_utility) - self.assertEqual(self.account.meta.register_to_eff, self.config.email) + assert self.account.meta.register_to_eff == self.config.email def _assert_no_get_utility_calls(self, mock_get_utility): - self.assertFalse(mock_get_utility().yesno.called) - self.assertFalse(mock_get_utility().add_message.called) + assert not mock_get_utility().yesno.called + assert not mock_get_utility().add_message.called def _assert_correct_yesno_call(self, mock_get_utility): - self.assertTrue(mock_get_utility().yesno.called) + assert mock_get_utility().yesno.called call_args, call_kwargs = mock_get_utility().yesno.call_args actual = call_args[0] expected_part = 'Electronic Frontier Foundation' - self.assertIn(expected_part, actual) - self.assertFalse(call_kwargs.get('default', True)) + assert expected_part in actual + assert not call_kwargs.get('default', True) class HandleSubscriptionTest(SubscriptionTest): @@ -102,14 +103,14 @@ class HandleSubscriptionTest(SubscriptionTest): @mock.patch('certbot._internal.eff.subscribe') def test_no_subscribe(self, mock_subscribe): self._call() - self.assertIs(mock_subscribe.called, False) + assert mock_subscribe.called is False @mock.patch('certbot._internal.eff.subscribe') def test_subscribe(self, mock_subscribe): self.account.meta = self.account.meta.update(register_to_eff=self.config.email) self._call() - self.assertTrue(mock_subscribe.called) - self.assertEqual(mock_subscribe.call_args[0][0], self.config.email) + assert mock_subscribe.called + assert mock_subscribe.call_args[0][0] == self.config.email class SubscribeTest(unittest.TestCase): @@ -132,20 +133,20 @@ class SubscribeTest(unittest.TestCase): self._check_post_call(mock_post) def _check_post_call(self, mock_post): - self.assertEqual(mock_post.call_count, 1) + assert mock_post.call_count == 1 call_args, call_kwargs = mock_post.call_args - self.assertEqual(call_args[0], constants.EFF_SUBSCRIBE_URI) + assert call_args[0] == constants.EFF_SUBSCRIBE_URI data = call_kwargs.get('data') - self.assertIsNotNone(data) - self.assertEqual(data.get('email'), self.email) + assert data is not None + assert data.get('email') == self.email def test_bad_status(self): self.json['status'] = False self._call() actual = self._get_reported_message() expected_part = 'because your e-mail address appears to be invalid.' - self.assertIn(expected_part, actual) + assert expected_part in actual def test_not_ok(self): self.response.ok = False @@ -153,31 +154,31 @@ class SubscribeTest(unittest.TestCase): self._call() actual = self._get_reported_message() unexpected_part = 'because' - self.assertNotIn(unexpected_part, actual) + assert unexpected_part not in actual def test_response_not_json(self): self.response.json.side_effect = ValueError() self._call() actual = self._get_reported_message() expected_part = 'problem' - self.assertIn(expected_part, actual) + assert expected_part in actual def test_response_json_missing_status_element(self): self.json.clear() self._call() actual = self._get_reported_message() expected_part = 'problem' - self.assertIn(expected_part, actual) + assert expected_part in actual def _get_reported_message(self): - self.assertTrue(self.mock_notify.called) + assert self.mock_notify.called return self.mock_notify.call_args[0][0] @test_util.patch_display_util() def test_subscribe(self, mock_get_utility): self._call() - self.assertIs(mock_get_utility.called, False) + assert mock_get_utility.called is False if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/error_handler_test.py b/certbot/tests/error_handler_test.py index d6d506956..30bd90150 100644 --- a/certbot/tests/error_handler_test.py +++ b/certbot/tests/error_handler_test.py @@ -8,6 +8,8 @@ from typing import Union import unittest from unittest import mock +import pytest + from certbot.compat import os @@ -61,7 +63,7 @@ class ErrorHandlerTest(unittest.TestCase): except ValueError: exception_raised = True - self.assertTrue(exception_raised) + assert exception_raised self.init_func.assert_called_once_with(*self.init_args, **self.init_kwargs) @@ -76,14 +78,14 @@ class ErrorHandlerTest(unittest.TestCase): should_be_42 *= 10 # check execution stopped when the signal was sent - self.assertEqual(42, should_be_42) + assert 42 == should_be_42 # assert signals were caught - self.assertEqual([self.signals[0]], signals_received) + assert [self.signals[0]] == signals_received # assert the error handling function was just called once self.init_func.assert_called_once_with(*self.init_args, **self.init_kwargs) for signum in self.signals: - self.assertEqual(init_signals[signum], signal.getsignal(signum)) + assert init_signals[signum] == signal.getsignal(signum) def test_bad_recovery(self): bad_func = mock.MagicMock(side_effect=[ValueError]) @@ -107,7 +109,7 @@ class ErrorHandlerTest(unittest.TestCase): with signal_receiver(self.signals) as signals_received: with self.handler: send_signal(sig2) - self.assertEqual([sig2, sig1], signals_received) + assert [sig2, sig1] == signals_received self.init_func.assert_called_once_with(*self.init_args, **self.init_kwargs) bad_func.assert_called_once_with() @@ -118,7 +120,7 @@ class ErrorHandlerTest(unittest.TestCase): sys.exit(0) except SystemExit: pass - self.assertIs(self.init_func.called, False) + assert self.init_func.called is False def test_regular_exit(self): func = mock.MagicMock() @@ -150,4 +152,4 @@ class ExitHandlerTest(ErrorHandlerTest): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/errors_test.py b/certbot/tests/errors_test.py index d05f2b43e..9432c7433 100644 --- a/certbot/tests/errors_test.py +++ b/certbot/tests/errors_test.py @@ -1,7 +1,10 @@ """Tests for certbot.errors.""" +import sys import unittest from unittest import mock +import pytest + from acme import messages from certbot import achallenges from certbot.tests import acme_util @@ -18,9 +21,9 @@ class FailedChallengesTest(unittest.TestCase): error=messages.Error.with_code("tls", detail="detail")))}) def test_str(self): - self.assertTrue(str(self.error).startswith( + assert str(self.error).startswith( "Failed authorization procedure. example.com (dns-01): " - "urn:ietf:params:acme:error:tls")) + "urn:ietf:params:acme:error:tls") def test_unicode(self): from certbot.errors import FailedChallenges @@ -30,9 +33,9 @@ class FailedChallengesTest(unittest.TestCase): chall=acme_util.DNS01, uri=None, error=messages.Error.with_code("tls", detail=arabic_detail)))}) - self.assertTrue(str(arabic_error).startswith( + assert str(arabic_error).startswith( "Failed authorization procedure. example.com (dns-01): " - "urn:ietf:params:acme:error:tls")) + "urn:ietf:params:acme:error:tls") class StandaloneBindErrorTest(unittest.TestCase): @@ -43,13 +46,13 @@ class StandaloneBindErrorTest(unittest.TestCase): self.error = StandaloneBindError(mock.sentinel.error, 1234) def test_instance_args(self): - self.assertEqual(mock.sentinel.error, self.error.socket_error) - self.assertEqual(1234, self.error.port) + assert mock.sentinel.error == self.error.socket_error + assert 1234 == self.error.port def test_str(self): - self.assertTrue(str(self.error).startswith( - "Problem binding to port 1234: ")) + assert str(self.error).startswith( + "Problem binding to port 1234: ") if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/helpful_test.py b/certbot/tests/helpful_test.py index c67211a43..0c928d961 100644 --- a/certbot/tests/helpful_test.py +++ b/certbot/tests/helpful_test.py @@ -1,82 +1,84 @@ """Tests for certbot.helpful_parser""" -import unittest +import sys from unittest import mock +import pytest + from certbot import errors -from certbot._internal.cli import HelpfulArgumentParser -from certbot._internal.cli import _DomainsAction from certbot._internal import constants +from certbot._internal.cli import _DomainsAction +from certbot._internal.cli import HelpfulArgumentParser -class TestScanningFlags(unittest.TestCase): +class TestScanningFlags: '''Test the prescan_for_flag method of HelpfulArgumentParser''' def test_prescan_no_help_flag(self): arg_parser = HelpfulArgumentParser(['run'], {}) detected_flag = arg_parser.prescan_for_flag('--help', ['all', 'certonly']) - self.assertIs(detected_flag, False) + assert detected_flag is False detected_flag = arg_parser.prescan_for_flag('-h', ['all, certonly']) - self.assertIs(detected_flag, False) + assert detected_flag is False def test_prescan_unvalid_topic(self): arg_parser = HelpfulArgumentParser(['--help', 'all'], {}) detected_flag = arg_parser.prescan_for_flag('--help', ['potato']) - self.assertIs(detected_flag, True) + assert detected_flag is True detected_flag = arg_parser.prescan_for_flag('-h', arg_parser.help_topics) - self.assertIs(detected_flag, False) + assert detected_flag is False def test_prescan_valid_topic(self): arg_parser = HelpfulArgumentParser(['-h', 'all'], {}) detected_flag = arg_parser.prescan_for_flag('-h', arg_parser.help_topics) - self.assertEqual(detected_flag, 'all') + assert detected_flag == 'all' detected_flag = arg_parser.prescan_for_flag('--help', arg_parser.help_topics) - self.assertIs(detected_flag, False) + assert detected_flag is False -class TestDetermineVerbs(unittest.TestCase): +class TestDetermineVerbs: '''Tests for determine_verb methods of HelpfulArgumentParser''' def test_determine_verb_wrong_verb(self): arg_parser = HelpfulArgumentParser(['potato'], {}) - self.assertEqual(arg_parser.verb, "run") - self.assertEqual(arg_parser.args, ["potato"]) + assert arg_parser.verb == "run" + assert arg_parser.args == ["potato"] def test_determine_verb_help(self): arg_parser = HelpfulArgumentParser(['--help', 'everything'], {}) - self.assertEqual(arg_parser.verb, "help") - self.assertEqual(arg_parser.args, ["--help", "everything"]) + assert arg_parser.verb == "help" + assert arg_parser.args == ["--help", "everything"] arg_parser = HelpfulArgumentParser(['-d', 'some_domain', '--help', 'all'], {}) - self.assertEqual(arg_parser.verb, "help") - self.assertEqual(arg_parser.args, ['-d', 'some_domain', '--help', - 'all']) + assert arg_parser.verb == "help" + assert arg_parser.args == ['-d', 'some_domain', '--help', + 'all'] def test_determine_verb(self): arg_parser = HelpfulArgumentParser(['certonly'], {}) - self.assertEqual(arg_parser.verb, 'certonly') - self.assertEqual(arg_parser.args, []) + assert arg_parser.verb == 'certonly' + assert arg_parser.args == [] arg_parser = HelpfulArgumentParser(['auth'], {}) - self.assertEqual(arg_parser.verb, 'certonly') - self.assertEqual(arg_parser.args, []) + assert arg_parser.verb == 'certonly' + assert arg_parser.args == [] arg_parser = HelpfulArgumentParser(['everything'], {}) - self.assertEqual(arg_parser.verb, 'run') - self.assertEqual(arg_parser.args, []) + assert arg_parser.verb == 'run' + assert arg_parser.args == [] -class TestAdd(unittest.TestCase): +class TestAdd: '''Tests for add method in HelpfulArgumentParser''' def test_add_trivial_argument(self): arg_parser = HelpfulArgumentParser(['run'], {}) arg_parser.add(None, "--hello-world") parsed_args = arg_parser.parser.parse_args(['--hello-world', 'Hello World!']) - self.assertIs(parsed_args.hello_world, 'Hello World!') - self.assertFalse(hasattr(parsed_args, 'potato')) + assert parsed_args.hello_world is 'Hello World!' + assert not hasattr(parsed_args, 'potato') def test_add_expected_argument(self): arg_parser = HelpfulArgumentParser(['--help', 'run'], {}) @@ -86,15 +88,16 @@ class TestAdd(unittest.TestCase): metavar="EAB_KID", help="Key Identifier for External Account Binding") parsed_args = arg_parser.parser.parse_args(["--eab-kid", None]) - self.assertIsNone(parsed_args.eab_kid) - self.assertTrue(hasattr(parsed_args, 'eab_kid')) + assert parsed_args.eab_kid is None + assert hasattr(parsed_args, 'eab_kid') -class TestAddGroup(unittest.TestCase): +class TestAddGroup: '''Test add_group method of HelpfulArgumentParser''' def test_add_group_no_input(self): arg_parser = HelpfulArgumentParser(['run'], {}) - self.assertRaises(TypeError, arg_parser.add_group) + with pytest.raises(TypeError): + arg_parser.add_group() def test_add_group_topic_not_visible(self): # The user request help on run. A topic that given somewhere in the @@ -102,19 +105,19 @@ class TestAddGroup(unittest.TestCase): arg_parser = HelpfulArgumentParser(['--help', 'run'], {}) arg_parser.add_group("auth", description="description of auth") - self.assertEqual(arg_parser.groups, {}) + assert arg_parser.groups == {} def test_add_group_topic_requested_help(self): arg_parser = HelpfulArgumentParser(['--help', 'run'], {}) arg_parser.add_group("run", description="description of run") - self.assertTrue(arg_parser.groups["run"]) + assert arg_parser.groups["run"] arg_parser.add_group("certonly", description="description of certonly") - with self.assertRaises(KeyError): - self.assertIs(arg_parser.groups["certonly"], False) + with pytest.raises(KeyError): + assert arg_parser.groups["certonly"] is False -class TestParseArgsErrors(unittest.TestCase): +class TestParseArgsErrors: '''Tests for errors that should be met for some cases in parse_args method in HelpfulArgumentParser''' def test_parse_args_renew_force_interactive(self): @@ -123,7 +126,7 @@ class TestParseArgsErrors(unittest.TestCase): arg_parser.add( None, constants.FORCE_INTERACTIVE_FLAG, action="store_true") - with self.assertRaises(errors.Error): + with pytest.raises(errors.Error): arg_parser.parse_args() def test_parse_args_non_interactive_and_force_interactive(self): @@ -136,7 +139,7 @@ class TestParseArgsErrors(unittest.TestCase): action="store_true" ) - with self.assertRaises(errors.Error): + with pytest.raises(errors.Error): arg_parser.parse_args() def test_parse_args_subset_names_wildcard_domain(self): @@ -186,11 +189,11 @@ class TestParseArgsErrors(unittest.TestCase): arg_parser.add(None, "--must-staple") arg_parser.add(None, "--validate-hooks") arg_parser.add(None, "--allow-subset-of-names") - with self.assertRaises(errors.Error): + with pytest.raises(errors.Error): arg_parser.parse_args() -class TestAddDeprecatedArgument(unittest.TestCase): +class TestAddDeprecatedArgument: """Tests for add_deprecated_argument method of HelpfulArgumentParser""" @mock.patch.object(HelpfulArgumentParser, "modify_kwargs_for_default_detection") @@ -202,4 +205,4 @@ class TestAddDeprecatedArgument(unittest.TestCase): if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index 8cd8e6631..de67d96ef 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -1,7 +1,10 @@ """Tests for certbot._internal.hooks.""" +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot import util from certbot.compat import filesystem @@ -23,9 +26,9 @@ class ValidateHooksTest(unittest.TestCase): self._call(config) types = [call[0][1] for call in mock_validate_hook.call_args_list] - self.assertEqual({"pre", "post", "deploy",}, set(types[:-1])) + assert {"pre", "post", "deploy",} == set(types[:-1]) # This ensures error messages are about deploy hooks when appropriate - self.assertEqual("renew", types[-1]) + assert "renew" == types[-1] class ValidateHookTest(test_util.TempDirTestCase): @@ -43,19 +46,21 @@ class ValidateHookTest(test_util.TempDirTestCase): # to get a fully working test around executable permissions. See # certbot.tests.compat.filesystem::NotExecutableTest for more in-depth tests. with mock.patch("certbot._internal.hooks.filesystem.is_executable", return_value=False): - self.assertRaises(errors.HookCommandNotFound, self._call, 'dummy', "foo") + with pytest.raises(errors.HookCommandNotFound): + self._call('dummy', "foo") @mock.patch("certbot._internal.hooks.util.exe_exists") def test_not_found(self, mock_exe_exists): mock_exe_exists.return_value = False with mock.patch("certbot._internal.hooks.plug_util.path_surgery") as mock_ps: - self.assertRaises(errors.HookCommandNotFound, self._call, "foo", "bar") - self.assertTrue(mock_ps.called) + with pytest.raises(errors.HookCommandNotFound): + self._call("foo", "bar") + assert mock_ps.called @mock.patch("certbot._internal.hooks._prog") def test_unset(self, mock_prog): self._call(None, "foo") - self.assertIs(mock_prog.called, False) + assert mock_prog.called is False class HookTest(test_util.ConfigTestCase): @@ -128,8 +133,8 @@ class PreHookTest(HookTest): with mock.patch("certbot._internal.hooks.logger") as mock_logger: mock_execute = self._call_with_mock_execute(self.config) - self.assertIs(mock_execute.called, False) - self.assertIs(mock_logger.info.called, False) + assert mock_execute.called is False + assert mock_logger.info.called is False def test_renew_disabled_dir_hooks(self): self.config.directory_hooks = False @@ -154,8 +159,8 @@ class PreHookTest(HookTest): def _test_no_executions_common(self): with mock.patch("certbot._internal.hooks.logger") as mock_logger: mock_execute = self._call_with_mock_execute(self.config) - self.assertIs(mock_execute.called, False) - self.assertTrue(mock_logger.info.called) + assert mock_execute.called is False + assert mock_logger.info.called class PostHookTest(HookTest): @@ -191,14 +196,14 @@ class PostHookTest(HookTest): self.config.verb = verb mock_execute = self._call_with_mock_execute(self.config) mock_execute.assert_called_once_with("post-hook", self.config.post_hook, env=mock.ANY) - self.assertFalse(self._get_eventually()) + assert not self._get_eventually() def test_cert_only_and_run_without_hook(self): self.config.post_hook = None for verb in ("certonly", "run",): self.config.verb = verb - self.assertFalse(self._call_with_mock_execute(self.config).called) - self.assertFalse(self._get_eventually()) + assert not self._call_with_mock_execute(self.config).called + assert not self._get_eventually() def test_renew_disabled_dir_hooks(self): self.config.directory_hooks = False @@ -234,7 +239,7 @@ class PostHookTest(HookTest): for _ in range(2): self._call(self.config) - self.assertEqual(self._get_eventually(), expected) + assert self._get_eventually() == expected def _get_eventually(self): from certbot._internal.hooks import post_hooks @@ -266,7 +271,7 @@ class RunSavedPostHooksTest(HookTest): self.eventually: List[str] = [] def test_empty(self): - self.assertFalse(self._call_with_mock_execute_and_eventually().called) + assert not self._call_with_mock_execute_and_eventually().called def test_multiple(self): self.eventually = ["foo", "bar", "baz", "qux"] @@ -274,7 +279,7 @@ class RunSavedPostHooksTest(HookTest): calls = mock_execute.call_args_list for actual_call, expected_arg in zip(calls, self.eventually): - self.assertEqual(actual_call[0][1], expected_arg) + assert actual_call[0][1] == expected_arg def test_single(self): self.eventually = ["foo"] @@ -305,8 +310,8 @@ class RenewalHookTest(HookTest): :rtype: `tuple` of `str` """ - self.assertEqual(os.environ["RENEWED_DOMAINS"], " ".join(domains)) - self.assertEqual(os.environ["RENEWED_LINEAGE"], lineage) + assert os.environ["RENEWED_DOMAINS"] == " ".join(domains) + assert os.environ["RENEWED_LINEAGE"] == lineage return (0, "", "") with mock.patch("certbot.compat.misc.execute_command_status") as mock_execute: @@ -341,16 +346,16 @@ class DeployHookTest(RenewalHookTest): self.config.dry_run = True mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") - self.assertIs(mock_execute.called, False) - self.assertTrue(mock_logger.info.called) + assert mock_execute.called is False + assert mock_logger.info.called @mock.patch("certbot._internal.hooks.logger") def test_no_hook(self, mock_logger): self.config.deploy_hook = None mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") - self.assertIs(mock_execute.called, False) - self.assertIs(mock_logger.info.called, False) + assert mock_execute.called is False + assert mock_logger.info.called is False def test_success(self): domains = ["example.org", "example.net"] @@ -389,8 +394,8 @@ class RenewHookTest(RenewalHookTest): self.config.dry_run = True mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") - self.assertIs(mock_execute.called, False) - self.assertEqual(mock_logger.info.call_count, 2) + assert mock_execute.called is False + assert mock_logger.info.call_count == 2 def test_no_hooks(self): self.config.renew_hook = None @@ -399,8 +404,8 @@ class RenewHookTest(RenewalHookTest): with mock.patch("certbot._internal.hooks.logger") as mock_logger: mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") - self.assertIs(mock_execute.called, False) - self.assertIs(mock_logger.info.called, False) + assert mock_execute.called is False + assert mock_logger.info.called is False def test_overlap(self): self.config.renew_hook = self.dir_hook @@ -424,7 +429,7 @@ class ListHooksTest(test_util.TempDirTestCase): return list_hooks(*args, **kwargs) def test_empty(self): - self.assertFalse(self._call(self.tempdir)) + assert not self._call(self.tempdir) def test_multiple(self): names = sorted( @@ -434,19 +439,19 @@ class ListHooksTest(test_util.TempDirTestCase): for name in names: create_hook(name) - self.assertEqual(self._call(self.tempdir), names) + assert self._call(self.tempdir) == names def test_single(self): name = os.path.join(self.tempdir, "foo") create_hook(name) - self.assertEqual(self._call(self.tempdir), [name]) + assert self._call(self.tempdir) == [name] def test_ignore_tilde(self): name = os.path.join(self.tempdir, "foo~") create_hook(name) - self.assertEqual(self._call(self.tempdir), []) + assert self._call(self.tempdir) == [] def create_hook(file_path): @@ -459,4 +464,4 @@ def create_hook(file_path): if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index 1e7525782..0aec70b21 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -1,9 +1,12 @@ """Tests for certbot._internal.lock.""" import functools import multiprocessing +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import os from certbot.tests import util as test_util @@ -49,8 +52,8 @@ class LockFileTest(test_util.TempDirTestCase): args=(self.lock_path,)) child.start() child.join() - self.assertEqual(child.exitcode, 0) - self.assertTrue(os.path.exists(self.lock_path)) + assert child.exitcode == 0 + assert os.path.exists(self.lock_path) # Test we're still able to properly acquire and release the lock self.test_removed() @@ -65,7 +68,7 @@ class LockFileTest(test_util.TempDirTestCase): try: locked_repr = repr(lock_file) self._test_repr_common(lock_file, locked_repr) - self.assertIn('acquired', locked_repr) + assert 'acquired' in locked_repr finally: lock_file.release() @@ -74,11 +77,11 @@ class LockFileTest(test_util.TempDirTestCase): lock_file.release() released_repr = repr(lock_file) self._test_repr_common(lock_file, released_repr) - self.assertIn('released', released_repr) + assert 'released' in released_repr def _test_repr_common(self, lock_file, lock_repr): - self.assertIn(lock_file.__class__.__name__, lock_repr) - self.assertIn(self.lock_path, lock_repr) + assert lock_file.__class__.__name__ in lock_repr + assert self.lock_path in lock_repr @test_util.skip_on_windows( 'Race conditions on lock are specific to the non-blocking file access approach on Linux.') @@ -98,12 +101,12 @@ class LockFileTest(test_util.TempDirTestCase): with mock.patch('certbot._internal.lock.filesystem.os.stat') as mock_stat: mock_stat.side_effect = delete_and_stat self._call(self.lock_path) - self.assertEqual(len(should_delete), 0) + assert len(should_delete) == 0 def test_removed(self): lock_file = self._call(self.lock_path) lock_file.release() - self.assertFalse(os.path.exists(self.lock_path)) + assert not os.path.exists(self.lock_path) def test_unexpected_lockf_or_locking_err(self): if POSIX_MODE: @@ -116,7 +119,7 @@ class LockFileTest(test_util.TempDirTestCase): try: self._call(self.lock_path) except IOError as err: - self.assertIn(msg, str(err)) + assert msg in str(err) else: # pragma: no cover self.fail('IOError not raised') @@ -132,10 +135,10 @@ class LockFileTest(test_util.TempDirTestCase): try: self._call(self.lock_path) except OSError as err: - self.assertIn(msg, str(err)) + assert msg in str(err) else: # pragma: no cover self.fail('OSError not raised') if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index 855582591..338425505 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -8,6 +8,8 @@ from typing import Optional import unittest from unittest import mock +import pytest + from acme import messages from certbot import errors from certbot import util @@ -17,7 +19,6 @@ from certbot.compat import os from certbot.tests import util as test_util - class PreArgParseSetupTest(unittest.TestCase): """Tests for certbot._internal.log.pre_arg_parse_setup.""" @@ -43,7 +44,7 @@ class PreArgParseSetupTest(unittest.TestCase): mock_root_logger = mock_get() mock_root_logger.setLevel.assert_called_once_with(logging.DEBUG) - self.assertEqual(mock_root_logger.addHandler.call_count, 2) + assert mock_root_logger.addHandler.call_count == 2 memory_handler: Optional[logging.handlers.MemoryHandler] = None for call in mock_root_logger.addHandler.call_args_list: @@ -52,8 +53,8 @@ class PreArgParseSetupTest(unittest.TestCase): memory_handler = handler target = memory_handler.target else: - self.assertIsInstance(handler, logging.StreamHandler) - self.assertIsInstance(target, logging.StreamHandler) + assert isinstance(handler, logging.StreamHandler) + assert isinstance(target, logging.StreamHandler) mock_register.assert_called_once_with(logging.shutdown) mock_sys.excepthook(1, 2, 3) @@ -79,7 +80,8 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): from certbot._internal.log import ColoredStreamHandler self.stream_handler = ColoredStreamHandler(io.StringIO()) - from certbot._internal.log import MemoryHandler, TempHandler + from certbot._internal.log import MemoryHandler + from certbot._internal.log import TempHandler self.temp_handler = TempHandler() self.temp_path = self.temp_handler.path self.memory_handler = MemoryHandler(self.temp_handler) @@ -106,9 +108,9 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): self.root_logger.removeHandler.assert_called_once_with( self.memory_handler) - self.assertTrue(self.root_logger.addHandler.called) - self.assertTrue(os.path.exists(log_path)) - self.assertFalse(os.path.exists(self.temp_path)) + assert self.root_logger.addHandler.called + assert os.path.exists(log_path) + assert not os.path.exists(self.temp_path) mock_sys.excepthook(1, 2, 3) mock_except_hook.assert_called_once_with( 1, 2, 3, debug=self.config.debug, @@ -116,9 +118,9 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): level = self.stream_handler.level if self.config.quiet: - self.assertEqual(level, constants.QUIET_LOGGING_LEVEL) + assert level == constants.QUIET_LOGGING_LEVEL else: - self.assertEqual(level, constants.DEFAULT_LOGGING_LEVEL) + assert level == constants.DEFAULT_LOGGING_LEVEL def test_debug(self): self.config.debug = True @@ -148,7 +150,7 @@ class SetupLogFileHandlerTest(test_util.ConfigTestCase): try: self._call(self.config, 'test.log', '%(message)s') except errors.Error as err: - self.assertIn('--logs-dir', str(err)) + assert '--logs-dir' in str(err) else: # pragma: no cover self.fail('Error not raised.') @@ -164,20 +166,20 @@ class SetupLogFileHandlerTest(test_util.ConfigTestCase): handler, log_path = self._call(self.config, log_file, '%(message)s') handler.close() - self.assertEqual(handler.level, logging.DEBUG) - self.assertEqual(handler.formatter.converter, time.localtime) + assert handler.level == logging.DEBUG + assert handler.formatter.converter == time.localtime expected_path = os.path.join(self.config.logs_dir, log_file) - self.assertEqual(log_path, expected_path) + assert log_path == expected_path backup_path = os.path.join(self.config.logs_dir, log_file + '.1') - self.assertEqual(os.path.exists(backup_path), should_rollover) + assert os.path.exists(backup_path) == should_rollover @mock.patch('certbot._internal.log.logging.handlers.RotatingFileHandler') def test_max_log_backups_used(self, mock_handler): self._call(self.config, 'test.log', '%(message)s') backup_count = mock_handler.call_args[1]['backupCount'] - self.assertEqual(self.config.max_log_backups, backup_count) + assert self.config.max_log_backups == backup_count class ColoredStreamHandlerTest(unittest.TestCase): @@ -199,17 +201,17 @@ class ColoredStreamHandlerTest(unittest.TestCase): def test_format(self): msg = 'I did a thing' self.logger.debug(msg) - self.assertEqual(self.stream.getvalue(), '{0}\n'.format(msg)) + assert self.stream.getvalue() == '{0}\n'.format(msg) def test_format_and_red_level(self): msg = 'I did another thing' self.handler.red_level = logging.DEBUG self.logger.debug(msg) - self.assertEqual(self.stream.getvalue(), + assert self.stream.getvalue() == \ '{0}{1}{2}\n'.format(util.ANSI_SGR_RED, msg, - util.ANSI_SGR_RESET)) + util.ANSI_SGR_RESET) class MemoryHandlerTest(unittest.TestCase): @@ -232,13 +234,13 @@ class MemoryHandlerTest(unittest.TestCase): def test_flush(self): self._test_log_debug() self.handler.flush(force=True) - self.assertEqual(self.stream.getvalue(), self.msg + '\n') + assert self.stream.getvalue() == self.msg + '\n' def test_not_flushed(self): # By default, logging.ERROR messages and higher are flushed self.logger.critical(self.msg) self.handler.flush() - self.assertEqual(self.stream.getvalue(), '') + assert self.stream.getvalue() == '' def test_target_reset(self): self._test_log_debug() @@ -247,8 +249,8 @@ class MemoryHandlerTest(unittest.TestCase): new_stream_handler = logging.StreamHandler(new_stream) self.handler.setTarget(new_stream_handler) self.handler.flush(force=True) - self.assertEqual(self.stream.getvalue(), '') - self.assertEqual(new_stream.getvalue(), self.msg + '\n') + assert self.stream.getvalue() == '' + assert new_stream.getvalue() == self.msg + '\n' new_stream_handler.close() def _test_log_debug(self): @@ -266,16 +268,16 @@ class TempHandlerTest(unittest.TestCase): self.handler.close() def test_permissions(self): - self.assertTrue(filesystem.check_permissions(self.handler.path, 0o600)) + assert filesystem.check_permissions(self.handler.path, 0o600) def test_delete(self): self.handler.close() - self.assertFalse(os.path.exists(self.handler.path)) + assert not os.path.exists(self.handler.path) def test_no_delete(self): self.handler.emit(mock.MagicMock()) self.handler.close() - self.assertTrue(os.path.exists(self.handler.path)) + assert os.path.exists(self.handler.path) os.remove(self.handler.path) @@ -326,7 +328,7 @@ class PostArgParseExceptHookTest(unittest.TestCase): exc_type = ValueError mock_logger, output = self._test_common(exc_type, debug=True, quiet=True) self._assert_exception_logged(mock_logger.error, exc_type) - self.assertNotIn('See the logfile', output) + assert 'See the logfile' not in output def test_custom_error(self): exc_type = errors.PluginError @@ -345,7 +347,7 @@ class PostArgParseExceptHookTest(unittest.TestCase): mock_logger, output = self._test_common(get_acme_error, debug=False) self._assert_exception_logged(mock_logger.debug, messages.Error) self._assert_quiet_output(mock_logger, output) - self.assertNotIn(messages.ERROR_PREFIX, output) + assert messages.ERROR_PREFIX not in output def test_other_error(self): exc_type = ValueError @@ -385,22 +387,22 @@ class PostArgParseExceptHookTest(unittest.TestCase): return mock_logger, output def _assert_exception_logged(self, log_func, exc_type): - self.assertTrue(log_func.called) + assert log_func.called call_kwargs = log_func.call_args[1] - self.assertIn('exc_info', call_kwargs) + assert 'exc_info' in call_kwargs actual_exc_info = call_kwargs['exc_info'] expected_exc_info = (exc_type, mock.ANY, mock.ANY) - self.assertEqual(actual_exc_info, expected_exc_info) + assert actual_exc_info == expected_exc_info def _assert_logfile_output(self, output): - self.assertIn('See the logfile', output) - self.assertIn(self.log_path, output) + assert 'See the logfile' in output + assert self.log_path in output def _assert_quiet_output(self, mock_logger, output): - self.assertIs(mock_logger.exception.called, False) - self.assertTrue(mock_logger.debug.called) - self.assertIn(self.error_msg, output) + assert mock_logger.exception.called is False + assert mock_logger.debug.called + assert self.error_msg in output class ExitWithAdviceTest(test_util.TempDirTestCase): @@ -415,13 +417,13 @@ class ExitWithAdviceTest(test_util.TempDirTestCase): open(log_file, 'w').close() err_str = self._test_common(log_file) - self.assertNotIn('logfiles', err_str) - self.assertIn(log_file, err_str) + assert 'logfiles' not in err_str + assert log_file in err_str def test_log_dir(self): err_str = self._test_common(self.tempdir) - self.assertIn('logfiles', err_str) - self.assertIn(self.tempdir, err_str) + assert 'logfiles' in err_str + assert self.tempdir in err_str # pylint: disable=inconsistent-return-statements def _test_common(self, *args, **kwargs): @@ -433,4 +435,4 @@ class ExitWithAdviceTest(test_util.TempDirTestCase): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index e857f6c33..f8f01e522 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -14,11 +14,14 @@ from typing import List import unittest from unittest import mock +import configobj import josepy as jose +import pytest import pytz from acme.messages import Error as acme_error -from certbot import crypto_util, configuration +from certbot import configuration +from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import util @@ -35,8 +38,6 @@ from certbot.compat import os from certbot.plugins import enhancements import certbot.tests.util as test_util - - CERT_PATH = test_util.vector_path('cert_512.pem') CERT = test_util.vector_path('cert_512.pem') CSR = test_util.vector_path('csr_512.der') @@ -54,8 +55,8 @@ class TestHandleCerts(unittest.TestCase): mock_lineage.ensure_deployed.return_value = False # pylint: disable=protected-access ret = main._handle_identical_cert_request(mock.Mock(), mock_lineage) - self.assertEqual(ret, ("reinstall", mock_lineage)) - self.assertTrue(mock_handle_migration.called) + assert ret == ("reinstall", mock_lineage) + assert mock_handle_migration.called @mock.patch('certbot._internal.renewal.should_renew') @mock.patch("certbot.display.util.menu") @@ -68,9 +69,9 @@ class TestHandleCerts(unittest.TestCase): mock_should_renew.return_value = False ret = main._handle_identical_cert_request(mock.MagicMock(verb="run", reinstall=False), mock_lineage) - self.assertTrue(mock_handle_migration.called) - self.assertFalse(mock_menu.called) - self.assertEqual(ret, ("renew", mock_lineage)) + assert mock_handle_migration.called + assert not mock_menu.called + assert ret == ("renew", mock_lineage) @mock.patch("certbot._internal.main._handle_unexpected_key_type_migration") def test_handle_subset_cert_request(self, mock_handle_migration): @@ -79,8 +80,8 @@ class TestHandleCerts(unittest.TestCase): mock_lineage = mock.Mock() mock_lineage.names.return_value = ["dummy1", "dummy2"] ret = main._handle_subset_cert_request(mock_config, ["dummy1"], mock_lineage) - self.assertEqual(ret, ("renew", mock_lineage)) - self.assertTrue(mock_handle_migration.called) + assert ret == ("renew", mock_lineage) + assert mock_handle_migration.called @mock.patch("certbot._internal.main.display_util.yesno") @mock.patch("certbot._internal.main.cli.set_by_cli") @@ -93,14 +94,14 @@ class TestHandleCerts(unittest.TestCase): cert.private_key_type = "rsa" main._handle_unexpected_key_type_migration(config, cert) mock_yesno.assert_not_called() - self.assertEqual(config.key_type, cert.private_key_type) + assert config.key_type == cert.private_key_type # If the user confirms the change interactively, the key change should proceed silently. cert.private_key_type = "ecdsa" mock_yesno.return_value = True main._handle_unexpected_key_type_migration(config, cert) - self.assertEqual(mock_set.call_count, 2) - self.assertEqual(config.key_type, "rsa") + assert mock_set.call_count == 2 + assert config.key_type == "rsa" # User does not interactively confirm the key type change. mock_yesno.return_value = False @@ -108,24 +109,23 @@ class TestHandleCerts(unittest.TestCase): # If --key-type and --cert-name are both set, the key type change should proceed silently. mock_set.return_value = True main._handle_unexpected_key_type_migration(config, cert) - self.assertEqual(config.key_type, "rsa") + assert config.key_type == "rsa" # If neither --key-type nor --cert-name are set, Certbot should keep the old key type. mock_set.return_value = False main._handle_unexpected_key_type_migration(config, cert) - self.assertEqual(config.key_type, "ecdsa") + assert config.key_type == "ecdsa" # If --key-type is set and --cert-name isn't, Certbot should error. config.key_type = "rsa" mock_set.side_effect = lambda var: var != "certname" - with self.assertRaises(errors.Error) as raised: + with pytest.raises(errors.Error, match="Please provide both --cert-name and --key-type"): main._handle_unexpected_key_type_migration(config, cert) - self.assertIn("Please provide both --cert-name and --key-type", str(raised.exception)) # If --key-type is not set, Certbot should keep the old key type. mock_set.side_effect = lambda var: var != "key_type" main._handle_unexpected_key_type_migration(config, cert) - self.assertEqual(config.key_type, "ecdsa") + assert config.key_type == "ecdsa" class RunTest(test_util.ConfigTestCase): @@ -192,16 +192,16 @@ class RunTest(test_util.ConfigTestCase): mock_choose.return_value = (null.Installer(self.config, "null"), None) plugins = disco.PluginsRegistry.find_all() self.config.auto_hsts = True - self.assertRaises(errors.NotSupportedError, - main.run, - self.config, plugins) + with pytest.raises(errors.NotSupportedError): + main.run(self.config, plugins) @mock.patch('certbot._internal.main._install_cert') def test_cert_success_install_error(self, mock_install_cert): mock_install_cert.side_effect = errors.PluginError("Fake installation error") self.mock_auth.return_value = mock.Mock() self.mock_find_cert.return_value = True, None - self.assertRaises(errors.PluginError, self._call) + with pytest.raises(errors.PluginError): + self._call() # Next steps should contain both renewal advice and installation error self.mock_report_next_steps.assert_called_once_with( @@ -214,9 +214,8 @@ class RunTest(test_util.ConfigTestCase): mock_choose.return_value = (null.Installer(self.config, "null"), None) plugins = disco.PluginsRegistry.find_all() self.config.must_staple = True - self.assertRaises(errors.NotSupportedError, - main.run, - self.config, plugins) + with pytest.raises(errors.NotSupportedError): + main.run(self.config, plugins) class CertonlyTest(unittest.TestCase): """Tests for certbot._internal.main.certonly.""" @@ -251,7 +250,7 @@ class CertonlyTest(unittest.TestCase): self._call('certonly --webroot -d example.com'.split()) def _assert_no_pause(self, *args, **kwargs): # pylint: disable=unused-argument - self.assertIs(kwargs.get("pause"), False) + assert kwargs.get("pause") is False @mock.patch('certbot._internal.main._report_next_steps') @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @@ -267,27 +266,27 @@ class CertonlyTest(unittest.TestCase): self._call(('certonly --webroot -d example.com -d test.org ' '--cert-name example.com').split()) - self.assertEqual(mock_lineage.call_count, 1) - self.assertEqual(mock_domains.call_count, 1) - self.assertEqual(mock_renew_cert.call_count, 1) - self.assertEqual(mock_report_cert.call_count, 1) - self.assertEqual(mock_handle_type.call_count, 1) + assert mock_lineage.call_count == 1 + assert mock_domains.call_count == 1 + assert mock_renew_cert.call_count == 1 + assert mock_report_cert.call_count == 1 + assert mock_handle_type.call_count == 1 mock_report_next_steps.assert_called_once_with( mock.ANY, None, mock.ANY, new_or_renewed_cert=True) # user confirms updating lineage with new domains self._call(('certonly --webroot -d example.com -d test.com ' '--cert-name example.com').split()) - self.assertEqual(mock_lineage.call_count, 2) - self.assertEqual(mock_domains.call_count, 2) - self.assertEqual(mock_renew_cert.call_count, 2) - self.assertEqual(mock_report_cert.call_count, 2) - self.assertEqual(mock_handle_type.call_count, 2) + assert mock_lineage.call_count == 2 + assert mock_domains.call_count == 2 + assert mock_renew_cert.call_count == 2 + assert mock_report_cert.call_count == 2 + assert mock_handle_type.call_count == 2 # error in _ask_user_to_confirm_new_names self.mock_get_utility().yesno.return_value = False - self.assertRaises(errors.ConfigurationError, self._call, - 'certonly --webroot -d example.com -d test.com --cert-name example.com'.split()) + with pytest.raises(errors.ConfigurationError): + self._call('certonly --webroot -d example.com -d test.com --cert-name example.com'.split()) @mock.patch('certbot._internal.main._report_next_steps') @mock.patch('certbot._internal.cert_manager.domains_for_certname') @@ -301,14 +300,14 @@ class CertonlyTest(unittest.TestCase): # no lineage with this name but we specified domains so create a new cert self._call(('certonly --webroot -d example.com -d test.com ' '--cert-name example.com').split()) - self.assertEqual(mock_lineage.call_count, 1) - self.assertEqual(mock_report_cert.call_count, 1) + assert mock_lineage.call_count == 1 + assert mock_report_cert.call_count == 1 # no lineage with this name and we didn't give domains mock_choose_names.return_value = ["somename"] mock_domains_for_certname.return_value = None self._call(('certonly --webroot --cert-name example.com').split()) - self.assertIs(mock_choose_names.called, True) + assert mock_choose_names.called is True @mock.patch('certbot._internal.main._report_next_steps') @mock.patch('certbot._internal.main._get_and_save_cert') @@ -364,24 +363,23 @@ class FindDomainsOrCertnameTest(unittest.TestCase): mock_config = mock.Mock(domains=None, certname=None) mock_choose_names.return_value = "domainname" # pylint: disable=protected-access - self.assertEqual(main._find_domains_or_certname(mock_config, None), ("domainname", None)) + assert main._find_domains_or_certname(mock_config, None) == ("domainname", None) @mock.patch('certbot.display.ops.choose_names') def test_no_results(self, mock_choose_names): mock_config = mock.Mock(domains=None, certname=None) mock_choose_names.return_value = [] # pylint: disable=protected-access - self.assertRaises(errors.Error, main._find_domains_or_certname, mock_config, None) + with pytest.raises(errors.Error): + main._find_domains_or_certname(mock_config, None) @mock.patch('certbot._internal.cert_manager.domains_for_certname') def test_grab_domains(self, mock_domains): mock_config = mock.Mock(domains=None, certname="one.com") mock_domains.return_value = ["one.com", "two.com"] # pylint: disable=protected-access - self.assertEqual( - main._find_domains_or_certname(mock_config, None), + assert main._find_domains_or_certname(mock_config, None) == \ (["one.com", "two.com"], "one.com") - ) class RevokeTest(test_util.TempDirTestCase): @@ -421,7 +419,6 @@ class RevokeTest(test_util.TempDirTestCase): if not args: args = 'revoke --cert-path={0} ' args = args.format(self.tmp_cert_path).split() - cli.set_by_cli.detector = None # required to reset set_by_cli state plugins = disco.PluginsRegistry.find_all() config = configuration.NamespaceConfig( cli.prepare_and_parse_args(plugins, args)) @@ -451,7 +448,7 @@ class RevokeTest(test_util.TempDirTestCase): reason.upper()).split() self._call(args) expected.append(mock.call(mock.ANY, code)) - self.assertEqual(expected, mock_revoke.call_args_list) + assert expected == mock_revoke.call_args_list @mock.patch('certbot._internal.main._delete_if_appropriate') @mock.patch('certbot._internal.storage.RenewableCert') @@ -468,8 +465,8 @@ class RevokeTest(test_util.TempDirTestCase): args = 'revoke --cert-name=example.com'.split() mock_delete_if_appropriate.return_value = False self._call(args) - self.assertEqual(mock_acme_from_config.call_args_list[0][0][0].server, - 'https://acme.example') + assert mock_acme_from_config.call_args_list[0][0][0].server == \ + 'https://acme.example' self.mock_success_revoke.assert_called_once_with(self.tmp_cert_path) @mock.patch('certbot._internal.main._delete_if_appropriate') @@ -487,8 +484,8 @@ class RevokeTest(test_util.TempDirTestCase): args = 'revoke --cert-name=example.com --server https://other.example'.split() mock_delete_if_appropriate.return_value = False self._call(args) - self.assertEqual(mock_acme_from_config.call_args_list[0][0][0].server, - 'https://other.example') + assert mock_acme_from_config.call_args_list[0][0][0].server == \ + 'https://other.example' self.mock_success_revoke.assert_called_once_with(self.tmp_cert_path) @mock.patch('certbot._internal.main._delete_if_appropriate') @@ -504,8 +501,8 @@ class RevokeTest(test_util.TempDirTestCase): args = 'revoke --cert-name=example.com'.split() mock_delete_if_appropriate.return_value = False self._call(args) - self.assertEqual(mock_acme_from_config.call_args_list[0][0][0].server, - constants.CLI_DEFAULTS['server']) + assert mock_acme_from_config.call_args_list[0][0][0].server == \ + constants.CLI_DEFAULTS['server'] self.mock_success_revoke.assert_called_once_with(self.tmp_cert_path) @mock.patch('certbot._internal.main._delete_if_appropriate') @@ -517,7 +514,8 @@ class RevokeTest(test_util.TempDirTestCase): def test_revocation_error(self): from acme import errors as acme_errors self.mock_acme_client.side_effect = acme_errors.ClientError() - self.assertRaises(acme_errors.ClientError, self._call) + with pytest.raises(acme_errors.ClientError): + self._call() self.mock_success_revoke.assert_not_called() @mock.patch('certbot._internal.main._delete_if_appropriate') @@ -528,7 +526,167 @@ class RevokeTest(test_util.TempDirTestCase): mock_get_utility().yesno.return_value = False mock_delete_if_appropriate.return_value = False self._call() - self.assertIs(mock_delete.called, False) + assert mock_delete.called is False + + +class ReconfigureTest(test_util.TempDirTestCase): + """Tests for certbot._internal.main.reconfigure""" + + def setUp(self): + super().setUp() + self.get_utility_patch = test_util.patch_display_util() + self.mock_get_utility = self.get_utility_patch.start() + self.patchers = { + 'check_symlinks': mock.patch('certbot._internal.storage.RenewableCert._check_symlinks'), + 'cert_names': mock.patch('certbot._internal.storage.RenewableCert.names'), + 'pick_installer': mock.patch('certbot._internal.plugins.selection.pick_installer'), + 'pick_auth': mock.patch('certbot._internal.plugins.selection.pick_authenticator'), + 'find_init': mock.patch('certbot._internal.plugins.disco.PluginsRegistry.find_init'), + '_get_and_save_cert': mock.patch('certbot._internal.main._get_and_save_cert'), + '_init_le_client': mock.patch('certbot._internal.main._init_le_client'), + 'list_hooks': mock.patch('certbot._internal.hooks.list_hooks'), + } + self.mocks = {k: v.start() for k, v in self.patchers.items()} + self.mocks['cert_names'].return_value = ['example.com'] + + self.config_dir = os.path.join(self.tempdir, 'config') + renewal_configs_dir = os.path.join(self.config_dir, 'renewal') + if not os.path.exists(renewal_configs_dir): + filesystem.makedirs(renewal_configs_dir) + self.renewal_file = os.path.join(renewal_configs_dir, 'example.com.conf') + original_config = """ + version = 1.32.0 + archive_dir = /etc/letsencrypt/archive/example.com + cert = /etc/letsencrypt/live/example.com/cert.pem + privkey = /etc/letsencrypt/live/example.com/privkey.pem + chain = /etc/letsencrypt/live/example.com/chain.pem + fullchain = /etc/letsencrypt/live/example.com/fullchain.pem + + # Options used in the renewal process + [renewalparams] + account = ee43634db0aa4e6804f152be39990e6a + server = https://acme-staging-v02.api.letsencrypt.org/directory + authenticator = nginx + installer = nginx + key_type = rsa + """ + with open(self.renewal_file, 'w') as f: + f.write(original_config) + with open(self.renewal_file, 'r') as f: + self.original_config = configobj.ConfigObj(f, + encoding='utf-8', default_encoding='utf-8') + + + def tearDown(self): + super().tearDown() + self.get_utility_patch.stop() + for patch in self.patchers.values(): + patch.stop() + + def _call(self, passed_args): + full_args = passed_args + ['--config-dir', self.config_dir] + plugins = disco.PluginsRegistry.find_all() + config = configuration.NamespaceConfig( + cli.prepare_and_parse_args(plugins, full_args)) + + from certbot._internal.main import reconfigure + reconfigure(config, plugins) + + with open(self.renewal_file, 'r') as f: + updated_conf = configobj.ConfigObj(f, encoding='utf-8', default_encoding='utf-8') + + return updated_conf + + def test_domains_set(self): + with pytest.raises(errors.ConfigurationError): + self._call('--cert-name cert1 -d one.cert.com'.split()) + + @mock.patch('certbot._internal.cert_manager.get_certnames') + def test_asks_for_certname(self, mock_cert_manager): + mock_cert_manager.return_value = ['example.com'] + self._call('--nginx'.split()) + assert mock_cert_manager.call_count == 1 + + def test_update_configurator(self): + named_mock = mock.Mock() + named_mock.name = 'apache' + + self.mocks['pick_installer'].return_value = named_mock + self.mocks['pick_auth'].return_value = named_mock + self.mocks['find_init'].return_value = named_mock + + new_config = self._call('--cert-name example.com --apache'.split()) + assert new_config['renewalparams']['authenticator'] == 'apache' + + @mock.patch('certbot._internal.hooks.validate_hooks') + def test_update_hooks(self, unused_validate_hooks): + assert 'pre_hook' not in self.original_config + # test set + new_config = self._call('--cert-name example.com --pre-hook'.split() + ['echo pre']) + assert new_config['renewalparams']['pre_hook'] == 'echo pre' + # test update + new_config = self._call('--cert-name example.com --pre-hook'.split() + ['echo pre2']) + assert new_config['renewalparams']['pre_hook'] == 'echo pre2' + + # test deploy hook is set even though we did a dry run + assert 'renew_hook' not in self.original_config + new_config = self._call('--cert-name example.com --deploy-hook'.split() + ['echo deploy']) + assert new_config['renewalparams']['renew_hook'] == 'echo deploy' + + def test_dry_run_fails(self): + # set side effect of raising error + self.mocks['_get_and_save_cert'].side_effect = errors.Error + + try: + self._call('--cert-name example.com --apache'.split()) + except errors.Error: + pass + + # check that config isn't modified + with open(self.renewal_file, 'r') as f: + new_config = configobj.ConfigObj(f, encoding='utf-8', default_encoding='utf-8') + assert new_config['renewalparams']['authenticator'] == 'nginx' + + @mock.patch('certbot._internal.main.display_util.notify') + def test_report_results(self, mock_notify): + # make sure report results works when config has a webroot map + original_config = """ + version = 2.0.0 + archive_dir = /etc/letsencrypt/archive/example.com + cert = /etc/letsencrypt/live/example.com/cert.pem + privkey = /etc/letsencrypt/live/example.com/privkey.pem + chain = /etc/letsencrypt/live/example.com/chain.pem + fullchain = /etc/letsencrypt/live/example.com/fullchain.pem + + # Options used in the renewal process + [renewalparams] + account = ee43634db0aa4e6804f152be39990e6a + server = https://acme-staging-v02.api.letsencrypt.org/directory + authenticator = webroot + installer = nginx + key_type = ecdsa + webroot_path = /var/www/html, + [[webroot_map]] + example.com = /var/www/html + """ + with open(self.renewal_file, 'w') as f: + f.write(original_config) + with open(self.renewal_file, 'r') as f: + self.original_config = configobj.ConfigObj(f, + encoding='utf-8', default_encoding='utf-8') + + named_mock = mock.Mock() + named_mock.name = 'nginx' + + self.mocks['pick_auth'].return_value = named_mock + self.mocks['find_init'].return_value = named_mock + + new_config = self._call('--cert-name example.com --nginx'.split()) + assert new_config['renewalparams']['authenticator'] == 'nginx' + mock_notify.assert_called_with( + '\nSuccessfully updated configuration.'+ + '\nChanges will apply when the certificate renews.') + class DeleteIfAppropriateTest(test_util.ConfigTestCase): """Tests for certbot._internal.main._delete_if_appropriate """ @@ -572,7 +730,7 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): mock_match_and_check_overlaps.side_effect = errors.OverlappingMatchFound() self._call(config) mock_delete.assert_not_called() - self.assertEqual(mock_warning.call_count, 1) + assert mock_warning.call_count == 1 @mock.patch('certbot._internal.storage.renewal_file_for_certname') @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') @@ -590,7 +748,7 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): mock_cert_path_to_lineage.return_value = "example.com" mock_overlapping_archive_dirs.return_value = False self._call(config) - self.assertEqual(mock_delete.call_count, 1) + assert mock_delete.call_count == 1 @mock.patch('certbot._internal.storage.renewal_file_for_certname') @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') @@ -610,7 +768,7 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): mock_full_archive_dir.return_value = "" mock_match_and_check_overlaps.return_value = "" self._call(config) - self.assertEqual(mock_delete.call_count, 1) + assert mock_delete.call_count == 1 @mock.patch('certbot._internal.storage.renewal_file_for_certname') @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') @@ -629,8 +787,8 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): mock_full_archive_dir.return_value = "" mock_match_and_check_overlaps.return_value = "" self._call(config) - self.assertEqual(mock_delete.call_count, 1) - self.assertFalse(mock_get_utility().yesno.called) + assert mock_delete.call_count == 1 + assert not mock_get_utility().yesno.called class DetermineAccountTest(test_util.ConfigTestCase): @@ -664,32 +822,31 @@ class DetermineAccountTest(test_util.ConfigTestCase): try: self._call() except errors.Error as err: - self.assertEqual(f"Unable to register an account with ACME server. {err_msg}", - str(err)) + assert f"Unable to register an account with ACME server. {err_msg}" == \ + str(err) def test_args_account_set(self): self.account_storage.save(self.accs[1], self.mock_client) self.config.account = self.accs[1].id - self.assertEqual((self.accs[1], None), self._call()) - self.assertEqual(self.accs[1].id, self.config.account) - self.assertIsNone(self.config.email) + assert (self.accs[1], None) == self._call() + assert self.accs[1].id == self.config.account + assert self.config.email is None def test_single_account(self): self.account_storage.save(self.accs[0], self.mock_client) - self.assertEqual((self.accs[0], None), self._call()) - self.assertEqual(self.accs[0].id, self.config.account) - self.assertIsNone(self.config.email) + assert (self.accs[0], None) == self._call() + assert self.accs[0].id == self.config.account + assert self.config.email is None @mock.patch('certbot._internal.client.display_ops.choose_account') def test_multiple_accounts(self, mock_choose_accounts): for acc in self.accs: self.account_storage.save(acc, self.mock_client) mock_choose_accounts.return_value = self.accs[1] - self.assertEqual((self.accs[1], None), self._call()) - self.assertEqual( - set(mock_choose_accounts.call_args[0][0]), set(self.accs)) - self.assertEqual(self.accs[1].id, self.config.account) - self.assertIsNone(self.config.email) + assert (self.accs[1], None) == self._call() + assert set(mock_choose_accounts.call_args[0][0]) == set(self.accs) + assert self.accs[1].id == self.config.account + assert self.config.email is None @mock.patch('certbot._internal.client.display_ops.choose_account') def test_multiple_accounts_canceled(self, mock_choose_accounts): @@ -699,7 +856,7 @@ class DetermineAccountTest(test_util.ConfigTestCase): try: self._call() except errors.Error as err: - self.assertIn("No account has been chosen", str(err)) + assert "No account has been chosen" in str(err) @mock.patch('certbot._internal.client.display_ops.get_email') @mock.patch('certbot._internal.main.display_util.notify') @@ -709,12 +866,12 @@ class DetermineAccountTest(test_util.ConfigTestCase): with mock.patch('certbot._internal.main.client') as client: client.register.return_value = ( self.accs[0], mock.sentinel.acme) - self.assertEqual((self.accs[0], mock.sentinel.acme), self._call()) + assert (self.accs[0], mock.sentinel.acme) == self._call() client.register.assert_called_once_with( self.config, self.account_storage, tos_cb=mock.ANY) - self.assertEqual(self.accs[0].id, self.config.account) - self.assertEqual('foo@bar.baz', self.config.email) + assert self.accs[0].id == self.config.account + assert 'foo@bar.baz' == self.config.email mock_notify.assert_called_once_with('Account registered.') def test_no_accounts_email(self): @@ -722,8 +879,8 @@ class DetermineAccountTest(test_util.ConfigTestCase): with mock.patch('certbot._internal.main.client') as client: client.register.return_value = (self.accs[1], mock.sentinel.acme) self._call() - self.assertEqual(self.accs[1].id, self.config.account) - self.assertEqual('other email', self.config.email) + assert self.accs[1].id == self.config.account + assert 'other email' == self.config.email def test_register_error_certbot(self): err_msg = "Some error message raised by Certbot" @@ -799,7 +956,7 @@ class MainTest(test_util.ConfigTestCase): def test_no_flags(self): with mock.patch('certbot._internal.main.run') as mock_run: self._call([]) - self.assertEqual(1, mock_run.call_count) + assert 1 == mock_run.call_count def test_version_string_program_name(self): toy_out = io.StringIO() @@ -812,7 +969,7 @@ class MainTest(test_util.ConfigTestCase): pass finally: output = toy_out.getvalue() or toy_err.getvalue() - self.assertIn("certbot", output, "Output is {0}".format(output)) + assert "certbot" in output, "Output is {0}".format(output) def _cli_missing_flag(self, args, message): "Ensure that a particular error raises a missing cli flag error containing message" @@ -822,8 +979,8 @@ class MainTest(test_util.ConfigTestCase): main.main(self.standard_args + args[:]) # NOTE: parser can alter its args! except errors.MissingCommandlineFlag as exc_: exc = exc_ - self.assertIn(message, str(exc)) - self.assertIsNotNone(exc) + assert message in str(exc) + assert exc is not None @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_noninteractive(self, _): @@ -851,11 +1008,11 @@ class MainTest(test_util.ConfigTestCase): self._call_no_clientmock(args) os_ver = util.get_os_info_ua() ua = acme_net.call_args[1]["user_agent"] - self.assertIn(os_ver, ua) + assert os_ver in ua import platform plat = platform.platform() if "linux" in plat.lower(): - self.assertIn(util.get_os_info_ua(), ua) + assert util.get_os_info_ua() in ua with mock.patch('certbot._internal.main.client.acme_client') as acme_client: acme_net = acme_client.ClientNetwork @@ -870,7 +1027,7 @@ class MainTest(test_util.ConfigTestCase): def test_installer_selection(self, mock_pick_installer, _rec): self._call(['install', '--domains', 'foo.bar', '--cert-path', 'cert', '--key-path', 'privkey', '--chain-path', 'chain'], mockisfile=True) - self.assertEqual(mock_pick_installer.call_count, 1) + assert mock_pick_installer.call_count == 1 @mock.patch('certbot._internal.main._install_cert') @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') @@ -885,9 +1042,9 @@ class MainTest(test_util.ConfigTestCase): mock_getlin.return_value = mock_lineage self._call(['install', '--cert-name', 'whatever'], mockisfile=True) call_config = mock_install.call_args[0][0] - self.assertEqual(call_config.cert_path, test_util.temp_join('cert')) - self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain')) - self.assertEqual(call_config.key_path, test_util.temp_join('privkey')) + assert call_config.cert_path == test_util.temp_join('cert') + assert call_config.fullchain_path == test_util.temp_join('chain') + assert call_config.key_path == test_util.temp_join('privkey') @mock.patch('certbot._internal.log.post_arg_parse_setup') @mock.patch('certbot._internal.main._install_cert') @@ -903,26 +1060,25 @@ class MainTest(test_util.ConfigTestCase): self._call(['install', '--cert-name', 'whatever', '--key-path', test_util.temp_join('overriding_privkey')], mockisfile=True) call_config = mock_install.call_args[0][0] - self.assertEqual(call_config.cert_path, test_util.temp_join('cert')) - self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain')) - self.assertEqual(call_config.chain_path, test_util.temp_join('chain')) - self.assertEqual(call_config.key_path, test_util.temp_join('overriding_privkey')) + assert call_config.cert_path == test_util.temp_join('cert') + assert call_config.fullchain_path == test_util.temp_join('chain') + assert call_config.chain_path == test_util.temp_join('chain') + assert call_config.key_path == test_util.temp_join('overriding_privkey') mock_install.reset() self._call(['install', '--cert-name', 'whatever', '--cert-path', test_util.temp_join('overriding_cert')], mockisfile=True) call_config = mock_install.call_args[0][0] - self.assertEqual(call_config.cert_path, test_util.temp_join('overriding_cert')) - self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain')) - self.assertEqual(call_config.key_path, test_util.temp_join('privkey')) + assert call_config.cert_path == test_util.temp_join('overriding_cert') + assert call_config.fullchain_path == test_util.temp_join('chain') + assert call_config.key_path == test_util.temp_join('privkey') @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') @mock.patch('certbot._internal.main.plug_sel.pick_installer') def test_installer_param_error(self, _inst, _rec): - self.assertRaises(errors.ConfigurationError, - self._call, - ['install', '--cert-name', 'notfound', + with pytest.raises(errors.ConfigurationError): + self._call(['install', '--cert-name', 'notfound', '--key-path', 'invalid']) @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') @@ -937,8 +1093,8 @@ class MainTest(test_util.ConfigTestCase): with mock.patch("certbot._internal.cert_manager.lineage_for_certname") as mock_getlin: mock_getlin.return_value = mock_lineage self._call(['install'], mockisfile=True) - self.assertTrue(mock_getcert.called) - self.assertTrue(mock_inst.called) + assert mock_getcert.called + assert mock_inst.called @mock.patch('certbot._internal.eff.handle_subscription') @mock.patch('certbot._internal.log.post_arg_parse_setup') @@ -965,8 +1121,8 @@ class MainTest(test_util.ConfigTestCase): # Sending nginx a non-existent conf dir will simulate misconfiguration # (we can only do that if certbot-nginx is actually present) ret, _, _, _ = self._call(args) - self.assertIn("The nginx plugin is not working", ret) - self.assertIn("MisconfigurationError", ret) + assert "The nginx plugin is not working" in ret + assert "MisconfigurationError" in ret self._cli_missing_flag(["--standalone"], "With the standalone plugin, you probably") @@ -975,16 +1131,16 @@ class MainTest(test_util.ConfigTestCase): mock_gsc.return_value = mock.MagicMock() self._call(["certonly", "--manual", "-d", "foo.bar"]) unused_config, auth, unused_installer = mock_init.call_args[0] - self.assertIsInstance(auth, manual.Authenticator) + assert isinstance(auth, manual.Authenticator) with mock.patch('certbot._internal.main.certonly') as mock_certonly: self._call(["auth", "--standalone"]) - self.assertEqual(1, mock_certonly.call_count) + assert 1 == mock_certonly.call_count @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_rollback(self, _): _, _, _, client = self._call(['rollback']) - self.assertEqual(1, client.rollback.call_count) + assert 1 == client.rollback.call_count _, _, _, client = self._call(['rollback', '--checkpoints', '123']) client.rollback.assert_called_once_with( @@ -993,17 +1149,17 @@ class MainTest(test_util.ConfigTestCase): @mock.patch('certbot._internal.cert_manager.update_live_symlinks') def test_update_symlinks(self, mock_cert_manager): self._call_no_clientmock(['update_symlinks']) - self.assertEqual(1, mock_cert_manager.call_count) + assert 1 == mock_cert_manager.call_count @mock.patch('certbot._internal.cert_manager.certificates') def test_certificates(self, mock_cert_manager): self._call_no_clientmock(['certificates']) - self.assertEqual(1, mock_cert_manager.call_count) + assert 1 == mock_cert_manager.call_count @mock.patch('certbot._internal.cert_manager.delete') def test_delete(self, mock_cert_manager): self._call_no_clientmock(['delete']) - self.assertEqual(1, mock_cert_manager.call_count) + assert 1 == mock_cert_manager.call_count @mock.patch('certbot._internal.main.plugins_disco') @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics') @@ -1028,7 +1184,7 @@ class MainTest(test_util.ConfigTestCase): plugins.visible.assert_called_once_with() plugins.visible().ifaces.assert_called_once_with(ifaces) filtered = plugins.visible().ifaces() - self.assertEqual(stdout.getvalue().strip(), str(filtered)) + assert stdout.getvalue().strip() == str(filtered) @mock.patch('certbot._internal.main.plugins_disco') @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics') @@ -1050,7 +1206,7 @@ class MainTest(test_util.ConfigTestCase): plugins.visible.assert_called_once_with() plugins.visible().ifaces.assert_called_once_with(ifaces) filtered = plugins.visible().ifaces() - self.assertEqual(stdout.getvalue().strip(), str(filtered)) + assert stdout.getvalue().strip() == str(filtered) @mock.patch('certbot._internal.main.plugins_disco') @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics') @@ -1065,8 +1221,8 @@ class MainTest(test_util.ConfigTestCase): plugins.visible.assert_called_once_with() plugins.visible().ifaces.assert_called_once_with(ifaces) filtered = plugins.visible().ifaces() - self.assertEqual(filtered.init.call_count, 1) - self.assertEqual(stdout.getvalue().strip(), str(filtered)) + assert filtered.init.call_count == 1 + assert stdout.getvalue().strip() == str(filtered) @mock.patch('certbot._internal.main.plugins_disco') @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics') @@ -1081,11 +1237,11 @@ class MainTest(test_util.ConfigTestCase): plugins.visible.assert_called_once_with() plugins.visible().ifaces.assert_called_once_with(ifaces) filtered = plugins.visible().ifaces() - self.assertEqual(filtered.init.call_count, 1) + assert filtered.init.call_count == 1 filtered.prepare.assert_called_once_with() filtered.available.assert_called_once_with() available = filtered.available() - self.assertEqual(stdout.getvalue().strip(), str(available)) + assert stdout.getvalue().strip() == str(available) def test_certonly_abspath(self): cert = 'cert' @@ -1099,40 +1255,35 @@ class MainTest(test_util.ConfigTestCase): '--fullchain-path', 'fullchain']) config, unused_plugins = mock_certonly.call_args[0] - self.assertEqual(config.cert_path, os.path.abspath(cert)) - self.assertEqual(config.key_path, os.path.abspath(key)) - self.assertEqual(config.chain_path, os.path.abspath(chain)) - self.assertEqual(config.fullchain_path, os.path.abspath(fullchain)) + assert config.cert_path == os.path.abspath(cert) + assert config.key_path == os.path.abspath(key) + assert config.chain_path == os.path.abspath(chain) + assert config.fullchain_path == os.path.abspath(fullchain) def test_certonly_bad_args(self): try: self._call(['-a', 'bad_auth', 'certonly']) assert False, "Exception should have been raised" except errors.PluginSelectionError as e: - self.assertIn('The requested bad_auth plugin does not appear', str(e)) + assert 'The requested bad_auth plugin does not appear' in str(e) def test_check_config_sanity_domain(self): # FQDN - self.assertRaises(errors.ConfigurationError, - self._call, - ['-d', 'a' * 64]) + with pytest.raises(errors.ConfigurationError): + self._call(['-d', 'a' * 64]) # FQDN 2 - self.assertRaises(errors.ConfigurationError, - self._call, - ['-d', (('a' * 50) + '.') * 10]) + with pytest.raises(errors.ConfigurationError): + self._call(['-d', (('a' * 50) + '.') * 10]) # Bare IP address (this is actually a different error message now) - self.assertRaises(errors.ConfigurationError, - self._call, - ['-d', '204.11.231.35']) + with pytest.raises(errors.ConfigurationError): + self._call(['-d', '204.11.231.35']) # Bare IPv6 address - self.assertRaises(errors.ConfigurationError, - self._call, - ['-d', '2001:db8:ac69:3ff:b1cb:c8c6:5a84:a31b']) + with pytest.raises(errors.ConfigurationError): + self._call(['-d', '2001:db8:ac69:3ff:b1cb:c8c6:5a84:a31b']) def test_csr_with_besteffort(self): - self.assertRaises( - errors.Error, self._call, - 'certonly --csr {0} --allow-subset-of-names'.format(CSR).split()) + with pytest.raises(errors.Error): + self._call('certonly --csr {0} --allow-subset-of-names'.format(CSR).split()) def test_run_with_csr(self): # This is an error because you can only use --csr with certonly @@ -1144,15 +1295,13 @@ class MainTest(test_util.ConfigTestCase): assert False, "Expected supplying --csr to fail with default verb" def test_csr_with_no_domains(self): - self.assertRaises( - errors.Error, self._call, - 'certonly --csr {0}'.format( + with pytest.raises(errors.Error): + self._call('certonly --csr {0}'.format( test_util.vector_path('csr-nonames_512.pem')).split()) def test_csr_with_inconsistent_domains(self): - self.assertRaises( - errors.Error, self._call, - 'certonly -d example.org --csr {0}'.format(CSR).split()) + with pytest.raises(errors.Error): + self._call('certonly -d example.org --csr {0}'.format(CSR).split()) def _certonly_new_request_common(self, mock_client, args=None): with mock.patch('certbot._internal.main._find_lineage_for_domains_and_certname') \ @@ -1170,10 +1319,9 @@ class MainTest(test_util.ConfigTestCase): mock_client = mock.MagicMock() mock_client.obtain_and_enroll_certificate.return_value = None self._certonly_new_request_common(mock_client, ['--dry-run']) - self.assertEqual( - mock_client.obtain_and_enroll_certificate.call_count, 1) - self.assertEqual(mock_report.call_count, 1) - self.assertIs(mock_report.call_args[0][0].dry_run, True) + assert mock_client.obtain_and_enroll_certificate.call_count == 1 + assert mock_report.call_count == 1 + assert mock_report.call_args[0][0].dry_run is True @mock.patch('certbot._internal.main._report_new_cert') @mock.patch('certbot._internal.main.util.atexit_register') @@ -1191,21 +1339,20 @@ class MainTest(test_util.ConfigTestCase): mock_client = mock.MagicMock() mock_client.obtain_and_enroll_certificate.return_value = mock_lineage self._certonly_new_request_common(mock_client) - self.assertEqual( - mock_client.obtain_and_enroll_certificate.call_count, 1) - self.assertEqual(mock_report.call_count, 1) - self.assertIn(cert_path, mock_report.call_args[0][2]) - self.assertIn(key_path, mock_report.call_args[0][3]) - self.assertIn('donate', mock_register.call_args[0][1]) - self.assertIs(mock_subscription.called, True) + assert mock_client.obtain_and_enroll_certificate.call_count == 1 + assert mock_report.call_count == 1 + assert cert_path in mock_report.call_args[0][2] + assert key_path in mock_report.call_args[0][3] + assert 'donate' in mock_register.call_args[0][1] + assert mock_subscription.called is True @mock.patch('certbot._internal.eff.handle_subscription') def test_certonly_new_request_failure(self, mock_subscription): mock_client = mock.MagicMock() mock_client.obtain_and_enroll_certificate.return_value = False - self.assertRaises(errors.Error, - self._certonly_new_request_common, mock_client) - self.assertIs(mock_subscription.called, False) + with pytest.raises(errors.Error): + self._certonly_new_request_common(mock_client) + assert mock_subscription.called is False def _test_renewal_common(self, due_for_renewal, extra_args, log_out=None, args=None, should_renew=True, error_expected=False, @@ -1271,14 +1418,14 @@ class MainTest(test_util.ConfigTestCase): else: mock_client.obtain_certificate.assert_called_once_with([mock.ANY], None) else: - self.assertEqual(mock_client.obtain_certificate.call_count, 0) + assert mock_client.obtain_certificate.call_count == 0 except: self._dump_log() raise finally: if log_out: with open(os.path.join(self.config.logs_dir, "letsencrypt.log")) as lf: - self.assertIn(log_out, lf.read()) + assert log_out in lf.read() return mock_lineage, mock_display, stdout @@ -1287,12 +1434,12 @@ class MainTest(test_util.ConfigTestCase): @mock.patch('certbot.crypto_util.notAfter') def test_certonly_renewal(self, _, mock_register, mock_report): lineage, _, _ = self._test_renewal_common(True, []) - self.assertEqual(lineage.save_successor.call_count, 1) + assert lineage.save_successor.call_count == 1 lineage.update_all_links_to.assert_called_once_with( lineage.latest_common_version()) - self.assertEqual(mock_report.call_count, 1) - self.assertIn('fullchain.pem', mock_report.call_args[0][2]) - self.assertIn('donate', mock_register.call_args[0][1]) + assert mock_report.call_count == 1 + assert 'fullchain.pem' in mock_report.call_args[0][2] + assert 'donate' in mock_register.call_args[0][1] @mock.patch('certbot._internal.main.display_util.notify') @mock.patch('certbot._internal.log.logging.handlers.RotatingFileHandler.doRollover') @@ -1308,7 +1455,7 @@ class MainTest(test_util.ConfigTestCase): _, mock_displayer, _ = self._test_renewal_common(False, ['-tvv', '--debug', '--keep'], should_renew=False) - self.assertIn('not yet due', mock_displayer().notification.call_args[0][0]) + assert 'not yet due' in mock_displayer().notification.call_args[0][0] def _dump_log(self): print("Logs:") @@ -1346,11 +1493,11 @@ class MainTest(test_util.ConfigTestCase): test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') args = ["renew", "--dry-run", "-tvv"] self._test_renewal_common(True, [], args=args, should_renew=True) - self.assertEqual(self.mock_sleep.call_count, 1) + assert self.mock_sleep.call_count == 1 # in main.py: # sleep_time = random.randint(1, 60*8) sleep_call_arg = self.mock_sleep.call_args[0][0] - self.assertTrue(1 <= sleep_call_arg <= 60*8) + assert 1 <= sleep_call_arg <= 60*8 @mock.patch('sys.stdin') def test_interactive_no_renewal_delay(self, stdin): @@ -1358,7 +1505,7 @@ class MainTest(test_util.ConfigTestCase): test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') args = ["renew", "--dry-run", "-tvv"] self._test_renewal_common(True, [], args=args, should_renew=True) - self.assertEqual(self.mock_sleep.call_count, 0) + assert self.mock_sleep.call_count == 0 @mock.patch('certbot._internal.renewal.should_renew') def test_renew_skips_recent_certs(self, should_renew): @@ -1367,8 +1514,8 @@ class MainTest(test_util.ConfigTestCase): expiry = datetime.datetime.now() + datetime.timedelta(days=90) _, _, stdout = self._test_renewal_common(False, extra_args=None, should_renew=False, args=['renew'], expiry_date=expiry) - self.assertIn('No renewals were attempted.', stdout.getvalue()) - self.assertIn('The following certificates are not due for renewal yet:', stdout.getvalue()) + assert 'No renewals were attempted.' in stdout.getvalue() + assert 'The following certificates are not due for renewal yet:' in stdout.getvalue() @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_quiet_renew(self, _): @@ -1376,13 +1523,13 @@ class MainTest(test_util.ConfigTestCase): args = ["renew", "--dry-run"] _, _, stdout = self._test_renewal_common(True, [], args=args, should_renew=True) out = stdout.getvalue() - self.assertIn("renew", out) + assert "renew" in out args = ["renew", "--dry-run", "-q"] _, _, stdout = self._test_renewal_common(True, [], args=args, should_renew=True, quiet_mode=True) out = stdout.getvalue() - self.assertEqual("", out) + assert "" == out def test_renew_hook_validation(self): test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') @@ -1440,9 +1587,9 @@ class MainTest(test_util.ConfigTestCase): if assert_oc_called is not None: if assert_oc_called: - self.assertTrue(mock_renew_cert.called) + assert mock_renew_cert.called else: - self.assertIs(mock_renew_cert.called, False) + assert mock_renew_cert.called is False def test_renew_no_renewalparams(self): self._test_renew_common(assert_oc_called=False, error_expected=True) @@ -1492,7 +1639,7 @@ class MainTest(test_util.ConfigTestCase): def test_renew_reconstitute_error(self): # pylint: disable=protected-access - with mock.patch('certbot._internal.main.renewal._reconstitute') as mock_reconstitute: + with mock.patch('certbot._internal.main.renewal.reconstitute') as mock_reconstitute: mock_reconstitute.side_effect = Exception self._test_renew_common(assert_oc_called=False, error_expected=True) @@ -1521,7 +1668,7 @@ class MainTest(test_util.ConfigTestCase): args=['renew', '--post-hook', '{0} -c "print(\'hello world\');"' .format(sys.executable)]) - self.assertIn('No hooks were run.', stdout.getvalue()) + assert 'No hooks were run.' in stdout.getvalue() @test_util.patch_display_util() @mock.patch('certbot._internal.main._find_lineage_for_domains_and_certname') @@ -1532,9 +1679,9 @@ class MainTest(test_util.ConfigTestCase): mock_renewal.return_value = ('reinstall', mock.MagicMock()) mock_init.return_value = mock_client = mock.MagicMock() self._call(['-d', 'foo.bar', '-a', 'standalone', 'certonly']) - self.assertIs(mock_client.obtain_certificate.called, False) - self.assertIs(mock_client.obtain_and_enroll_certificate.called, False) - self.assertEqual(mock_get_utility().add_message.call_count, 0) + assert mock_client.obtain_certificate.called is False + assert mock_client.obtain_and_enroll_certificate.called is False + assert mock_get_utility().add_message.call_count == 0 mock_report_new_cert.assert_not_called() #self.assertTrue('donate' not in mock_get_utility().add_message.call_args[0][0]) @@ -1564,7 +1711,7 @@ class MainTest(test_util.ConfigTestCase): self._call(args) if '--dry-run' in args: - self.assertIs(mock_client.save_certificate.called, False) + assert mock_client.save_certificate.called is False else: mock_client.save_certificate.assert_called_once_with( certr, chain, cert_path, chain_path, full_path) @@ -1574,18 +1721,18 @@ class MainTest(test_util.ConfigTestCase): @mock.patch('certbot._internal.eff.handle_subscription') def test_certonly_csr(self, mock_subscription, mock_register, mock_csr_report): self._test_certonly_csr_common() - self.assertEqual(mock_csr_report.call_count, 1) - self.assertIn('cert_512.pem', mock_csr_report.call_args[0][1]) - self.assertIsNone(mock_csr_report.call_args[0][2]) - self.assertIn('fullchain.pem', mock_csr_report.call_args[0][3]) - self.assertIn('donate', mock_register.call_args[0][1]) - self.assertIs(mock_subscription.called, True) + assert mock_csr_report.call_count == 1 + assert 'cert_512.pem' in mock_csr_report.call_args[0][1] + assert mock_csr_report.call_args[0][2] is None + assert 'fullchain.pem' in mock_csr_report.call_args[0][3] + assert 'donate' in mock_register.call_args[0][1] + assert mock_subscription.called is True @mock.patch('certbot._internal.main._csr_report_new_cert') def test_certonly_csr_dry_run(self, mock_csr_report): self._test_certonly_csr_common(['--dry-run']) - self.assertEqual(mock_csr_report.call_count, 1) - self.assertIs(mock_csr_report.call_args[0][0].dry_run, True) + assert mock_csr_report.call_count == 1 + assert mock_csr_report.call_args[0][0].dry_run is True @mock.patch('certbot._internal.main._delete_if_appropriate') @mock.patch('certbot._internal.main.client.acme_client') @@ -1596,9 +1743,9 @@ class MainTest(test_util.ConfigTestCase): self._call_no_clientmock(['--cert-path', SS_CERT_PATH, '--key-path', RSA2048_KEY_PATH, '--server', server, 'revoke']) with open(RSA2048_KEY_PATH, 'rb') as f: - self.assertEqual(mock_acme_client.ClientV2.call_count, 1) - self.assertEqual(mock_acme_client.ClientNetwork.call_args[0][0], - jose.JWK.load(f.read())) + assert mock_acme_client.ClientV2.call_count == 1 + assert mock_acme_client.ClientNetwork.call_args[0][0] == \ + jose.JWK.load(f.read()) with open(SS_CERT_PATH, 'rb') as f: cert = crypto_util.pyopenssl_load_certificate(f.read())[0] mock_revoke = mock_acme_client.ClientV2().revoke @@ -1608,8 +1755,8 @@ class MainTest(test_util.ConfigTestCase): def test_revoke_with_key_mismatch(self): server = 'foo.bar' - self.assertRaises(errors.Error, self._call_no_clientmock, - ['--cert-path', CERT, '--key-path', KEY, + with pytest.raises(errors.Error): + self._call_no_clientmock(['--cert-path', CERT, '--key-path', KEY, '--server', server, 'revoke']) @mock.patch('certbot._internal.main._delete_if_appropriate') @@ -1641,20 +1788,20 @@ class MainTest(test_util.ConfigTestCase): mocked_account.AccountFileStorage.return_value = mocked_storage mocked_storage.find_all.return_value = ["an account"] x = self._call_no_clientmock(["register", "--email", "user@example.org"]) - self.assertIn("There is an existing account", x[0]) + assert "There is an existing account" in x[0] @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins') @mock.patch('certbot._internal.updater._run_updaters') def test_plugin_selection_error(self, mock_run, mock_choose): mock_choose.side_effect = errors.PluginSelectionError - self.assertRaises(errors.PluginSelectionError, main.renew_cert, - None, None, None) + with pytest.raises(errors.PluginSelectionError): + main.renew_cert(None, None, None) self.config.dry_run = False updater.run_generic_updaters(self.config, None, None) # Make sure we're returning None, and hence not trying to run the # without installer - self.assertIs(mock_run.called, False) + assert mock_run.called is False @mock.patch('certbot._internal.main.updater.run_renewal_deployer') @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins') @@ -1669,8 +1816,8 @@ class MainTest(test_util.ConfigTestCase): main.renew_cert(self.config, None, None) - self.assertEqual(mock_init.call_count, 1) - self.assertEqual(mock_get_cert.call_count, 1) + assert mock_init.call_count == 1 + assert mock_get_cert.call_count == 1 installer.restart.assert_not_called() mock_run_renewal_deployer.assert_not_called() @@ -1698,7 +1845,7 @@ class UnregisterTest(unittest.TestCase): unused_plugins = mock.Mock() res = main.unregister(config, unused_plugins) - self.assertEqual(res, "Deactivation aborted.") + assert res == "Deactivation aborted." @mock.patch("certbot._internal.main.display_util.notify") def test_unregister(self, mock_notify): @@ -1716,7 +1863,7 @@ class UnregisterTest(unittest.TestCase): res = main.unregister(config, unused_plugins) - self.assertIsNone(res) + assert res is None mock_notify.assert_called_once_with("Account deactivated.") def test_unregister_no_account(self): @@ -1733,8 +1880,8 @@ class UnregisterTest(unittest.TestCase): res = main.unregister(config, unused_plugins) m = "Could not find existing account for server https://acme.example.com/directory." - self.assertEqual(res, m) - self.assertIs(cb_client.acme.deactivate_registration.called, False) + assert res == m + assert cb_client.acme.deactivate_registration.called is False class MakeOrVerifyNeededDirs(test_util.ConfigTestCase): @@ -1796,9 +1943,9 @@ class EnhanceTest(test_util.ConfigTestCase): mock_find.return_value = (None, None) with mock.patch('certbot._internal.main.plug_sel.pick_installer') as mock_pick: self._call(['enhance', '--redirect']) - self.assertTrue(mock_pick.called) + assert mock_pick.called # Check that the message includes "enhancements" - self.assertIn("enhancements", mock_pick.call_args[0][3]) + assert "enhancements" in mock_pick.call_args[0][3] @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @@ -1811,9 +1958,9 @@ class EnhanceTest(test_util.ConfigTestCase): with mock.patch('certbot._internal.main.plug_sel.pick_installer'): with mock.patch('certbot._internal.main.plug_sel.logger.warning') as mock_log: mock_client = self._call(['enhance', '-a', 'webroot', '--redirect']) - self.assertTrue(mock_log.called) - self.assertIn("make sense", mock_log.call_args[0][0]) - self.assertTrue(mock_client.enhance_config.called) + assert mock_log.called + assert "make sense" in mock_log.call_args[0][0] + assert mock_client.enhance_config.called @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @mock.patch('certbot._internal.main.display_ops.choose_values') @@ -1825,13 +1972,10 @@ class EnhanceTest(test_util.ConfigTestCase): mock_client = self._call(['enhance', '--redirect', '--hsts']) req_enh = ["redirect", "hsts"] not_req_enh = ["uir"] - self.assertTrue(mock_client.enhance_config.called) - self.assertTrue( - all(getattr(mock_client.config, e) for e in req_enh)) - self.assertFalse( - any(getattr(mock_client.config, e) for e in not_req_enh)) - self.assertIn( - "example.com", mock_client.enhance_config.call_args[0][0]) + assert mock_client.enhance_config.called + assert all(getattr(mock_client.config, e) for e in req_enh) + assert not any(getattr(mock_client.config, e) for e in not_req_enh) + assert "example.com" in mock_client.enhance_config.call_args[0][0] @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @mock.patch('certbot._internal.main.display_ops.choose_values') @@ -1843,21 +1987,20 @@ class EnhanceTest(test_util.ConfigTestCase): with mock.patch('certbot._internal.main.plug_sel.pick_installer'): mock_client = self._call(['enhance', '--redirect', '--hsts', '--non-interactive']) - self.assertTrue(mock_client.enhance_config.called) - self.assertIs(mock_choose.called, False) + assert mock_client.enhance_config.called + assert mock_choose.called is False @mock.patch('certbot._internal.main.display_ops.choose_values') @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') def test_user_abort_domains(self, _rec, mock_choose): mock_choose.return_value = [] with mock.patch('certbot._internal.main.plug_sel.pick_installer'): - self.assertRaises(errors.Error, - self._call, - ['enhance', '--redirect', '--hsts']) + with pytest.raises(errors.Error): + self._call(['enhance', '--redirect', '--hsts']) def test_no_enhancements_defined(self): - self.assertRaises(errors.MisconfigurationError, - self._call, ['enhance', '-a', 'null']) + with pytest.raises(errors.MisconfigurationError): + self._call(['enhance', '-a', 'null']) @mock.patch('certbot._internal.main.plug_sel.choose_configurator_plugins') @mock.patch('certbot._internal.main.display_ops.choose_values') @@ -1867,7 +2010,7 @@ class EnhanceTest(test_util.ConfigTestCase): mock_pick.return_value = (None, None) mock_pick.side_effect = errors.PluginSelectionError() mock_client = self._call(['enhance', '--hsts']) - self.assertIs(mock_client.enhance_config.called, False) + assert mock_client.enhance_config.called is False @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @mock.patch('certbot._internal.main.display_ops.choose_values') @@ -1879,9 +2022,9 @@ class EnhanceTest(test_util.ConfigTestCase): mock_choose.return_value = ["example.com", "another.tld"] mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent") self._call(['enhance', '--auto-hsts']) - self.assertTrue(self.mockinstaller.enable_autohsts.called) - self.assertEqual(self.mockinstaller.enable_autohsts.call_args[0][1], - ["example.com", "another.tld"]) + assert self.mockinstaller.enable_autohsts.called + assert self.mockinstaller.enable_autohsts.call_args[0][1] == \ + ["example.com", "another.tld"] @mock.patch('certbot._internal.cert_manager.lineage_for_certname') @mock.patch('certbot._internal.main.display_ops.choose_values') @@ -1892,14 +2035,12 @@ class EnhanceTest(test_util.ConfigTestCase): mock_inst.return_value = null.Installer(self.config, "null") mock_choose.return_value = ["example.com", "another.tld"] mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent") - self.assertRaises( - errors.NotSupportedError, - self._call, ['enhance', '--auto-hsts']) + with pytest.raises(errors.NotSupportedError): + self._call(['enhance', '--auto-hsts']) def test_enhancement_enable_conflict(self): - self.assertRaises( - errors.Error, - self._call, ['enhance', '--auto-hsts', '--hsts']) + with pytest.raises(errors.Error): + self._call(['enhance', '--auto-hsts', '--hsts']) class InstallTest(test_util.ConfigTestCase): @@ -1916,9 +2057,8 @@ class InstallTest(test_util.ConfigTestCase): plugins = disco.PluginsRegistry.find_all() self.config.auto_hsts = True self.config.certname = "nonexistent" - self.assertRaises(errors.NotSupportedError, - main.install, - self.config, plugins) + with pytest.raises(errors.NotSupportedError): + main.install(self.config, plugins) @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') @mock.patch('certbot._internal.main.plug_sel.pick_installer') @@ -1929,9 +2069,8 @@ class InstallTest(test_util.ConfigTestCase): self.config.certname = None self.config.key_path = "/tmp/nonexistent" self.config.cert_path = "/tmp/nonexistent" - self.assertRaises(errors.ConfigurationError, - main.install, - self.config, plugins) + with pytest.raises(errors.ConfigurationError): + main.install(self.config, plugins) class ReportNewCertTest(unittest.TestCase): @@ -1971,10 +2110,10 @@ class ReportNewCertTest(unittest.TestCase): self.mock_notify.assert_called_with("The dry run was successful.") def test_report_no_paths(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self._call(mock.Mock(dry_run=False), None, None, None) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self._call_csr(mock.Mock(dry_run=False), None, None, None) def test_report(self): @@ -2068,8 +2207,8 @@ class ReportNextStepsTest(unittest.TestCase): _report_next_steps(*args, **kwargs) def _output(self) -> str: - self.assertEqual(self.mock_notify.call_count, 2) - self.assertEqual(self.mock_notify.call_args_list[0][0][0], 'NEXT STEPS:') + assert self.mock_notify.call_count == 2 + assert self.mock_notify.call_args_list[0][0][0] == 'NEXT STEPS:' return self.mock_notify.call_args_list[1][0][0] def test_report(self): @@ -2083,19 +2222,19 @@ class ReportNextStepsTest(unittest.TestCase): """--csr requires manual renewal""" self.config.csr = "foo.csr" self._call(self.config, None, None) - self.assertIn("--csr will not be renewed", self._output()) + assert "--csr will not be renewed" in self._output() def test_manual_no_hook_renewal(self): """--manual without a hook requires manual renewal""" self.config.authenticator = "manual" self._call(self.config, None, None) - self.assertIn("--manual certificates requires", self._output()) + assert "--manual certificates requires" in self._output() def test_no_preconfigured_renewal(self): """No --preconfigured-renewal needs manual cron setup""" self.config.preconfigured_renewal = False self._call(self.config, None, None) - self.assertIn("https://certbot.org/renewal-setup", self._output()) + assert "https://certbot.org/renewal-setup" in self._output() class UpdateAccountTest(test_util.ConfigTestCase): @@ -2140,12 +2279,12 @@ class UpdateAccountTest(test_util.ConfigTestCase): (_, mock_storage, mock_regr) = self._prepare_mock_account() result = self._call(args) # When update succeeds, the return value of update_account() is None - self.assertIsNone(result) + assert result is None # We submitted a registration to the server - self.assertEqual(self.mocks['client'].Client().acme.update_registration.call_count, 1) + assert self.mocks['client'].Client().acme.update_registration.call_count == 1 mock_regr.body.update.assert_called_with(contact=()) # We got an update from the server and persisted it - self.assertEqual(mock_storage.update_regr.call_count, 1) + assert mock_storage.update_regr.call_count == 1 # We should have notified the user self.mocks['notify'].assert_called_with( 'Any contact information associated with this account has been removed.' @@ -2158,9 +2297,9 @@ class UpdateAccountTest(test_util.ConfigTestCase): mock_storage = mock.MagicMock() mock_storage.find_all.return_value = [] self.mocks['account'].AccountFileStorage.return_value = mock_storage - self.assertEqual(self._call(['update_account', '--email', 'user@example.org']), - 'Could not find an existing account for server' - ' https://acme-v02.api.letsencrypt.org/directory.') + assert self._call(['update_account', '--email', 'user@example.org']) == \ + 'Could not find an existing account for server' \ + ' https://acme-v02.api.letsencrypt.org/directory.' def test_update_account_remove_email(self): """Test that --register-unsafely-without-email is handled as no email""" @@ -2180,13 +2319,13 @@ class UpdateAccountTest(test_util.ConfigTestCase): result = self._call(['update_account']) # None if registration succeeds - self.assertIsNone(result) + assert result is None # We should have updated the server - self.assertEqual(mock_client.acme.update_registration.call_count, 1) + assert mock_client.acme.update_registration.call_count == 1 # We should have updated the account on disk - self.assertEqual(mock_storage.update_regr.call_count, 1) + assert mock_storage.update_regr.call_count == 1 # Subscription should have been prompted - self.assertEqual(self.mocks['prepare_sub'].call_count, 1) + assert self.mocks['prepare_sub'].call_count == 1 # Should have printed the email self.mocks['notify'].assert_called_with( 'Your e-mail address was updated to user@example.com.') @@ -2194,13 +2333,11 @@ class UpdateAccountTest(test_util.ConfigTestCase): def test_update_account_with_multiple_emails(self): """Test that multiple email addresses are handled correctly""" (_, mock_storage, mock_regr) = self._prepare_mock_account() - self.assertIsNone( - self._call(['update_account', '-m', 'user@example.com,user@example.org']) - ) + assert self._call(['update_account', '-m', 'user@example.com,user@example.org']) is None mock_regr.body.update.assert_called_with( contact=['mailto:user@example.com', 'mailto:user@example.org'] ) - self.assertEqual(mock_storage.update_regr.call_count, 1) + assert mock_storage.update_regr.call_count == 1 self.mocks['notify'].assert_called_with( 'Your e-mail address was updated to user@example.com,user@example.org.') @@ -2238,6 +2375,7 @@ class ShowAccountTest(test_util.ConfigTestCase): mock_storage.find_all.return_value = [mock_account] self.mocks['account'].AccountFileStorage.return_value = mock_storage mock_account.regr.body = mock_regr.body + mock_account.key.thumbprint.return_value = b'foobarbaz' self.mocks['determine_account'].return_value = (mock_account, mock.MagicMock()) def _test_show_account(self, contact): @@ -2246,7 +2384,6 @@ class ShowAccountTest(test_util.ConfigTestCase): mock_regr = mock.MagicMock() mock_regr.body.contact = contact mock_regr.uri = 'https://www.letsencrypt-demo.org/acme/reg/1' - mock_regr.body.key.thumbprint.return_value = b'foobarbaz' mock_client.acme.query_registration.return_value = mock_regr self.mocks['client'].Client.return_value = mock_client @@ -2254,16 +2391,16 @@ class ShowAccountTest(test_util.ConfigTestCase): self._call(args) - self.assertEqual(mock_client.acme.query_registration.call_count, 1) + assert mock_client.acme.query_registration.call_count == 1 def test_no_existing_accounts(self): """Test that no existing account is handled correctly""" mock_storage = mock.MagicMock() mock_storage.find_all.return_value = [] self.mocks['account'].AccountFileStorage.return_value = mock_storage - self.assertEqual(self._call(['show_account']), - 'Could not find an existing account for server' - ' https://acme-v02.api.letsencrypt.org/directory.') + assert self._call(['show_account']) == \ + 'Could not find an existing account for server' \ + ' https://acme-v02.api.letsencrypt.org/directory.' def test_no_existing_client(self): """Test that issues with the ACME client are handled correctly""" @@ -2274,37 +2411,40 @@ class ShowAccountTest(test_util.ConfigTestCase): try: self._call(['show_account']) except errors.Error as e: - self.assertEqual('ACME client is not set.', str(e)) + assert 'ACME client is not set.' == str(e) def test_no_contacts(self): self._test_show_account(()) - self.assertEqual(self.mocks['notify'].call_count, 1) + assert self.mocks['notify'].call_count == 1 self.mocks['notify'].assert_has_calls([ mock.call('Account details for server https://acme-v02.api.letsencr' 'ypt.org/directory:\n Account URL: https://www.letsencry' - 'pt-demo.org/acme/reg/1\n Email contact: none')]) + 'pt-demo.org/acme/reg/1\n Account Thumbprint: Zm9vYmFyYmF6\n' + ' Email contact: none')]) def test_single_email(self): contact = ('mailto:foo@example.com',) self._test_show_account(contact) - self.assertEqual(self.mocks['notify'].call_count, 1) + assert self.mocks['notify'].call_count == 1 self.mocks['notify'].assert_has_calls([ mock.call('Account details for server https://acme-v02.api.letsencr' 'ypt.org/directory:\n Account URL: https://www.letsencry' - 'pt-demo.org/acme/reg/1\n Email contact: foo@example.com')]) + 'pt-demo.org/acme/reg/1\n Account Thumbprint: Zm9vYmFyYmF6' + '\n Email contact: foo@example.com')]) def test_double_email(self): contact = ('mailto:foo@example.com', 'mailto:bar@example.com') self._test_show_account(contact) - self.assertEqual(self.mocks['notify'].call_count, 1) + assert self.mocks['notify'].call_count == 1 self.mocks['notify'].assert_has_calls([ mock.call('Account details for server https://acme-v02.api.letsencr' 'ypt.org/directory:\n Account URL: https://www.letsencry' - 'pt-demo.org/acme/reg/1\n Email contacts: foo@example.com, bar@example.com')]) + 'pt-demo.org/acme/reg/1\n Account Thumbprint: Zm9vYmFyYmF6\n' + ' Email contacts: foo@example.com, bar@example.com')]) if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index 802787e02..0d1404fcb 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -3,6 +3,7 @@ import contextlib from datetime import datetime from datetime import timedelta +import sys import unittest from unittest import mock @@ -12,12 +13,12 @@ from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.x509 import ocsp as ocsp_lib +import pytest import pytz from certbot import errors from certbot.tests import util as test_util - out = """Missing = in header key=value ocsp: Use -help for summary. """ @@ -36,9 +37,6 @@ class OCSPTestOpenSSL(unittest.TestCase): mock_exists.return_value = True self.checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) - def tearDown(self): - pass - @mock.patch('certbot.ocsp.logger.info') @mock.patch('certbot.ocsp.subprocess.run') @mock.patch('certbot.util.exe_exists') @@ -48,20 +46,20 @@ class OCSPTestOpenSSL(unittest.TestCase): from certbot import ocsp checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) - self.assertEqual(mock_run.call_count, 1) - self.assertEqual(checker.host_args("x"), ["Host=x"]) + assert mock_run.call_count == 1 + assert checker.host_args("x") == ["Host=x"] mock_run.return_value.stderr = out.partition("\n")[2] checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) - self.assertEqual(checker.host_args("x"), ["Host", "x"]) - self.assertIs(checker.broken, False) + assert checker.host_args("x") == ["Host", "x"] + assert checker.broken is False mock_exists.return_value = False mock_run.call_count = 0 checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) - self.assertEqual(mock_run.call_count, 0) - self.assertEqual(mock_log.call_count, 1) - self.assertIs(checker.broken, True) + assert mock_run.call_count == 0 + assert mock_log.call_count == 1 + assert checker.broken is True @mock.patch('certbot.ocsp._determine_ocsp_server') @mock.patch('certbot.ocsp.crypto_util.notAfter') @@ -75,32 +73,32 @@ class OCSPTestOpenSSL(unittest.TestCase): self.checker.broken = True mock_determine.return_value = ("", "") - self.assertIs(self.checker.ocsp_revoked(cert_obj), False) + assert self.checker.ocsp_revoked(cert_obj) is False self.checker.broken = False mock_run.return_value = tuple(openssl_happy[1:]) - self.assertIs(self.checker.ocsp_revoked(cert_obj), False) - self.assertEqual(mock_run.call_count, 0) + assert self.checker.ocsp_revoked(cert_obj) is False + assert mock_run.call_count == 0 mock_determine.return_value = ("http://x.co", "x.co") - self.assertIs(self.checker.ocsp_revoked(cert_obj), False) + assert self.checker.ocsp_revoked(cert_obj) is False mock_run.side_effect = errors.SubprocessError("Unable to load certificate launcher") - self.assertIs(self.checker.ocsp_revoked(cert_obj), False) - self.assertEqual(mock_run.call_count, 2) + assert self.checker.ocsp_revoked(cert_obj) is False + assert mock_run.call_count == 2 # cert expired mock_na.return_value = now mock_determine.return_value = ("", "") count_before = mock_determine.call_count - self.assertIs(self.checker.ocsp_revoked(cert_obj), False) - self.assertEqual(mock_determine.call_count, count_before) + assert self.checker.ocsp_revoked(cert_obj) is False + assert mock_determine.call_count == count_before def test_determine_ocsp_server(self): cert_path = test_util.vector_path('ocsp_certificate.pem') from certbot import ocsp result = ocsp._determine_ocsp_server(cert_path) - self.assertEqual(('http://ocsp.test4.buypass.com', 'ocsp.test4.buypass.com'), result) + assert ('http://ocsp.test4.buypass.com', 'ocsp.test4.buypass.com') == result @mock.patch('certbot.ocsp.logger') @mock.patch('certbot.util.run_script') @@ -108,23 +106,23 @@ class OCSPTestOpenSSL(unittest.TestCase): # pylint: disable=protected-access mock_run.return_value = openssl_confused from certbot import ocsp - self.assertIs(ocsp._translate_ocsp_query(*openssl_happy), False) - self.assertIs(ocsp._translate_ocsp_query(*openssl_confused), False) - self.assertEqual(mock_log.debug.call_count, 1) - self.assertEqual(mock_log.warning.call_count, 0) + assert ocsp._translate_ocsp_query(*openssl_happy) is False + assert ocsp._translate_ocsp_query(*openssl_confused) is False + assert mock_log.debug.call_count == 1 + assert mock_log.warning.call_count == 0 mock_log.debug.call_count = 0 - self.assertIs(ocsp._translate_ocsp_query(*openssl_unknown), False) - self.assertEqual(mock_log.debug.call_count, 1) - self.assertEqual(mock_log.warning.call_count, 0) - self.assertIs(ocsp._translate_ocsp_query(*openssl_expired_ocsp), False) - self.assertEqual(mock_log.debug.call_count, 2) - self.assertIs(ocsp._translate_ocsp_query(*openssl_broken), False) - self.assertEqual(mock_log.warning.call_count, 1) + assert ocsp._translate_ocsp_query(*openssl_unknown) is False + assert mock_log.debug.call_count == 1 + assert mock_log.warning.call_count == 0 + assert ocsp._translate_ocsp_query(*openssl_expired_ocsp) is False + assert mock_log.debug.call_count == 2 + assert ocsp._translate_ocsp_query(*openssl_broken) is False + assert mock_log.warning.call_count == 1 mock_log.info.call_count = 0 - self.assertIs(ocsp._translate_ocsp_query(*openssl_revoked), True) - self.assertEqual(mock_log.info.call_count, 0) - self.assertIs(ocsp._translate_ocsp_query(*openssl_expired_ocsp_revoked), True) - self.assertEqual(mock_log.info.call_count, 1) + assert ocsp._translate_ocsp_query(*openssl_revoked) is True + assert mock_log.info.call_count == 0 + assert ocsp._translate_ocsp_query(*openssl_expired_ocsp_revoked) is True + assert mock_log.info.call_count == 1 class OSCPTestCryptography(unittest.TestCase): @@ -158,7 +156,7 @@ class OSCPTestCryptography(unittest.TestCase): def test_revoke(self): with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertTrue(revoked) + assert revoked def test_responder_is_issuer(self): issuer = x509.load_pem_x509_certificate( @@ -178,11 +176,11 @@ class OSCPTestCryptography(unittest.TestCase): # Here responder and issuer are the same. So only the signature of the OCSP # response is checked (using the issuer/responder public key). - self.assertEqual(mocks['mock_check'].call_count, 2) - self.assertEqual(mocks['mock_check'].call_args_list[0][0][0].public_numbers(), - issuer.public_key().public_numbers()) - self.assertEqual(mocks['mock_check'].call_args_list[1][0][0].public_numbers(), - issuer.public_key().public_numbers()) + assert mocks['mock_check'].call_count == 2 + assert mocks['mock_check'].call_args_list[0][0][0].public_numbers() == \ + issuer.public_key().public_numbers() + assert mocks['mock_check'].call_args_list[1][0][0].public_numbers() == \ + issuer.public_key().public_numbers() def test_responder_is_authorized_delegate(self): issuer = x509.load_pem_x509_certificate( @@ -205,32 +203,32 @@ class OSCPTestCryptography(unittest.TestCase): # Here responder and issuer are not the same. Two signatures will be checked then, # first to verify the responder cert (using the issuer public key), second to # to verify the OCSP response itself (using the responder public key). - self.assertEqual(mocks['mock_check'].call_count, 4) - self.assertEqual(mocks['mock_check'].call_args_list[0][0][0].public_numbers(), - issuer.public_key().public_numbers()) - self.assertEqual(mocks['mock_check'].call_args_list[1][0][0].public_numbers(), - responder.public_key().public_numbers()) - self.assertEqual(mocks['mock_check'].call_args_list[2][0][0].public_numbers(), - issuer.public_key().public_numbers()) - self.assertEqual(mocks['mock_check'].call_args_list[3][0][0].public_numbers(), - responder.public_key().public_numbers()) + assert mocks['mock_check'].call_count == 4 + assert mocks['mock_check'].call_args_list[0][0][0].public_numbers() == \ + issuer.public_key().public_numbers() + assert mocks['mock_check'].call_args_list[1][0][0].public_numbers() == \ + responder.public_key().public_numbers() + assert mocks['mock_check'].call_args_list[2][0][0].public_numbers() == \ + issuer.public_key().public_numbers() + assert mocks['mock_check'].call_args_list[3][0][0].public_numbers() == \ + responder.public_key().public_numbers() def test_revoke_resiliency(self): # Server return an invalid HTTP response with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, http_status_code=400): revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False # OCSP response in invalid with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED): revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False # OCSP response is valid, but certificate status is unknown with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False # The OCSP response says that the certificate is revoked, but certificate # does not contain the OCSP extension. @@ -239,32 +237,32 @@ class OSCPTestCryptography(unittest.TestCase): side_effect=x509.ExtensionNotFound( 'Not found', x509.AuthorityInformationAccessOID.OCSP)): revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False # OCSP response uses an unsupported signature. with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, check_signature_side_effect=UnsupportedAlgorithm('foo')): revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False # OSCP signature response is invalid. with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, check_signature_side_effect=InvalidSignature('foo')): revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False # Assertion error on OCSP response validity with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, check_signature_side_effect=AssertionError('foo')): revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False # No responder cert in OCSP response with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: mocks['mock_response'].return_value.certificates = [] revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False # Responder cert is not signed by certificate issuer with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, @@ -273,7 +271,7 @@ class OSCPTestCryptography(unittest.TestCase): mocks['mock_response'].return_value.certificates[0] = mock.Mock( issuer='fake', subject=cert.subject) revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): # This mock is necessary to avoid the first call contained in _determine_ocsp_server @@ -284,7 +282,7 @@ class OSCPTestCryptography(unittest.TestCase): side_effect=x509.ExtensionNotFound( 'Not found', x509.AuthorityInformationAccessOID.OCSP)): revoked = self.checker.ocsp_revoked(self.cert_obj) - self.assertIs(revoked, False) + assert revoked is False @contextlib.contextmanager @@ -387,4 +385,4 @@ revoked if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/common_test.py b/certbot/tests/plugins/common_test.py index 215faaea3..4a0af7c5e 100644 --- a/certbot/tests/plugins/common_test.py +++ b/certbot/tests/plugins/common_test.py @@ -1,10 +1,12 @@ """Tests for certbot.plugins.common.""" import functools import shutil +import sys import unittest from unittest import mock import josepy as jose +import pytest from acme import challenges from certbot import achallenges @@ -27,15 +29,15 @@ class NamespaceFunctionsTest(unittest.TestCase): def test_option_namespace(self): from certbot.plugins.common import option_namespace - self.assertEqual("foo-", option_namespace("foo")) + assert "foo-" == option_namespace("foo") def test_dest_namespace(self): from certbot.plugins.common import dest_namespace - self.assertEqual("foo_", dest_namespace("foo")) + assert "foo_" == dest_namespace("foo") def test_dest_namespace_with_dashes(self): from certbot.plugins.common import dest_namespace - self.assertEqual("foo_bar_", dest_namespace("foo-bar")) + assert "foo_bar_" == dest_namespace("foo-bar") class PluginTest(unittest.TestCase): @@ -60,24 +62,24 @@ class PluginTest(unittest.TestCase): self.plugin = MockPlugin(config=self.config, name="mock") def test_init(self): - self.assertEqual("mock", self.plugin.name) - self.assertEqual(self.config, self.plugin.config) + assert "mock" == self.plugin.name + assert self.config == self.plugin.config def test_option_namespace(self): - self.assertEqual("mock-", self.plugin.option_namespace) + assert "mock-" == self.plugin.option_namespace def test_option_name(self): - self.assertEqual("mock-foo_bar", self.plugin.option_name("foo_bar")) + assert "mock-foo_bar" == self.plugin.option_name("foo_bar") def test_dest_namespace(self): - self.assertEqual("mock_", self.plugin.dest_namespace) + assert "mock_" == self.plugin.dest_namespace def test_dest(self): - self.assertEqual("mock_foo_bar", self.plugin.dest("foo-bar")) - self.assertEqual("mock_foo_bar", self.plugin.dest("foo_bar")) + assert "mock_foo_bar" == self.plugin.dest("foo-bar") + assert "mock_foo_bar" == self.plugin.dest("foo_bar") def test_conf(self): - self.assertEqual(self.config.mock_foo_bar, self.plugin.conf("foo-bar")) + assert self.config.mock_foo_bar == self.plugin.conf("foo-bar") def test_inject_parser_options(self): parser = mock.MagicMock() @@ -88,11 +90,11 @@ class PluginTest(unittest.TestCase): "--mock-foo-bar", dest="different_to_foo_bar", x=1, y=None) def test_fallback_auth_hint(self): - self.assertIn("the mock plugin completed the required dns-01 challenges", - self.plugin.auth_hint([acme_util.DNS01_A, acme_util.DNS01_A])) - self.assertIn("the mock plugin completed the required dns-01 and http-01 challenges", + assert "the mock plugin completed the required dns-01 challenges" in \ + self.plugin.auth_hint([acme_util.DNS01_A, acme_util.DNS01_A]) + assert "the mock plugin completed the required dns-01 and http-01 challenges" in \ self.plugin.auth_hint([acme_util.DNS01_A, acme_util.HTTP01_A, - acme_util.DNS01_A])) + acme_util.DNS01_A]) class InstallerTest(test_util.ConfigTestCase): @@ -172,12 +174,12 @@ class InstallerTest(test_util.ConfigTestCase): installer_func(*passed_args, **passed_kwargs) reverter_func.assert_called_once_with(*passed_args, **passed_kwargs) reverter_func.side_effect = errors.ReverterError - self.assertRaises( - errors.PluginError, installer_func, *passed_args, **passed_kwargs) + with pytest.raises(errors.PluginError): + installer_func(*passed_args, **passed_kwargs) def test_install_ssl_dhparams(self): self.installer.install_ssl_dhparams() - self.assertTrue(os.path.isfile(self.installer.ssl_dhparams)) + assert os.path.isfile(self.installer.ssl_dhparams) def _current_ssl_dhparams_hash(self): from certbot._internal.constants import SSL_DHPARAMS_SRC @@ -185,9 +187,9 @@ class InstallerTest(test_util.ConfigTestCase): def test_current_file_hash_in_all_hashes(self): from certbot._internal.constants import ALL_SSL_DHPARAMS_HASHES - self.assertIn(self._current_ssl_dhparams_hash(), ALL_SSL_DHPARAMS_HASHES, - "Constants.ALL_SSL_DHPARAMS_HASHES must be appended" - " with the sha256 hash of self.config.ssl_dhparams when it is updated.") + assert self._current_ssl_dhparams_hash() in ALL_SSL_DHPARAMS_HASHES, \ + "Constants.ALL_SSL_DHPARAMS_HASHES must be appended" \ + " with the sha256 hash of self.config.ssl_dhparams when it is updated." class AddrTest(unittest.TestCase): @@ -205,53 +207,53 @@ class AddrTest(unittest.TestCase): self.addr8 = Addr.fromstring("[fe00:1:2:3:4:5:6:7:8:9]:8080") def test_fromstring(self): - self.assertEqual(self.addr1.get_addr(), "192.168.1.1") - self.assertEqual(self.addr1.get_port(), "") - self.assertEqual(self.addr2.get_addr(), "192.168.1.1") - self.assertEqual(self.addr2.get_port(), "*") - self.assertEqual(self.addr3.get_addr(), "192.168.1.1") - self.assertEqual(self.addr3.get_port(), "80") - self.assertEqual(self.addr4.get_addr(), "[fe00::1]") - self.assertEqual(self.addr4.get_port(), "") - self.assertEqual(self.addr5.get_addr(), "[fe00::1]") - self.assertEqual(self.addr5.get_port(), "*") - self.assertEqual(self.addr6.get_addr(), "[fe00::1]") - self.assertEqual(self.addr6.get_port(), "80") - self.assertEqual(self.addr6.get_ipv6_exploded(), - "fe00:0:0:0:0:0:0:1") - self.assertEqual(self.addr1.get_ipv6_exploded(), - "") - self.assertEqual(self.addr7.get_port(), "5") - self.assertEqual(self.addr8.get_ipv6_exploded(), - "fe00:1:2:3:4:5:6:7") + assert self.addr1.get_addr() == "192.168.1.1" + assert self.addr1.get_port() == "" + assert self.addr2.get_addr() == "192.168.1.1" + assert self.addr2.get_port() == "*" + assert self.addr3.get_addr() == "192.168.1.1" + assert self.addr3.get_port() == "80" + assert self.addr4.get_addr() == "[fe00::1]" + assert self.addr4.get_port() == "" + assert self.addr5.get_addr() == "[fe00::1]" + assert self.addr5.get_port() == "*" + assert self.addr6.get_addr() == "[fe00::1]" + assert self.addr6.get_port() == "80" + assert self.addr6.get_ipv6_exploded() == \ + "fe00:0:0:0:0:0:0:1" + assert self.addr1.get_ipv6_exploded() == \ + "" + assert self.addr7.get_port() == "5" + assert self.addr8.get_ipv6_exploded() == \ + "fe00:1:2:3:4:5:6:7" def test_str(self): - self.assertEqual(str(self.addr1), "192.168.1.1") - self.assertEqual(str(self.addr2), "192.168.1.1:*") - self.assertEqual(str(self.addr3), "192.168.1.1:80") - self.assertEqual(str(self.addr4), "[fe00::1]") - self.assertEqual(str(self.addr5), "[fe00::1]:*") - self.assertEqual(str(self.addr6), "[fe00::1]:80") + assert str(self.addr1) == "192.168.1.1" + assert str(self.addr2) == "192.168.1.1:*" + assert str(self.addr3) == "192.168.1.1:80" + assert str(self.addr4) == "[fe00::1]" + assert str(self.addr5) == "[fe00::1]:*" + assert str(self.addr6) == "[fe00::1]:80" def test_get_addr_obj(self): - self.assertEqual(str(self.addr1.get_addr_obj("443")), "192.168.1.1:443") - self.assertEqual(str(self.addr2.get_addr_obj("")), "192.168.1.1") - self.assertEqual(str(self.addr1.get_addr_obj("*")), "192.168.1.1:*") - self.assertEqual(str(self.addr4.get_addr_obj("443")), "[fe00::1]:443") - self.assertEqual(str(self.addr5.get_addr_obj("")), "[fe00::1]") - self.assertEqual(str(self.addr4.get_addr_obj("*")), "[fe00::1]:*") + assert str(self.addr1.get_addr_obj("443")) == "192.168.1.1:443" + assert str(self.addr2.get_addr_obj("")) == "192.168.1.1" + assert str(self.addr1.get_addr_obj("*")) == "192.168.1.1:*" + assert str(self.addr4.get_addr_obj("443")) == "[fe00::1]:443" + assert str(self.addr5.get_addr_obj("")) == "[fe00::1]" + assert str(self.addr4.get_addr_obj("*")) == "[fe00::1]:*" def test_eq(self): - self.assertEqual(self.addr1, self.addr2.get_addr_obj("")) - self.assertNotEqual(self.addr1, self.addr2) - self.assertNotEqual(self.addr1, 3333) + assert self.addr1 == self.addr2.get_addr_obj("") + assert self.addr1 != self.addr2 + assert self.addr1 != 3333 - self.assertEqual(self.addr4, self.addr4.get_addr_obj("")) - self.assertNotEqual(self.addr4, self.addr5) - self.assertNotEqual(self.addr4, 3333) + assert self.addr4 == self.addr4.get_addr_obj("") + assert self.addr4 != self.addr5 + assert self.addr4 != 3333 from certbot.plugins.common import Addr - self.assertEqual(self.addr4, Addr.fromstring("[fe00:0:0::1]")) - self.assertEqual(self.addr4, Addr.fromstring("[fe00:0::0:0:1]")) + assert self.addr4 == Addr.fromstring("[fe00:0:0::1]") + assert self.addr4 == Addr.fromstring("[fe00:0::0:0:1]") def test_set_inclusion(self): @@ -261,14 +263,14 @@ class AddrTest(unittest.TestCase): addr2b = Addr.fromstring("192.168.1.1:*") set_b = {addr1b, addr2b} - self.assertEqual(set_a, set_b) + assert set_a == set_b set_c = {self.addr4, self.addr5} addr4b = Addr.fromstring("[fe00::1]") addr5b = Addr.fromstring("[fe00::1]:*") set_d = {addr4b, addr5b} - self.assertEqual(set_c, set_d) + assert set_c == set_d class ChallengePerformerTest(unittest.TestCase): @@ -282,11 +284,12 @@ class ChallengePerformerTest(unittest.TestCase): def test_add_chall(self): self.performer.add_chall(ACHALL, 0) - self.assertEqual(1, len(self.performer.achalls)) - self.assertEqual([0], self.performer.indices) + assert 1 == len(self.performer.achalls) + assert [0] == self.performer.indices def test_perform(self): - self.assertRaises(NotImplementedError, self.performer.perform) + with pytest.raises(NotImplementedError): + self.performer.perform() class InstallVersionControlledFileTest(test_util.TempDirTestCase): @@ -315,12 +318,12 @@ class InstallVersionControlledFileTest(test_util.TempDirTestCase): return crypto_util.sha256sum(self.source_path) def _assert_current_file(self): - self.assertTrue(os.path.isfile(self.dest_path)) - self.assertEqual(crypto_util.sha256sum(self.dest_path), - self._current_file_hash()) + assert os.path.isfile(self.dest_path) + assert crypto_util.sha256sum(self.dest_path) == \ + self._current_file_hash() def test_no_file(self): - self.assertFalse(os.path.isfile(self.dest_path)) + assert not os.path.isfile(self.dest_path) self._call() self._assert_current_file() @@ -341,12 +344,12 @@ class InstallVersionControlledFileTest(test_util.TempDirTestCase): mod_ssl_conf.write("a new line for the wrong hash\n") with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() - self.assertIs(mock_logger.warning.called, False) - self.assertTrue(os.path.isfile(self.dest_path)) - self.assertEqual(crypto_util.sha256sum(self.source_path), - self._current_file_hash()) - self.assertNotEqual(crypto_util.sha256sum(self.dest_path), - self._current_file_hash()) + assert mock_logger.warning.called is False + assert os.path.isfile(self.dest_path) + assert crypto_util.sha256sum(self.source_path) == \ + self._current_file_hash() + assert crypto_util.sha256sum(self.dest_path) != \ + self._current_file_hash() def test_manually_modified_past_file_warns(self): with open(self.dest_path, "a") as mod_ssl_conf: @@ -355,15 +358,15 @@ class InstallVersionControlledFileTest(test_util.TempDirTestCase): f.write("hashofanoldversion") with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() - self.assertEqual(mock_logger.warning.call_args[0][0], - "%s has been manually modified; updated file " - "saved to %s. We recommend updating %s for security purposes.") - self.assertEqual(crypto_util.sha256sum(self.source_path), - self._current_file_hash()) + assert mock_logger.warning.call_args[0][0] == \ + "%s has been manually modified; updated file " \ + "saved to %s. We recommend updating %s for security purposes." + assert crypto_util.sha256sum(self.source_path) == \ + self._current_file_hash() # only print warning once with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() - self.assertIs(mock_logger.warning.called, False) + assert mock_logger.warning.called is False if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/disco_test.py b/certbot/tests/plugins/disco_test.py index cb6e4b53e..0a0fd9826 100644 --- a/certbot/tests/plugins/disco_test.py +++ b/certbot/tests/plugins/disco_test.py @@ -1,11 +1,13 @@ """Tests for certbot._internal.plugins.disco.""" import functools import string +import sys from typing import List import unittest from unittest import mock import pkg_resources +import pytest from certbot import errors from certbot import interfaces @@ -13,7 +15,6 @@ from certbot._internal.plugins import null from certbot._internal.plugins import standalone from certbot._internal.plugins import webroot - EP_SA = pkg_resources.EntryPoint( "sa", "certbot._internal.plugins.standalone", attrs=("Authenticator",), @@ -54,68 +55,64 @@ class PluginEntryPointTest(unittest.TestCase): } for entry_point, name in names.items(): - self.assertEqual( - name, PluginEntryPoint.entry_point_to_plugin_name(entry_point)) + assert name == PluginEntryPoint.entry_point_to_plugin_name(entry_point) def test_description(self): - self.assertIn("server locally", self.plugin_ep.description) + assert "server locally" in self.plugin_ep.description def test_description_with_name(self): self.plugin_ep.plugin_cls = mock.MagicMock(description="Desc") - self.assertEqual( - "Desc (sa)", self.plugin_ep.description_with_name) + assert "Desc (sa)" == self.plugin_ep.description_with_name def test_long_description(self): self.plugin_ep.plugin_cls = mock.MagicMock( long_description="Long desc") - self.assertEqual( - "Long desc", self.plugin_ep.long_description) + assert "Long desc" == self.plugin_ep.long_description def test_long_description_nonexistent(self): self.plugin_ep.plugin_cls = mock.MagicMock( description="Long desc not found", spec=["description"]) - self.assertEqual( - "Long desc not found", self.plugin_ep.long_description) + assert "Long desc not found" == self.plugin_ep.long_description def test_ifaces(self): - self.assertTrue(self.plugin_ep.ifaces((interfaces.Authenticator,))) - self.assertFalse(self.plugin_ep.ifaces((interfaces.Installer,))) - self.assertFalse(self.plugin_ep.ifaces(( - interfaces.Installer, interfaces.Authenticator))) + assert self.plugin_ep.ifaces((interfaces.Authenticator,)) + assert not self.plugin_ep.ifaces((interfaces.Installer,)) + assert not self.plugin_ep.ifaces(( + interfaces.Installer, interfaces.Authenticator)) def test__init__(self): - self.assertIs(self.plugin_ep.initialized, False) - self.assertIs(self.plugin_ep.prepared, False) - self.assertIs(self.plugin_ep.misconfigured, False) - self.assertIs(self.plugin_ep.available, False) - self.assertIsNone(self.plugin_ep.problem) - self.assertIs(self.plugin_ep.entry_point, EP_SA) - self.assertEqual("sa", self.plugin_ep.name) + assert self.plugin_ep.initialized is False + assert self.plugin_ep.prepared is False + assert self.plugin_ep.misconfigured is False + assert self.plugin_ep.available is False + assert self.plugin_ep.problem is None + assert self.plugin_ep.entry_point is EP_SA + assert "sa" == self.plugin_ep.name - self.assertIs(self.plugin_ep.plugin_cls, standalone.Authenticator) + assert self.plugin_ep.plugin_cls is standalone.Authenticator def test_init(self): config = mock.MagicMock() plugin = self.plugin_ep.init(config=config) - self.assertIs(self.plugin_ep.initialized, True) - self.assertIs(plugin.config, config) + assert self.plugin_ep.initialized is True + assert plugin.config is config # memoize! - self.assertIs(self.plugin_ep.init(), plugin) - self.assertIs(plugin.config, config) + assert self.plugin_ep.init() is plugin + assert plugin.config is config # try to give different config - self.assertIs(self.plugin_ep.init(123), plugin) - self.assertIs(plugin.config, config) + assert self.plugin_ep.init(123) is plugin + assert plugin.config is config - self.assertIs(self.plugin_ep.prepared, False) - self.assertIs(self.plugin_ep.misconfigured, False) - self.assertIs(self.plugin_ep.available, False) + assert self.plugin_ep.prepared is False + assert self.plugin_ep.misconfigured is False + assert self.plugin_ep.available is False def test_prepare(self): config = mock.MagicMock() self.plugin_ep.init(config=config) self.plugin_ep.prepare() - self.assertTrue(self.plugin_ep.prepared) - self.assertIs(self.plugin_ep.misconfigured, False) + assert self.plugin_ep.prepared + assert self.plugin_ep.misconfigured is False # output doesn't matter that much, just test if it runs str(self.plugin_ep) @@ -125,41 +122,40 @@ class PluginEntryPointTest(unittest.TestCase): plugin.prepare.side_effect = errors.MisconfigurationError # pylint: disable=protected-access self.plugin_ep._initialized = plugin - self.assertIsInstance(self.plugin_ep.prepare(), - errors.MisconfigurationError) - self.assertTrue(self.plugin_ep.prepared) - self.assertTrue(self.plugin_ep.misconfigured) - self.assertIsInstance(self.plugin_ep.problem, errors.MisconfigurationError) - self.assertTrue(self.plugin_ep.available) + assert isinstance(self.plugin_ep.prepare(), errors.MisconfigurationError) + assert self.plugin_ep.prepared + assert self.plugin_ep.misconfigured + assert isinstance(self.plugin_ep.problem, errors.MisconfigurationError) + assert self.plugin_ep.available def test_prepare_no_installation(self): plugin = mock.MagicMock() plugin.prepare.side_effect = errors.NoInstallationError # pylint: disable=protected-access self.plugin_ep._initialized = plugin - self.assertIsInstance(self.plugin_ep.prepare(), errors.NoInstallationError) - self.assertIs(self.plugin_ep.prepared, True) - self.assertIs(self.plugin_ep.misconfigured, False) - self.assertIs(self.plugin_ep.available, False) + assert isinstance(self.plugin_ep.prepare(), errors.NoInstallationError) + assert self.plugin_ep.prepared is True + assert self.plugin_ep.misconfigured is False + assert self.plugin_ep.available is False def test_prepare_generic_plugin_error(self): plugin = mock.MagicMock() plugin.prepare.side_effect = errors.PluginError # pylint: disable=protected-access self.plugin_ep._initialized = plugin - self.assertIsInstance(self.plugin_ep.prepare(), errors.PluginError) - self.assertTrue(self.plugin_ep.prepared) - self.assertIs(self.plugin_ep.misconfigured, False) - self.assertIs(self.plugin_ep.available, False) + assert isinstance(self.plugin_ep.prepare(), errors.PluginError) + assert self.plugin_ep.prepared + assert self.plugin_ep.misconfigured is False + assert self.plugin_ep.available is False def test_str(self): output = str(self.plugin_ep) - self.assertIn("Authenticator", output) - self.assertNotIn("Installer", output) - self.assertIn("Plugin", output) + assert "Authenticator" in output + assert "Installer" not in output + assert "Plugin" in output def test_repr(self): - self.assertEqual("PluginEntryPoint#sa", repr(self.plugin_ep)) + assert "PluginEntryPoint#sa" == repr(self.plugin_ep) class PluginsRegistryTest(unittest.TestCase): @@ -190,46 +186,44 @@ class PluginsRegistryTest(unittest.TestCase): standalone.Authenticator, webroot.Authenticator, null.Installer, null.Installer] plugins = PluginsRegistry.find_all() - self.assertIs(plugins["sa"].plugin_cls, standalone.Authenticator) - self.assertIs(plugins["sa"].entry_point, EP_SA) - self.assertIs(plugins["wr"].plugin_cls, webroot.Authenticator) - self.assertIs(plugins["wr"].entry_point, EP_WR) - self.assertIs(plugins["ep1"].plugin_cls, null.Installer) - self.assertIs(plugins["ep1"].entry_point, self.ep1) - self.assertNotIn("p1:ep1", plugins) + assert plugins["sa"].plugin_cls is standalone.Authenticator + assert plugins["sa"].entry_point is EP_SA + assert plugins["wr"].plugin_cls is webroot.Authenticator + assert plugins["wr"].entry_point is EP_WR + assert plugins["ep1"].plugin_cls is null.Installer + assert plugins["ep1"].entry_point is self.ep1 + assert "p1:ep1" not in plugins def test_getitem(self): - self.assertEqual(self.plugin_ep, self.reg["mock"]) + assert self.plugin_ep == self.reg["mock"] def test_iter(self): - self.assertEqual(["mock"], list(self.reg)) + assert ["mock"] == list(self.reg) def test_len(self): - self.assertEqual(0, len(self._create_new_registry({}))) - self.assertEqual(1, len(self.reg)) + assert 0 == len(self._create_new_registry({})) + assert 1 == len(self.reg) def test_init(self): self.plugin_ep.init.return_value = "baz" - self.assertEqual(["baz"], self.reg.init("bar")) + assert ["baz"] == self.reg.init("bar") self.plugin_ep.init.assert_called_once_with("bar") def test_filter(self): - self.assertEqual( - self.plugins, - self.reg.filter(lambda p_ep: p_ep.name.startswith("m"))) - self.assertEqual( - {}, self.reg.filter(lambda p_ep: p_ep.name.startswith("b"))) + assert self.plugins == \ + self.reg.filter(lambda p_ep: p_ep.name.startswith("m")) + assert {} == self.reg.filter(lambda p_ep: p_ep.name.startswith("b")) def test_ifaces(self): self.plugin_ep.ifaces.return_value = True # pylint: disable=protected-access - self.assertEqual(self.plugins, self.reg.ifaces()._plugins) + assert self.plugins == self.reg.ifaces()._plugins self.plugin_ep.ifaces.return_value = False - self.assertEqual({}, self.reg.ifaces()._plugins) + assert {} == self.reg.ifaces()._plugins def test_prepare(self): self.plugin_ep.prepare.return_value = "baz" - self.assertEqual(["baz"], self.reg.prepare()) + assert ["baz"] == self.reg.prepare() self.plugin_ep.prepare.assert_called_once_with() def test_prepare_order(self): @@ -242,34 +236,33 @@ class PluginsRegistryTest(unittest.TestCase): reg.prepare() # order of prepare calls must be sorted to prevent deadlock # caused by plugins acquiring locks during prepare - self.assertEqual(order, sorted(string.ascii_letters)) + assert order == sorted(string.ascii_letters) def test_available(self): self.plugin_ep.available = True # pylint: disable=protected-access - self.assertEqual(self.plugins, self.reg.available()._plugins) + assert self.plugins == self.reg.available()._plugins self.plugin_ep.available = False - self.assertEqual({}, self.reg.available()._plugins) + assert {} == self.reg.available()._plugins def test_find_init(self): - self.assertIsNone(self.reg.find_init(mock.Mock())) + assert self.reg.find_init(mock.Mock()) is None self.plugin_ep.initialized = True - self.assertIs( - self.reg.find_init(self.plugin_ep.init()), self.plugin_ep) + assert self.reg.find_init(self.plugin_ep.init()) is self.plugin_ep def test_repr(self): self.plugin_ep.__repr__ = lambda _: "PluginEntryPoint#mock" - self.assertEqual("PluginsRegistry(PluginEntryPoint#mock)", - repr(self.reg)) + assert "PluginsRegistry(PluginEntryPoint#mock)" == \ + repr(self.reg) def test_str(self): - self.assertEqual("No plugins", str(self._create_new_registry({}))) + assert "No plugins" == str(self._create_new_registry({})) self.plugin_ep.__str__ = lambda _: "Mock" - self.assertEqual("Mock", str(self.reg)) + assert "Mock" == str(self.reg) plugins = {self.plugin_ep.name: self.plugin_ep, "foo": "Bar"} reg = self._create_new_registry(plugins) - self.assertEqual("Bar\n\nMock", str(reg)) + assert "Bar\n\nMock" == str(reg) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/dns_common_lexicon_test.py b/certbot/tests/plugins/dns_common_lexicon_test.py index 4634c2057..53bcb1a0d 100644 --- a/certbot/tests/plugins/dns_common_lexicon_test.py +++ b/certbot/tests/plugins/dns_common_lexicon_test.py @@ -1,8 +1,11 @@ """Tests for certbot.plugins.dns_common_lexicon.""" +import sys import unittest from unittest import mock +import pytest + from certbot.plugins import dns_common_lexicon from certbot.plugins import dns_test_common_lexicon @@ -23,4 +26,4 @@ class LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconCl if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/dns_common_test.py b/certbot/tests/plugins/dns_common_test.py index 97bc5dea6..c74910d40 100644 --- a/certbot/tests/plugins/dns_common_test.py +++ b/certbot/tests/plugins/dns_common_test.py @@ -2,9 +2,12 @@ import collections import logging +import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot import util from certbot.compat import os @@ -58,14 +61,15 @@ class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthen (display_util.OK, "value",)) self.auth._configure("other_key", "") - self.assertEqual(self.auth.config.fake_other_key, "value") + assert self.auth.config.fake_other_key == "value" @test_util.patch_display_util() def test_prompt_canceled(self, mock_get_utility): mock_display = mock_get_utility() mock_display.input.side_effect = ((display_util.CANCEL, "c",),) - self.assertRaises(errors.PluginError, self.auth._configure, "other_key", "") + with pytest.raises(errors.PluginError): + self.auth._configure("other_key", "") @test_util.patch_display_util() def test_prompt_file(self, mock_get_utility): @@ -79,14 +83,15 @@ class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthen (display_util.OK, path,)) self.auth._configure_file("file_path", "") - self.assertEqual(self.auth.config.fake_file_path, path) + assert self.auth.config.fake_file_path == path @test_util.patch_display_util() def test_prompt_file_canceled(self, mock_get_utility): mock_display = mock_get_utility() mock_display.directory_select.side_effect = ((display_util.CANCEL, "c",),) - self.assertRaises(errors.PluginError, self.auth._configure_file, "file_path", "") + with pytest.raises(errors.PluginError): + self.auth._configure_file("file_path", "") def test_configure_credentials(self): path = os.path.join(self.tempdir, 'file.ini') @@ -95,7 +100,7 @@ class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthen credentials = self.auth._configure_credentials("credentials", "", {"test": ""}) - self.assertEqual(credentials.conf("test"), "value") + assert credentials.conf("test") == "value" @test_util.patch_display_util() def test_prompt_credentials(self, mock_get_utility): @@ -114,13 +119,11 @@ class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthen (display_util.OK, path,)) credentials = self.auth._configure_credentials("credentials", "", {"test": ""}) - self.assertEqual(credentials.conf("test"), "value") + assert credentials.conf("test") == "value" def test_auth_hint(self): - self.assertIn( - 'try increasing --fake-propagation-seconds (currently 0 seconds).', + assert 'try increasing --fake-propagation-seconds (currently 0 seconds).' in \ self.auth.auth_hint([mock.MagicMock()]) - ) class CredentialsConfigurationTest(test_util.TempDirTestCase): @@ -144,13 +147,14 @@ class CredentialsConfigurationTest(test_util.TempDirTestCase): dns_test_common.write({"test": "value", "other": 1}, path) credentials_configuration = dns_common.CredentialsConfiguration(path) - self.assertEqual("value", credentials_configuration.conf("test")) - self.assertEqual("1", credentials_configuration.conf("other")) + assert "value" == credentials_configuration.conf("test") + assert "1" == credentials_configuration.conf("other") def test_nonexistent_file(self): path = os.path.join(self.tempdir, 'not-a-file.ini') - self.assertRaises(errors.PluginError, dns_common.CredentialsConfiguration, path) + with pytest.raises(errors.PluginError): + dns_common.CredentialsConfiguration(path) def test_valid_file_with_unsafe_permissions(self): log = self._MockLoggingHandler() @@ -161,7 +165,7 @@ class CredentialsConfigurationTest(test_util.TempDirTestCase): dns_common.CredentialsConfiguration(path) - self.assertEqual(1, len([_ for _ in log.messages['warning'] if _.startswith("Unsafe")])) + assert 1 == len([_ for _ in log.messages['warning'] if _.startswith("Unsafe")]) class CredentialsConfigurationRequireTest(test_util.TempDirTestCase): @@ -196,41 +200,38 @@ class CredentialsConfigurationRequireTest(test_util.TempDirTestCase): self._write({}) credentials_configuration = dns_common.CredentialsConfiguration(self.path) - self.assertRaises(errors.PluginError, credentials_configuration.require, {"test": ""}) + with pytest.raises(errors.PluginError): + credentials_configuration.require({"test": ""}) def test_blank(self): self._write({"test": ""}) credentials_configuration = dns_common.CredentialsConfiguration(self.path) - self.assertRaises(errors.PluginError, credentials_configuration.require, {"test": ""}) + with pytest.raises(errors.PluginError): + credentials_configuration.require({"test": ""}) def test_typo(self): self._write({"tets": "typo!"}) credentials_configuration = dns_common.CredentialsConfiguration(self.path) - self.assertRaises(errors.PluginError, credentials_configuration.require, {"test": ""}) + with pytest.raises(errors.PluginError): + credentials_configuration.require({"test": ""}) class DomainNameGuessTest(unittest.TestCase): def test_simple_case(self): - self.assertIn( - 'example.com', + assert 'example.com' in \ dns_common.base_domain_name_guesses("example.com") - ) def test_sub_domain(self): - self.assertIn( - 'example.com', + assert 'example.com' in \ dns_common.base_domain_name_guesses("foo.bar.baz.example.com") - ) def test_second_level_domain(self): - self.assertIn( - 'example.co.uk', + assert 'example.co.uk' in \ dns_common.base_domain_name_guesses("foo.bar.baz.example.co.uk") - ) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/enhancements_test.py b/certbot/tests/plugins/enhancements_test.py index 903d3e095..39734899c 100644 --- a/certbot/tests/plugins/enhancements_test.py +++ b/certbot/tests/plugins/enhancements_test.py @@ -1,7 +1,10 @@ """Tests for new style enhancements""" +import sys import unittest from unittest import mock +import pytest + from certbot._internal.plugins import null from certbot.plugins import enhancements import certbot.tests.util as test_util @@ -31,32 +34,32 @@ class EnhancementTest(test_util.ConfigTestCase): self.config.auto_hsts = True self.config.something = True enabled = list(enhancements.enabled_enhancements(self.config)) - self.assertEqual(len(enabled), 2) - self.assertTrue([i for i in enabled if i["name"] == "autohsts"]) - self.assertTrue([i for i in enabled if i["name"] == "somethingelse"]) + assert len(enabled) == 2 + assert [i for i in enabled if i["name"] == "autohsts"] + assert [i for i in enabled if i["name"] == "somethingelse"] def test_are_requested(self): - self.assertEqual(len(list(enhancements.enabled_enhancements(self.config))), 0) - self.assertFalse(enhancements.are_requested(self.config)) + assert len(list(enhancements.enabled_enhancements(self.config))) == 0 + assert not enhancements.are_requested(self.config) self.config.auto_hsts = True - self.assertEqual(len(list(enhancements.enabled_enhancements(self.config))), 1) - self.assertTrue(enhancements.are_requested(self.config)) + assert len(list(enhancements.enabled_enhancements(self.config))) == 1 + assert enhancements.are_requested(self.config) def test_are_supported(self): self.config.auto_hsts = True unsupported = null.Installer(self.config, "null") - self.assertTrue(enhancements.are_supported(self.config, self.mockinstaller)) - self.assertFalse(enhancements.are_supported(self.config, unsupported)) + assert enhancements.are_supported(self.config, self.mockinstaller) + assert not enhancements.are_supported(self.config, unsupported) def test_enable(self): self.config.auto_hsts = True domains = ["example.com", "www.example.com"] lineage = "lineage" enhancements.enable(lineage, domains, self.mockinstaller, self.config) - self.assertTrue(self.mockinstaller.enable_autohsts.called) - self.assertEqual(self.mockinstaller.enable_autohsts.call_args[0], - (lineage, domains)) + assert self.mockinstaller.enable_autohsts.called + assert self.mockinstaller.enable_autohsts.call_args[0] == \ + (lineage, domains) if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/manual_test.py b/certbot/tests/plugins/manual_test.py index a5dc69c32..b4125c659 100644 --- a/certbot/tests/plugins/manual_test.py +++ b/certbot/tests/plugins/manual_test.py @@ -4,6 +4,8 @@ import textwrap import unittest from unittest import mock +import pytest + from acme import challenges from certbot import errors from certbot.compat import filesystem @@ -45,19 +47,21 @@ class AuthenticatorTest(test_util.TempDirTestCase): def test_prepare_no_hook_noninteractive(self): self.config.noninteractive_mode = True - self.assertRaises(errors.PluginError, self.auth.prepare) + with pytest.raises(errors.PluginError): + self.auth.prepare() def test_prepare_bad_hook(self): self.config.manual_auth_hook = os.path.abspath(os.sep) # is / on UNIX self.config.validate_hooks = True - self.assertRaises(errors.HookCommandNotFound, self.auth.prepare) + with pytest.raises(errors.HookCommandNotFound): + self.auth.prepare() def test_more_info(self): - self.assertIsInstance(self.auth.more_info(), str) + assert isinstance(self.auth.more_info(), str) def test_get_chall_pref(self): - self.assertEqual(self.auth.get_chall_pref('example.org'), - [challenges.HTTP01, challenges.DNS01]) + assert self.auth.get_chall_pref('example.org') == \ + [challenges.HTTP01, challenges.DNS01] def test_script_perform(self): self.config.manual_auth_hook = ( @@ -80,32 +84,28 @@ class AuthenticatorTest(test_util.TempDirTestCase): ','.join(achall.domain for achall in self.achalls), len(self.achalls) - self.achalls.index(self.http_achall) - 1) - self.assertEqual( - self.auth.perform(self.achalls), - [achall.response(achall.account_key) for achall in self.achalls]) - self.assertEqual( - self.auth.env[self.dns_achall]['CERTBOT_AUTH_OUTPUT'], - dns_expected) - self.assertEqual( - self.auth.env[self.http_achall]['CERTBOT_AUTH_OUTPUT'], - http_expected) + assert self.auth.perform(self.achalls) == \ + [achall.response(achall.account_key) for achall in self.achalls] + assert self.auth.env[self.dns_achall]['CERTBOT_AUTH_OUTPUT'] == \ + dns_expected + assert self.auth.env[self.http_achall]['CERTBOT_AUTH_OUTPUT'] == \ + http_expected # Successful hook output should be sent to notify - self.assertEqual(self.mock_get_display().notification.call_count, len(self.achalls)) + assert self.mock_get_display().notification.call_count == len(self.achalls) for i, (args, _) in enumerate(self.mock_get_display().notification.call_args_list): needle = textwrap.indent(self.auth.env[self.achalls[i]]['CERTBOT_AUTH_OUTPUT'], ' ') - self.assertIn(needle, args[0]) + assert needle in args[0] def test_manual_perform(self): - self.assertEqual( - self.auth.perform(self.achalls), - [achall.response(achall.account_key) for achall in self.achalls]) + assert self.auth.perform(self.achalls) == \ + [achall.response(achall.account_key) for achall in self.achalls] - self.assertEqual(self.mock_get_display().notification.call_count, len(self.achalls)) + assert self.mock_get_display().notification.call_count == len(self.achalls) for i, (args, kwargs) in enumerate(self.mock_get_display().notification.call_args_list): achall = self.achalls[i] - self.assertIn(achall.validation(achall.account_key), args[0]) - self.assertIs(kwargs['wrap'], False) + assert achall.validation(achall.account_key) in args[0] + assert kwargs['wrap'] is False def test_cleanup(self): self.config.manual_auth_hook = ('{0} -c "import sys; sys.stdout.write(\'foo\')"' @@ -115,48 +115,38 @@ class AuthenticatorTest(test_util.TempDirTestCase): for achall in self.achalls: self.auth.cleanup([achall]) - self.assertEqual(os.environ['CERTBOT_AUTH_OUTPUT'], 'foo') - self.assertEqual(os.environ['CERTBOT_DOMAIN'], achall.domain) + assert os.environ['CERTBOT_AUTH_OUTPUT'] == 'foo' + assert os.environ['CERTBOT_DOMAIN'] == achall.domain if isinstance(achall.chall, (challenges.HTTP01, challenges.DNS01)): - self.assertEqual( - os.environ['CERTBOT_VALIDATION'], - achall.validation(achall.account_key)) + assert os.environ['CERTBOT_VALIDATION'] == \ + achall.validation(achall.account_key) if isinstance(achall.chall, challenges.HTTP01): - self.assertEqual( - os.environ['CERTBOT_TOKEN'], - achall.chall.encode('token')) + assert os.environ['CERTBOT_TOKEN'] == \ + achall.chall.encode('token') else: - self.assertNotIn('CERTBOT_TOKEN', os.environ) + assert 'CERTBOT_TOKEN' not in os.environ def test_auth_hint_hook(self): self.config.manual_auth_hook = '/bin/true' - self.assertEqual( - self.auth.auth_hint([acme_util.DNS01_A, acme_util.HTTP01_A]), - 'The Certificate Authority failed to verify the DNS TXT records and challenge ' - 'files created by the --manual-auth-hook. Ensure that this hook is functioning ' - 'correctly and that it waits a sufficient duration of time for DNS propagation. ' + assert self.auth.auth_hint([acme_util.DNS01_A, acme_util.HTTP01_A]) == \ + 'The Certificate Authority failed to verify the DNS TXT records and challenge ' \ + 'files created by the --manual-auth-hook. Ensure that this hook is functioning ' \ + 'correctly and that it waits a sufficient duration of time for DNS propagation. ' \ 'Refer to "certbot --help manual" and the Certbot User Guide.' - ) - self.assertEqual( - self.auth.auth_hint([acme_util.HTTP01_A]), - 'The Certificate Authority failed to verify the challenge files created by the ' - '--manual-auth-hook. Ensure that this hook is functioning correctly. Refer to ' + assert self.auth.auth_hint([acme_util.HTTP01_A]) == \ + 'The Certificate Authority failed to verify the challenge files created by the ' \ + '--manual-auth-hook. Ensure that this hook is functioning correctly. Refer to ' \ '"certbot --help manual" and the Certbot User Guide.' - ) def test_auth_hint_no_hook(self): - self.assertEqual( - self.auth.auth_hint([acme_util.DNS01_A, acme_util.HTTP01_A]), - 'The Certificate Authority failed to verify the manually created DNS TXT records ' - 'and challenge files. Ensure that you created these in the correct location, or ' + assert self.auth.auth_hint([acme_util.DNS01_A, acme_util.HTTP01_A]) == \ + 'The Certificate Authority failed to verify the manually created DNS TXT records ' \ + 'and challenge files. Ensure that you created these in the correct location, or ' \ 'try waiting longer for DNS propagation on the next attempt.' - ) - self.assertEqual( - self.auth.auth_hint([acme_util.HTTP01_A, acme_util.HTTP01_A, acme_util.HTTP01_A]), - 'The Certificate Authority failed to verify the manually created challenge files. ' + assert self.auth.auth_hint([acme_util.HTTP01_A, acme_util.HTTP01_A, acme_util.HTTP01_A]) == \ + 'The Certificate Authority failed to verify the manually created challenge files. ' \ 'Ensure that you created these in the correct location.' - ) if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/null_test.py b/certbot/tests/plugins/null_test.py index ce3440e5b..4740b7d87 100644 --- a/certbot/tests/plugins/null_test.py +++ b/certbot/tests/plugins/null_test.py @@ -1,7 +1,10 @@ """Tests for certbot._internal.plugins.null.""" +import sys import unittest from unittest import mock +import pytest + class InstallerTest(unittest.TestCase): """Tests for certbot._internal.plugins.null.Installer.""" @@ -11,10 +14,10 @@ class InstallerTest(unittest.TestCase): self.installer = Installer(config=mock.MagicMock(), name="null") def test_it(self): - self.assertIsInstance(self.installer.more_info(), str) - self.assertEqual([], self.installer.get_all_names()) - self.assertEqual([], self.installer.supported_enhancements()) + assert isinstance(self.installer.more_info(), str) + assert [] == self.installer.get_all_names() + assert [] == self.installer.supported_enhancements() if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/selection_test.py b/certbot/tests/plugins/selection_test.py index 6aed9ec8d..290965544 100644 --- a/certbot/tests/plugins/selection_test.py +++ b/certbot/tests/plugins/selection_test.py @@ -4,6 +4,8 @@ from typing import List import unittest from unittest import mock +import pytest + from certbot import errors from certbot import interfaces from certbot._internal.display import obj as display_obj @@ -22,7 +24,7 @@ class ConveniencePickPluginTest(unittest.TestCase): with mock.patch("certbot._internal.plugins.selection.pick_plugin") as mock_p: mock_p.return_value = "foo" - self.assertEqual("foo", fun(config, default, plugins, "Question?")) + assert "foo" == fun(config, default, plugins, "Question?") mock_p.assert_called_once_with( config, default, plugins, "Question?", ifaces) @@ -58,14 +60,14 @@ class PickPluginTest(unittest.TestCase): def test_default_provided(self): self.default = "foo" self._call() - self.assertEqual(1, self.reg.filter.call_count) + assert 1 == self.reg.filter.call_count def test_no_default(self): self._call() - self.assertEqual(1, self.reg.visible().ifaces.call_count) + assert 1 == self.reg.visible().ifaces.call_count def test_no_candidate(self): - self.assertIsNone(self._call()) + assert self._call() is None def test_single(self): plugin_ep = mock.MagicMock() @@ -74,7 +76,7 @@ class PickPluginTest(unittest.TestCase): self.reg.visible().ifaces().available.return_value = { "bar": plugin_ep} - self.assertEqual("foo", self._call()) + assert "foo" == self._call() def test_single_misconfigured(self): plugin_ep = mock.MagicMock() @@ -83,7 +85,7 @@ class PickPluginTest(unittest.TestCase): self.reg.visible().ifaces().available.return_value = { "bar": plugin_ep} - self.assertIsNone(self._call()) + assert self._call() is None def test_multiple(self): plugin_ep = mock.MagicMock() @@ -94,7 +96,7 @@ class PickPluginTest(unittest.TestCase): } with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: mock_choose.return_value = plugin_ep - self.assertEqual("foo", self._call()) + assert "foo" == self._call() mock_choose.assert_called_once_with( [plugin_ep, plugin_ep], self.question) @@ -106,7 +108,7 @@ class PickPluginTest(unittest.TestCase): with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: mock_choose.return_value = None - self.assertIsNone(self._call()) + assert self._call() is None class ChoosePluginTest(unittest.TestCase): @@ -134,8 +136,8 @@ class ChoosePluginTest(unittest.TestCase): def test_selection(self, mock_util): mock_util().menu.side_effect = [(display_util.OK, 0), (display_util.OK, 1)] - self.assertEqual(self.mock_stand, self._call()) - self.assertEqual(mock_util().notification.call_count, 1) + assert self.mock_stand == self._call() + assert mock_util().notification.call_count == 1 @test_util.patch_display_util() def test_more_info(self, mock_util): @@ -143,12 +145,12 @@ class ChoosePluginTest(unittest.TestCase): (display_util.OK, 1), ] - self.assertEqual(self.mock_stand, self._call()) + assert self.mock_stand == self._call() @test_util.patch_display_util() def test_no_choice(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, 0) - self.assertIsNone(self._call()) + assert self._call() is None class GetUnpreparedInstallerTest(test_util.ConfigTestCase): @@ -175,23 +177,25 @@ class GetUnpreparedInstallerTest(test_util.ConfigTestCase): def test_no_installer_defined(self): self.config.configurator = None - self.assertIsNone(self._call()) + assert self._call() is None def test_no_available_installers(self): self.config.configurator = "apache" self.plugins = PluginsRegistry({}) - self.assertRaises(errors.PluginSelectionError, self._call) + with pytest.raises(errors.PluginSelectionError): + self._call() def test_get_plugin(self): self.config.configurator = "apache" installer = self._call() - self.assertIs(installer, self.mock_apache_plugin) + assert installer is self.mock_apache_plugin def test_multiple_installers_returned(self): self.config.configurator = "apache" # Two plugins with the same name self.mock_apache_fail_ep.check_name = lambda name: name == "apache" - self.assertRaises(errors.PluginSelectionError, self._call) + with pytest.raises(errors.PluginSelectionError): + self._call() class TestChooseConfiguratorPlugins(unittest.TestCase): @@ -228,37 +232,37 @@ class TestChooseConfiguratorPlugins(unittest.TestCase): # For certonly, setting either the nginx or apache configurators should # return both an installer and authenticator inst, auth = self._runWithArgs("certonly --nginx") - self.assertEqual(inst.name, "nginx") - self.assertEqual(auth.name, "nginx") + assert inst.name == "nginx" + assert auth.name == "nginx" inst, auth = self._runWithArgs("certonly --apache") - self.assertEqual(inst.name, "apache") - self.assertEqual(auth.name, "apache") + assert inst.name == "apache" + assert auth.name == "apache" def test_noninteractive_inst_arg(self): # For certonly, if an installer arg is set, it should be returned as expected inst, auth = self._runWithArgs("certonly -a nginx -i nginx") - self.assertEqual(inst.name, "nginx") - self.assertEqual(auth.name, "nginx") + assert inst.name == "nginx" + assert auth.name == "nginx" inst, auth = self._runWithArgs("certonly -a apache -i apache") - self.assertEqual(inst.name, "apache") - self.assertEqual(auth.name, "apache") + assert inst.name == "apache" + assert auth.name == "apache" # if no installer arg is set (or it's set to none), one shouldn't be returned inst, auth = self._runWithArgs("certonly -a nginx") - self.assertEqual(inst, None) - self.assertEqual(auth.name, "nginx") + assert inst == None + assert auth.name == "nginx" inst, auth = self._runWithArgs("certonly -a nginx -i none") - self.assertEqual(inst, None) - self.assertEqual(auth.name, "nginx") + assert inst == None + assert auth.name == "nginx" inst, auth = self._runWithArgs("certonly -a apache") - self.assertEqual(inst, None) - self.assertEqual(auth.name, "apache") + assert inst == None + assert auth.name == "apache" inst, auth = self._runWithArgs("certonly -a apache -i none") - self.assertEqual(inst, None) - self.assertEqual(auth.name, "apache") + assert inst == None + assert auth.name == "apache" if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/standalone_test.py b/certbot/tests/plugins/standalone_test.py index 39454570e..7c2153a63 100644 --- a/certbot/tests/plugins/standalone_test.py +++ b/certbot/tests/plugins/standalone_test.py @@ -1,6 +1,7 @@ """Tests for certbot._internal.plugins.standalone.""" import errno import socket +import sys from typing import Dict from typing import Set from typing import Tuple @@ -9,6 +10,7 @@ from unittest import mock import josepy as jose import OpenSSL.crypto +import pytest from acme import challenges from acme import standalone as acme_standalone @@ -28,15 +30,15 @@ class ServerManagerTest(unittest.TestCase): self.mgr = ServerManager(self.certs, self.http_01_resources) def test_init(self): - self.assertIs(self.mgr.certs, self.certs) - self.assertIs(self.mgr.http_01_resources, self.http_01_resources) + assert self.mgr.certs is self.certs + assert self.mgr.http_01_resources is self.http_01_resources def _test_run_stop(self, challenge_type): server = self.mgr.run(port=0, challenge_type=challenge_type) port = server.getsocknames()[0][1] - self.assertEqual(self.mgr.running(), {port: server}) + assert self.mgr.running() == {port: server} self.mgr.stop(port=port) - self.assertEqual(self.mgr.running(), {}) + assert self.mgr.running() == {} def test_run_stop_http_01(self): self._test_run_stop(challenges.HTTP01) @@ -45,10 +47,10 @@ class ServerManagerTest(unittest.TestCase): server = self.mgr.run(port=0, challenge_type=challenges.HTTP01) port = server.getsocknames()[0][1] server2 = self.mgr.run(port=port, challenge_type=challenges.HTTP01) - self.assertEqual(self.mgr.running(), {port: server}) - self.assertIs(server, server2) + assert self.mgr.running() == {port: server} + assert server is server2 self.mgr.stop(port) - self.assertEqual(self.mgr.running(), {}) + assert self.mgr.running() == {} def test_run_bind_error(self): some_server = socket.socket(socket.AF_INET6) @@ -59,10 +61,10 @@ class ServerManagerTest(unittest.TestCase): maybe_another_server.bind(("", port)) except socket.error: pass - self.assertRaises( - errors.StandaloneBindError, self.mgr.run, port, + with pytest.raises(errors.StandaloneBindError): + self.mgr.run(port, challenge_type=challenges.HTTP01) - self.assertEqual(self.mgr.running(), {}) + assert self.mgr.running() == {} some_server.close() maybe_another_server.close() @@ -87,18 +89,18 @@ class AuthenticatorTest(unittest.TestCase): self.auth.servers = mock.MagicMock() def test_more_info(self): - self.assertIsInstance(self.auth.more_info(), str) + assert isinstance(self.auth.more_info(), str) def test_get_chall_pref(self): - self.assertEqual(self.auth.get_chall_pref(domain=None), - [challenges.HTTP01]) + assert self.auth.get_chall_pref(domain=None) == \ + [challenges.HTTP01] def test_perform(self): achalls = self._get_achalls() response = self.auth.perform(achalls) expected = [achall.response(achall.account_key) for achall in achalls] - self.assertEqual(response, expected) + assert response == expected @test_util.patch_display_util() def test_perform_eaddrinuse_retry(self, mock_get_utility): @@ -119,22 +121,24 @@ class AuthenticatorTest(unittest.TestCase): mock_yesno.return_value = False encountered_errno = errno.EADDRINUSE - self.assertRaises(errors.PluginError, self._fail_perform, encountered_errno) + with pytest.raises(errors.PluginError): + self._fail_perform(encountered_errno) self._assert_correct_yesno_call(mock_yesno) def _assert_correct_yesno_call(self, mock_yesno): yesno_args, yesno_kwargs = mock_yesno.call_args - self.assertIn("in use", yesno_args[0]) - self.assertFalse(yesno_kwargs.get("default", True)) + assert "in use" in yesno_args[0] + assert not yesno_kwargs.get("default", True) def test_perform_eacces(self): encountered_errno = errno.EACCES - self.assertRaises(errors.PluginError, self._fail_perform, encountered_errno) + with pytest.raises(errors.PluginError): + self._fail_perform(encountered_errno) def test_perform_unexpected_socket_error(self): encountered_errno = errno.ENOTCONN - self.assertRaises( - errors.StandaloneBindError, self._fail_perform, encountered_errno) + with pytest.raises(errors.StandaloneBindError): + self._fail_perform(encountered_errno) def _fail_perform(self, encountered_errno): error = errors.StandaloneBindError(mock.MagicMock(errno=encountered_errno), -1) @@ -159,30 +163,30 @@ class AuthenticatorTest(unittest.TestCase): self.auth.served["server2"].update(["chall2", "chall3"]) self.auth.cleanup(["chall1"]) - self.assertEqual(self.auth.served, { - "server1": set(), "server2": {"chall2", "chall3"}}) + assert self.auth.served == { + "server1": set(), "server2": {"chall2", "chall3"}} self.auth.servers.stop.assert_called_once_with(1) self.auth.servers.running.return_value = { 2: "server2", } self.auth.cleanup(["chall2"]) - self.assertEqual(self.auth.served, { - "server1": set(), "server2": {"chall3"}}) - self.assertEqual(1, self.auth.servers.stop.call_count) + assert self.auth.served == { + "server1": set(), "server2": {"chall3"}} + assert 1 == self.auth.servers.stop.call_count self.auth.cleanup(["chall3"]) - self.assertEqual(self.auth.served, { - "server1": set(), "server2": set()}) + assert self.auth.served == { + "server1": set(), "server2": set()} self.auth.servers.stop.assert_called_with(2) def test_auth_hint(self): self.config.http01_port = "80" self.config.http01_address = None - self.assertIn("on port 80", self.auth.auth_hint([])) + assert "on port 80" in self.auth.auth_hint([]) self.config.http01_address = "127.0.0.1" - self.assertIn("on 127.0.0.1:80", self.auth.auth_hint([])) + assert "on 127.0.0.1:80" in self.auth.auth_hint([]) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/storage_test.py b/certbot/tests/plugins/storage_test.py index a63ef7795..a6f8f876d 100644 --- a/certbot/tests/plugins/storage_test.py +++ b/certbot/tests/plugins/storage_test.py @@ -1,18 +1,20 @@ """Tests for certbot.plugins.storage.PluginStorage""" import json +import sys from typing import Iterable from typing import List from typing import Optional import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import filesystem from certbot.compat import os from certbot.tests import util as test_util - class PluginStorageTest(test_util.ConfigTestCase): """Test for certbot.plugins.storage.PluginStorage""" @@ -35,8 +37,8 @@ class PluginStorageTest(test_util.ConfigTestCase): with mock.patch("builtins.open", mock_open): with mock.patch('certbot.compat.os.path.isfile', return_value=True): with mock.patch("certbot.reverter.util"): - self.assertRaises(errors.PluginStorageError, - self.plugin.storage._load) # pylint: disable=protected-access + with pytest.raises(errors.PluginStorageError): + self.plugin.storage._load() # pylint: disable=protected-access def test_load_errors_empty(self): with open(os.path.join(self.config.config_dir, ".pluginstorage.json"), "w") as fh: @@ -45,10 +47,10 @@ class PluginStorageTest(test_util.ConfigTestCase): # Should not error out but write a debug log line instead with mock.patch("certbot.reverter.util"): nocontent = self.plugin_cls(self.config, "mockplugin") - self.assertRaises(KeyError, - nocontent.storage.fetch, "value") - self.assertTrue(mock_log.called) - self.assertIn("no values loaded", mock_log.call_args[0][0]) + with pytest.raises(KeyError): + nocontent.storage.fetch("value") + assert mock_log.called + assert "no values loaded" in mock_log.call_args[0][0] def test_load_errors_corrupted(self): with open(os.path.join(self.config.config_dir, @@ -57,10 +59,9 @@ class PluginStorageTest(test_util.ConfigTestCase): with mock.patch("certbot.plugins.storage.logger.error") as mock_log: with mock.patch("certbot.reverter.util"): corrupted = self.plugin_cls(self.config, "mockplugin") - self.assertRaises(errors.PluginError, - corrupted.storage.fetch, - "value") - self.assertIn("is corrupted", mock_log.call_args[0][0]) + with pytest.raises(errors.PluginError): + corrupted.storage.fetch("value") + assert "is corrupted" in mock_log.call_args[0][0] def test_save_errors_cant_serialize(self): with mock.patch("certbot.plugins.storage.logger.error") as mock_log: @@ -68,9 +69,9 @@ class PluginStorageTest(test_util.ConfigTestCase): self.plugin.storage._initialized = True # pylint: disable=protected-access self.plugin.storage._storagepath = "/tmp/whatever" self.plugin.storage._data = self.plugin_cls # pylint: disable=protected-access - self.assertRaises(errors.PluginStorageError, - self.plugin.storage.save) - self.assertIn("Could not serialize", mock_log.call_args[0][0]) + with pytest.raises(errors.PluginStorageError): + self.plugin.storage.save() + assert "Could not serialize" in mock_log.call_args[0][0] def test_save_errors_unable_to_write_file(self): mock_open = mock.mock_open() @@ -80,25 +81,25 @@ class PluginStorageTest(test_util.ConfigTestCase): self.plugin.storage._data = {"valid": "data"} # pylint: disable=protected-access self.plugin.storage._initialized = True # pylint: disable=protected-access self.plugin.storage._storagepath = "/tmp/whatever" - self.assertRaises(errors.PluginStorageError, - self.plugin.storage.save) - self.assertIn("Could not write", mock_log.call_args[0][0]) + with pytest.raises(errors.PluginStorageError): + self.plugin.storage.save() + assert "Could not write" in mock_log.call_args[0][0] def test_save_uninitialized(self): with mock.patch("certbot.reverter.util"): - self.assertRaises(errors.PluginStorageError, - self.plugin_cls(self.config, "x").storage.save) + with pytest.raises(errors.PluginStorageError): + self.plugin_cls(self.config, "x").storage.save() def test_namespace_isolation(self): with mock.patch("certbot.reverter.util"): plugin1 = self.plugin_cls(self.config, "first") plugin2 = self.plugin_cls(self.config, "second") plugin1.storage.put("first_key", "first_value") - self.assertRaises(KeyError, - plugin2.storage.fetch, "first_key") - self.assertRaises(KeyError, - plugin2.storage.fetch, "first") - self.assertEqual(plugin1.storage.fetch("first_key"), "first_value") + with pytest.raises(KeyError): + plugin2.storage.fetch("first_key") + with pytest.raises(KeyError): + plugin2.storage.fetch("first") + assert plugin1.storage.fetch("first_key") == "first_value" def test_saved_state(self): self.plugin.storage.put("testkey", "testvalue") @@ -106,16 +107,16 @@ class PluginStorageTest(test_util.ConfigTestCase): self.plugin.storage.save() with mock.patch("certbot.reverter.util"): another = self.plugin_cls(self.config, "mockplugin") - self.assertEqual(another.storage.fetch("testkey"), "testvalue") + assert another.storage.fetch("testkey") == "testvalue" with open(os.path.join(self.config.config_dir, ".pluginstorage.json"), 'r') as fh: psdata = fh.read() psjson = json.loads(psdata) - self.assertIn("mockplugin", psjson.keys()) - self.assertEqual(len(psjson), 1) - self.assertEqual(psjson["mockplugin"]["testkey"], "testvalue") + assert "mockplugin" in psjson.keys() + assert len(psjson) == 1 + assert psjson["mockplugin"]["testkey"] == "testvalue" if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/util_test.py b/certbot/tests/plugins/util_test.py index faac01165..c3d76f801 100644 --- a/certbot/tests/plugins/util_test.py +++ b/certbot/tests/plugins/util_test.py @@ -1,44 +1,40 @@ """Tests for certbot.plugins.util.""" -import unittest +import sys from unittest import mock +import pytest + from certbot.compat import os -class GetPrefixTest(unittest.TestCase): - """Tests for certbot.plugins.get_prefixes.""" - def test_get_prefix(self): - from certbot.plugins.util import get_prefixes - self.assertEqual( - get_prefixes('/a/b/c'), - [os.path.normpath(path) for path in ['/a/b/c', '/a/b', '/a', '/']]) - self.assertEqual(get_prefixes('/'), [os.path.normpath('/')]) - self.assertEqual(get_prefixes('a'), ['a']) +def test_get_prefix(): + from certbot.plugins.util import get_prefixes + assert get_prefixes('/a/b/c') == \ + [os.path.normpath(path) for path in ['/a/b/c', '/a/b', '/a', '/']] + assert get_prefixes('/') == [os.path.normpath('/')] + assert get_prefixes('a') == ['a'] -class PathSurgeryTest(unittest.TestCase): - """Tests for certbot.plugins.path_surgery.""" - - @mock.patch("certbot.plugins.util.logger.debug") - def test_path_surgery(self, mock_debug): - from certbot.plugins.util import path_surgery - all_path = {"PATH": "/usr/local/bin:/bin/:/usr/sbin/:/usr/local/sbin/"} - with mock.patch.dict('os.environ', all_path): - with mock.patch('certbot.util.exe_exists') as mock_exists: - mock_exists.return_value = True - self.assertIs(path_surgery("eg"), True) - self.assertEqual(mock_debug.call_count, 0) - self.assertEqual(os.environ["PATH"], all_path["PATH"]) - if os.name != 'nt': - # This part is specific to Linux since on Windows no PATH surgery is ever done. - no_path = {"PATH": "/tmp/"} - with mock.patch.dict('os.environ', no_path): - path_surgery("thingy") - self.assertEqual(mock_debug.call_count, 2 if os.name != 'nt' else 1) - self.assertIn("Failed to find", mock_debug.call_args[0][0]) - self.assertIn("/usr/local/bin", os.environ["PATH"]) - self.assertIn("/tmp", os.environ["PATH"]) +@mock.patch("certbot.plugins.util.logger.debug") +def test_path_surgery(mock_debug): + from certbot.plugins.util import path_surgery + all_path = {"PATH": "/usr/local/bin:/bin/:/usr/sbin/:/usr/local/sbin/"} + with mock.patch.dict('os.environ', all_path): + with mock.patch('certbot.util.exe_exists') as mock_exists: + mock_exists.return_value = True + assert path_surgery("eg") is True + assert mock_debug.call_count == 0 + assert os.environ["PATH"] == all_path["PATH"] + if os.name != 'nt': + # This part is specific to Linux since on Windows no PATH surgery is ever done. + no_path = {"PATH": "/tmp/"} + with mock.patch.dict('os.environ', no_path): + path_surgery("thingy") + assert mock_debug.call_count == (2 if os.name != 'nt' else 1) + assert "Failed to find" in mock_debug.call_args[0][0] + assert "/usr/local/bin" in os.environ["PATH"] + assert "/tmp" in os.environ["PATH"] if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/plugins/webroot_test.py b/certbot/tests/plugins/webroot_test.py index d5ccc4b4f..2561ccaf1 100644 --- a/certbot/tests/plugins/webroot_test.py +++ b/certbot/tests/plugins/webroot_test.py @@ -6,11 +6,13 @@ import argparse import errno import json import shutil +import sys import tempfile import unittest from unittest import mock import josepy as jose +import pytest from acme import challenges from certbot import achallenges @@ -32,6 +34,7 @@ class AuthenticatorTest(unittest.TestCase): def setUp(self): from certbot._internal.plugins.webroot import Authenticator + # On Linux directories created by tempfile.mkdtemp inherit their permissions from their # parent directory. So the actual permissions are inconsistent over various tests env. # To circumvent this, a dedicated sub-workspace is created under the workspace, using @@ -55,13 +58,13 @@ class AuthenticatorTest(unittest.TestCase): def test_more_info(self): more_info = self.auth.more_info() - self.assertIsInstance(more_info, str) - self.assertIn(self.path, more_info) + assert isinstance(more_info, str) + assert self.path in more_info def test_add_parser_arguments(self): add = mock.MagicMock() self.auth.add_parser_arguments(add) - self.assertEqual(2, add.call_count) + assert 2 == add.call_count def test_prepare(self): self.auth.prepare() # shouldn't raise any exceptions @@ -74,14 +77,14 @@ class AuthenticatorTest(unittest.TestCase): mock_display.menu.return_value = (display_util.OK, 1,) self.auth.perform([self.achall]) - self.assertTrue(mock_display.menu.called) + assert mock_display.menu.called for call in mock_display.menu.call_args_list: - self.assertIn(self.achall.domain, call[0][0]) - self.assertTrue(all( + assert self.achall.domain in call[0][0] + assert all( webroot in call[0][1] - for webroot in self.config.webroot_map.values())) - self.assertEqual(self.config.webroot_map[self.achall.domain], - self.path) + for webroot in self.config.webroot_map.values()) + assert self.config.webroot_map[self.achall.domain] == \ + self.path @unittest.skipIf(filesystem.POSIX_MODE, reason='Test specific to Windows') @test_util.patch_display_util() @@ -90,9 +93,9 @@ class AuthenticatorTest(unittest.TestCase): mock_display.menu.return_value = (display_util.OK, 1,) self.auth.perform([self.achall]) - self.assertTrue(os.path.exists(os.path.join(self.root_challenge_path, "web.config"))) + assert os.path.exists(os.path.join(self.root_challenge_path, "web.config")) self.auth.cleanup([self.achall]) - self.assertFalse(os.path.exists(os.path.join(self.root_challenge_path, "web.config"))) + assert not os.path.exists(os.path.join(self.root_challenge_path, "web.config")) @unittest.skipIf(filesystem.POSIX_MODE, reason='Test specific to Windows') @test_util.patch_display_util() @@ -110,7 +113,7 @@ class AuthenticatorTest(unittest.TestCase): from certbot import crypto_util webconfig_hash = crypto_util.sha256sum(webconfig_path) from certbot._internal.plugins.webroot import _WEB_CONFIG_SHA256SUMS - self.assertTrue(webconfig_hash not in _WEB_CONFIG_SHA256SUMS) + assert webconfig_hash not in _WEB_CONFIG_SHA256SUMS @unittest.skipIf(filesystem.POSIX_MODE, reason='Test specific to Windows') def test_foreign_webconfig_multiple_domains(self): @@ -135,13 +138,14 @@ class AuthenticatorTest(unittest.TestCase): mock_display = mock_get_utility() mock_display.menu.side_effect = ((display_util.CANCEL, -1),) - self.assertRaises(errors.PluginError, self.auth.perform, [self.achall]) - self.assertTrue(mock_display.menu.called) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) + assert mock_display.menu.called for call in mock_display.menu.call_args_list: - self.assertIn(self.achall.domain, call[0][0]) - self.assertTrue(all( + assert self.achall.domain in call[0][0] + assert all( webroot in call[0][1] - for webroot in self.config.webroot_map.values())) + for webroot in self.config.webroot_map.values()) @test_util.patch_display_util() def test_new_webroot(self, mock_get_utility): @@ -156,7 +160,7 @@ class AuthenticatorTest(unittest.TestCase): self.auth.perform([self.achall]) - self.assertEqual(self.config.webroot_map[self.achall.domain], self.path) + assert self.config.webroot_map[self.achall.domain] == self.path @test_util.patch_display_util() def test_new_webroot_empty_map_cancel(self, mock_get_utility): @@ -167,14 +171,14 @@ class AuthenticatorTest(unittest.TestCase): mock_display.menu.return_value = (display_util.OK, 0,) with mock.patch('certbot.display.ops.validated_directory') as m: m.return_value = (display_util.CANCEL, -1) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) + with pytest.raises(errors.PluginError): + self.auth.perform([self.achall]) def test_perform_missing_root(self): self.config.webroot_path = None self.config.webroot_map = {} - self.assertRaises(errors.PluginError, self.auth.perform, []) + with pytest.raises(errors.PluginError): + self.auth.perform([]) def test_perform_reraises_other_errors(self): self.auth.full_path = os.path.join(self.path, "null") @@ -188,7 +192,8 @@ class AuthenticatorTest(unittest.TestCase): print("Warning, running tests as root skips permissions tests...") except IOError: # ok, permissions work, test away... - self.assertRaises(errors.PluginError, self.auth.perform, []) + with pytest.raises(errors.PluginError): + self.auth.perform([]) filesystem.chmod(self.path, 0o700) @mock.patch("certbot._internal.plugins.webroot.filesystem.copy_ownership_and_apply_mode") @@ -209,7 +214,7 @@ class AuthenticatorTest(unittest.TestCase): with mock.patch('certbot.display.ops.validated_directory') as m: m.return_value = (display_util.OK, new_webroot,) self.auth.perform([achall]) - self.assertEqual(self.config.webroot_map[achall.domain], new_webroot) + assert self.config.webroot_map[achall.domain] == new_webroot def test_perform_permissions(self): self.auth.prepare() @@ -217,32 +222,31 @@ class AuthenticatorTest(unittest.TestCase): # Remove exec bit from permission check, so that it # matches the file self.auth.perform([self.achall]) - self.assertTrue(filesystem.check_mode(self.validation_path, 0o644)) + assert filesystem.check_mode(self.validation_path, 0o644) # Check permissions of the directories for dirpath, dirnames, _ in os.walk(self.path): for directory in dirnames: full_path = os.path.join(dirpath, directory) - self.assertTrue(filesystem.check_mode(full_path, 0o755)) + assert filesystem.check_mode(full_path, 0o755) - self.assertTrue(filesystem.has_same_ownership(self.validation_path, self.path)) + assert filesystem.has_same_ownership(self.validation_path, self.path) def test_perform_cleanup(self): self.auth.prepare() responses = self.auth.perform([self.achall]) - self.assertEqual(1, len(responses)) - self.assertTrue(os.path.exists(self.validation_path)) + assert 1 == len(responses) + assert os.path.exists(self.validation_path) with open(self.validation_path) as validation_f: validation = validation_f.read() - self.assertTrue( - challenges.KeyAuthorizationChallengeResponse( + assert challenges.KeyAuthorizationChallengeResponse( key_authorization=validation).verify( - self.achall.chall, KEY.public_key())) + self.achall.chall, KEY.public_key()) self.auth.cleanup([self.achall]) - self.assertFalse(os.path.exists(self.validation_path)) - self.assertFalse(os.path.exists(self.root_challenge_path)) - self.assertFalse(os.path.exists(self.partial_root_challenge_path)) + assert not os.path.exists(self.validation_path) + assert not os.path.exists(self.root_challenge_path) + assert not os.path.exists(self.partial_root_challenge_path) def test_perform_cleanup_existing_dirs(self): filesystem.mkdir(self.partial_root_challenge_path) @@ -251,8 +255,8 @@ class AuthenticatorTest(unittest.TestCase): self.auth.cleanup([self.achall]) # Ensure we don't "clean up" directories that previously existed - self.assertFalse(os.path.exists(self.validation_path)) - self.assertFalse(os.path.exists(self.root_challenge_path)) + assert not os.path.exists(self.validation_path) + assert not os.path.exists(self.root_challenge_path) def test_perform_cleanup_multiple_challenges(self): bingo_achall = achallenges.KeyAuthorizationAnnotatedChallenge( @@ -266,11 +270,11 @@ class AuthenticatorTest(unittest.TestCase): self.auth.perform([bingo_achall, self.achall]) self.auth.cleanup([self.achall]) - self.assertFalse(os.path.exists(bingo_validation_path)) - self.assertTrue(os.path.exists(self.root_challenge_path)) + assert not os.path.exists(bingo_validation_path) + assert os.path.exists(self.root_challenge_path) self.auth.cleanup([bingo_achall]) - self.assertFalse(os.path.exists(self.validation_path)) - self.assertFalse(os.path.exists(self.root_challenge_path)) + assert not os.path.exists(self.validation_path) + assert not os.path.exists(self.root_challenge_path) def test_cleanup_leftovers(self): self.auth.prepare() @@ -280,8 +284,8 @@ class AuthenticatorTest(unittest.TestCase): filesystem.mkdir(leftover_path) self.auth.cleanup([self.achall]) - self.assertFalse(os.path.exists(self.validation_path)) - self.assertTrue(os.path.exists(self.root_challenge_path)) + assert not os.path.exists(self.validation_path) + assert os.path.exists(self.root_challenge_path) os.rmdir(leftover_path) @@ -295,8 +299,8 @@ class AuthenticatorTest(unittest.TestCase): mock_rmdir.side_effect = os_error self.auth.cleanup([self.achall]) - self.assertFalse(os.path.exists(self.validation_path)) - self.assertTrue(os.path.exists(self.root_challenge_path)) + assert not os.path.exists(self.validation_path) + assert os.path.exists(self.root_challenge_path) class WebrootActionTest(unittest.TestCase): @@ -316,27 +320,26 @@ class WebrootActionTest(unittest.TestCase): def test_webroot_map_action(self): args = self.parser.parse_args( ["--webroot-map", json.dumps({'thing.com': self.path})]) - self.assertEqual(args.webroot_map["thing.com"], self.path) + assert args.webroot_map["thing.com"] == self.path def test_domain_before_webroot(self): args = self.parser.parse_args( "-d {0} -w {1}".format(self.achall.domain, self.path).split()) config = self._get_config_after_perform(args) - self.assertEqual(config.webroot_map[self.achall.domain], self.path) + assert config.webroot_map[self.achall.domain] == self.path def test_domain_before_webroot_error(self): - self.assertRaises(errors.PluginError, self.parser.parse_args, - "-d foo -w bar -w baz".split()) - self.assertRaises(errors.PluginError, self.parser.parse_args, - "-d foo -w bar -d baz -w qux".split()) + with pytest.raises(errors.PluginError): + self.parser.parse_args("-d foo -w bar -w baz".split()) + with pytest.raises(errors.PluginError): + self.parser.parse_args("-d foo -w bar -d baz -w qux".split()) def test_multiwebroot(self): args = self.parser.parse_args("-w {0} -d {1} -w {2} -d bar".format( self.path, self.achall.domain, tempfile.mkdtemp()).split()) - self.assertEqual(args.webroot_map[self.achall.domain], self.path) + assert args.webroot_map[self.achall.domain] == self.path config = self._get_config_after_perform(args) - self.assertEqual( - config.webroot_map[self.achall.domain], self.path) + assert config.webroot_map[self.achall.domain] == self.path def test_webroot_map_partial_without_perform(self): # This test acknowledges the fact that webroot_map content will be partial if webroot @@ -348,8 +351,8 @@ class WebrootActionTest(unittest.TestCase): other_webroot_path = tempfile.mkdtemp() args = self.parser.parse_args("-w {0} -d {1} -w {2} -d bar".format( self.path, self.achall.domain, other_webroot_path).split()) - self.assertEqual(args.webroot_map, {self.achall.domain: self.path}) - self.assertEqual(args.webroot_path, [self.path, other_webroot_path]) + assert args.webroot_map == {self.achall.domain: self.path} + assert args.webroot_path == [self.path, other_webroot_path] def _get_config_after_perform(self, config): from certbot._internal.plugins.webroot import Authenticator @@ -359,4 +362,4 @@ class WebrootActionTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index ce6065091..0bb915345 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -1,10 +1,14 @@ """Tests for certbot._internal.renewal""" import copy +import sys import unittest from unittest import mock +import pytest + from acme import challenges -from certbot import errors, configuration +from certbot import configuration +from certbot import errors from certbot._internal import storage import certbot.tests.util as test_util @@ -24,7 +28,7 @@ class RenewalTest(test_util.ConfigTestCase): # pylint: disable=protected-access from certbot._internal import renewal renewal._restore_webroot_config(config, renewalparams) - self.assertEqual(config.webroot_path, ['/var/www/']) + assert config.webroot_path == ['/var/www/'] @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_webroot_params_conservation(self, mock_set_by_cli): @@ -39,16 +43,16 @@ class RenewalTest(test_util.ConfigTestCase): 'webroot_path': ['/var/www/test', '/var/www/other'], } renewal._restore_webroot_config(self.config, renewalparams) # pylint: disable=protected-access - self.assertEqual(self.config.webroot_map, {'test.example.com': '/var/www/test'}) - self.assertEqual(self.config.webroot_path, ['/var/www/test', '/var/www/other']) + assert self.config.webroot_map == {'test.example.com': '/var/www/test'} + assert self.config.webroot_path == ['/var/www/test', '/var/www/other'] renewalparams = { 'webroot_map': {}, 'webroot_path': '/var/www/test', } renewal._restore_webroot_config(self.config, renewalparams) # pylint: disable=protected-access - self.assertEqual(self.config.webroot_map, {}) - self.assertEqual(self.config.webroot_path, ['/var/www/test']) + assert self.config.webroot_map == {} + assert self.config.webroot_path == ['/var/www/test'] @mock.patch('certbot._internal.renewal._avoid_reuse_key_conflicts') def test_reuse_key_renewal_params(self, unused_mock_avoid_reuse_conflicts): @@ -118,9 +122,9 @@ class RenewalTest(test_util.ConfigTestCase): with mock.patch('certbot._internal.renewal.hooks.renew_hook'): renewal.renew_cert(self.config, None, le_client, lineage) - self.assertEqual(self.config.elliptic_curve, 'secp256r1') - self.assertEqual(self.config.key_type, 'ecdsa') - self.assertTrue(self.config.reuse_key) + assert self.config.elliptic_curve == 'secp256r1' + assert self.config.key_type == 'ecdsa' + assert self.config.reuse_key # None is passed as the existing key, i.e. the key is not actually being reused. le_client.obtain_certificate.assert_called_with(mock.ANY, None) @@ -148,7 +152,7 @@ class RenewalTest(test_util.ConfigTestCase): from certbot._internal import renewal - with self.assertRaisesRegex(errors.Error, "Unable to change the --key-type"): + with pytest.raises(errors.Error, match="Unable to change the --key-type"): renewal.renew_cert(self.config, None, le_client, lineage) # ... unless --no-reuse-key is set @@ -168,10 +172,10 @@ class RenewalTest(test_util.ConfigTestCase): from certbot._internal import renewal lineage_config = copy.deepcopy(self.config) - renewal_candidate = renewal._reconstitute(lineage_config, rc_path) + renewal_candidate = renewal.reconstitute(lineage_config, rc_path) # This means that manual_public_ip_logging_ok was not modified in the config based on its # value in the renewal conf file - self.assertIsInstance(lineage_config.manual_public_ip_logging_ok, mock.MagicMock) + assert isinstance(lineage_config.manual_public_ip_logging_ok, mock.MagicMock) class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): @@ -185,14 +189,14 @@ class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): def test_allow_subset_of_names_success(self, mock_set_by_cli): mock_set_by_cli.return_value = False self._call(self.config, {'allow_subset_of_names': 'True'}) - self.assertIs(self.config.allow_subset_of_names, True) + assert self.config.allow_subset_of_names is True @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_allow_subset_of_names_failure(self, mock_set_by_cli): mock_set_by_cli.return_value = False renewalparams = {'allow_subset_of_names': 'maybe'} - self.assertRaises( - errors.Error, self._call, self.config, renewalparams) + with pytest.raises(errors.Error): + self._call(self.config, renewalparams) @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_pref_challs_list(self, mock_set_by_cli): @@ -200,7 +204,7 @@ class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): renewalparams = {'pref_challs': 'http-01, dns'.split(',')} self._call(self.config, renewalparams) expected = [challenges.HTTP01.typ, challenges.DNS01.typ] - self.assertEqual(self.config.pref_challs, expected) + assert self.config.pref_challs == expected @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_pref_challs_str(self, mock_set_by_cli): @@ -208,26 +212,27 @@ class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): renewalparams = {'pref_challs': 'dns'} self._call(self.config, renewalparams) expected = [challenges.DNS01.typ] - self.assertEqual(self.config.pref_challs, expected) + assert self.config.pref_challs == expected @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_pref_challs_failure(self, mock_set_by_cli): mock_set_by_cli.return_value = False renewalparams = {'pref_challs': 'finding-a-shrubbery'} - self.assertRaises(errors.Error, self._call, self.config, renewalparams) + with pytest.raises(errors.Error): + self._call(self.config, renewalparams) @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_must_staple_success(self, mock_set_by_cli): mock_set_by_cli.return_value = False self._call(self.config, {'must_staple': 'True'}) - self.assertIs(self.config.must_staple, True) + assert self.config.must_staple is True @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_must_staple_failure(self, mock_set_by_cli): mock_set_by_cli.return_value = False renewalparams = {'must_staple': 'maybe'} - self.assertRaises( - errors.Error, self._call, self.config, renewalparams) + with pytest.raises(errors.Error): + self._call(self.config, renewalparams) @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_ancient_server_renewal_conf(self, mock_set_by_cli): @@ -235,7 +240,7 @@ class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): self.config.server = None mock_set_by_cli.return_value = False self._call(self.config, {'server': constants.V1_URI}) - self.assertEqual(self.config.server, constants.CLI_DEFAULTS['server']) + assert self.config.server == constants.CLI_DEFAULTS['server'] class DescribeResultsTest(unittest.TestCase): @@ -304,4 +309,4 @@ class DescribeResultsTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/renewupdater_test.py b/certbot/tests/renewupdater_test.py index 30a7b0f46..eab07cd34 100644 --- a/certbot/tests/renewupdater_test.py +++ b/certbot/tests/renewupdater_test.py @@ -1,7 +1,10 @@ """Tests for renewal updater interfaces""" +import sys import unittest from unittest import mock +import pytest + from certbot import interfaces from certbot._internal import main from certbot._internal import updater @@ -32,60 +35,60 @@ class RenewUpdaterTest(test_util.ConfigTestCase): mock_geti.return_value = mock_generic_updater with mock.patch('certbot._internal.main._init_le_client'): main.renew_cert(self.config, None, mock.MagicMock()) - self.assertTrue(mock_generic_updater.restart.called) + assert mock_generic_updater.restart.called mock_generic_updater.restart.reset_mock() mock_generic_updater.generic_updates.reset_mock() updater.run_generic_updaters(self.config, mock.MagicMock(), None) - self.assertEqual(mock_generic_updater.generic_updates.call_count, 1) - self.assertIs(mock_generic_updater.restart.called, False) + assert mock_generic_updater.generic_updates.call_count == 1 + assert mock_generic_updater.restart.called is False def test_renew_deployer(self): lineage = mock.MagicMock() mock_deployer = self.renew_deployer updater.run_renewal_deployer(self.config, lineage, mock_deployer) - self.assertTrue(mock_deployer.renew_deploy.called_with(lineage)) + assert mock_deployer.renew_deploy.called_with(lineage) @mock.patch("certbot._internal.updater.logger.debug") def test_updater_skip_dry_run(self, mock_log): self.config.dry_run = True updater.run_generic_updaters(self.config, None, None) - self.assertTrue(mock_log.called) - self.assertEqual(mock_log.call_args[0][0], - "Skipping updaters in dry-run mode.") + assert mock_log.called + assert mock_log.call_args[0][0] == \ + "Skipping updaters in dry-run mode." @mock.patch("certbot._internal.updater.logger.debug") def test_deployer_skip_dry_run(self, mock_log): self.config.dry_run = True updater.run_renewal_deployer(self.config, None, None) - self.assertTrue(mock_log.called) - self.assertEqual(mock_log.call_args[0][0], - "Skipping renewal deployer in dry-run mode.") + assert mock_log.called + assert mock_log.call_args[0][0] == \ + "Skipping renewal deployer in dry-run mode." @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') def test_enhancement_updates(self, mock_geti): mock_geti.return_value = self.mockinstaller updater.run_generic_updaters(self.config, mock.MagicMock(), None) - self.assertTrue(self.mockinstaller.update_autohsts.called) - self.assertEqual(self.mockinstaller.update_autohsts.call_count, 1) + assert self.mockinstaller.update_autohsts.called + assert self.mockinstaller.update_autohsts.call_count == 1 def test_enhancement_deployer(self): updater.run_renewal_deployer(self.config, mock.MagicMock(), self.mockinstaller) - self.assertTrue(self.mockinstaller.deploy_autohsts.called) + assert self.mockinstaller.deploy_autohsts.called @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') def test_enhancement_updates_not_called(self, mock_geti): self.config.disable_renew_updates = True mock_geti.return_value = self.mockinstaller updater.run_generic_updaters(self.config, mock.MagicMock(), None) - self.assertIs(self.mockinstaller.update_autohsts.called, False) + assert self.mockinstaller.update_autohsts.called is False def test_enhancement_deployer_not_called(self): self.config.disable_renew_updates = True updater.run_renewal_deployer(self.config, mock.MagicMock(), self.mockinstaller) - self.assertIs(self.mockinstaller.deploy_autohsts.called, False) + assert self.mockinstaller.deploy_autohsts.called is False @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') def test_enhancement_no_updater(self, mock_geti): @@ -101,7 +104,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): mock_geti.return_value = self.mockinstaller with mock.patch("certbot.plugins.enhancements._INDEX", FAKEINDEX): updater.run_generic_updaters(self.config, mock.MagicMock(), None) - self.assertIs(self.mockinstaller.update_autohsts.called, False) + assert self.mockinstaller.update_autohsts.called is False def test_enhancement_no_deployer(self): FAKEINDEX = [ @@ -116,8 +119,8 @@ class RenewUpdaterTest(test_util.ConfigTestCase): with mock.patch("certbot.plugins.enhancements._INDEX", FAKEINDEX): updater.run_renewal_deployer(self.config, mock.MagicMock(), self.mockinstaller) - self.assertIs(self.mockinstaller.deploy_autohsts.called, False) + assert self.mockinstaller.deploy_autohsts.called is False if __name__ == '__main__': - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index 5124c7d9f..dded4d813 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -2,10 +2,13 @@ import csv import logging import shutil +import sys import tempfile import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import os from certbot.tests import util as test_util @@ -44,29 +47,27 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): no_change = os.path.join(self.reverter.config.backup_dir, path, "CHANGES_SINCE") with open(no_change, "r") as f: x = f.read() - self.assertIn("No changes", x) + assert "No changes" in x def test_basic_add_to_temp_checkpoint(self): # These shouldn't conflict even though they are both named config.txt self.reverter.add_to_temp_checkpoint(self.sets[0], "save1") self.reverter.add_to_temp_checkpoint(self.sets[1], "save2") - self.assertTrue(os.path.isdir(self.config.temp_checkpoint_dir)) - self.assertEqual(get_save_notes( - self.config.temp_checkpoint_dir), "save1save2") - self.assertFalse(os.path.isfile( - os.path.join(self.config.temp_checkpoint_dir, "NEW_FILES"))) + assert os.path.isdir(self.config.temp_checkpoint_dir) + assert get_save_notes( + self.config.temp_checkpoint_dir) == "save1save2" + assert not os.path.isfile( + os.path.join(self.config.temp_checkpoint_dir, "NEW_FILES")) - self.assertEqual( - get_filepaths(self.config.temp_checkpoint_dir), - "{0}\n{1}\n".format(self.config1, self.config2)) + assert get_filepaths(self.config.temp_checkpoint_dir) == \ + "{0}\n{1}\n".format(self.config1, self.config2) def test_add_to_checkpoint_copy_failure(self): with mock.patch("certbot.reverter.shutil.copy2") as mock_copy2: mock_copy2.side_effect = IOError("bad copy") - self.assertRaises( - errors.ReverterError, self.reverter.add_to_checkpoint, - self.sets[0], "save1") + with pytest.raises(errors.ReverterError): + self.reverter.add_to_checkpoint(self.sets[0], "save1") def test_checkpoint_conflict(self): """Make sure that checkpoint errors are thrown appropriately.""" @@ -78,14 +79,14 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): # This shouldn't throw an error self.reverter.add_to_temp_checkpoint(self.sets[0], "save2") # Raise error - self.assertRaises(errors.ReverterError, self.reverter.add_to_checkpoint, - self.sets[2], "save3") + with pytest.raises(errors.ReverterError): + self.reverter.add_to_checkpoint(self.sets[2], "save3") # Should not cause an error self.reverter.add_to_checkpoint(self.sets[1], "save4") # Check to make sure new files are also checked... - self.assertRaises(errors.ReverterError, self.reverter.add_to_checkpoint, - {config3}, "invalid save") + with pytest.raises(errors.ReverterError): + self.reverter.add_to_checkpoint({config3}, "invalid save") def test_multiple_saves_and_temp_revert(self): self.reverter.add_to_temp_checkpoint(self.sets[0], "save1") @@ -94,7 +95,7 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): update_file(self.config1, "new directive change that we won't keep") self.reverter.revert_temporary_config() - self.assertEqual(read_in(self.config1), "directive-dir1") + assert read_in(self.config1) == "directive-dir1" def test_multiple_registration_fail_and_revert(self): @@ -111,10 +112,10 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): # Simulate Certbot crash... recovery routine is run self.reverter.recovery_routine() - self.assertFalse(os.path.isfile(self.config1)) - self.assertFalse(os.path.isfile(self.config2)) - self.assertFalse(os.path.isfile(config3)) - self.assertFalse(os.path.isfile(config4)) + assert not os.path.isfile(self.config1) + assert not os.path.isfile(self.config2) + assert not os.path.isfile(config3) + assert not os.path.isfile(config4) def test_multiple_registration_same_file(self): self.reverter.register_file_creation(True, self.config1) @@ -124,21 +125,19 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): files = get_new_files(self.config.temp_checkpoint_dir) - self.assertEqual(len(files), 1) + assert len(files) == 1 def test_register_file_creation_write_error(self): m_open = mock.mock_open() with mock.patch("certbot.reverter.open", m_open, create=True): m_open.side_effect = OSError("bad open") - self.assertRaises( - errors.ReverterError, self.reverter.register_file_creation, - True, self.config1) + with pytest.raises(errors.ReverterError): + self.reverter.register_file_creation(True, self.config1) def test_bad_registration(self): # Made this mistake and want to make sure it doesn't happen again... - self.assertRaises( - errors.ReverterError, self.reverter.register_file_creation, - "filepath") + with pytest.raises(errors.ReverterError): + self.reverter.register_file_creation("filepath") def test_register_undo_command(self): coms = [ @@ -152,15 +151,14 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): act_coms = get_undo_commands(self.config.temp_checkpoint_dir) for a_com, com in zip(act_coms, coms): - self.assertEqual(a_com, com) + assert a_com == com def test_bad_register_undo_command(self): m_open = mock.mock_open() with mock.patch("certbot.reverter.open", m_open, create=True): m_open.side_effect = OSError("bad open") - self.assertRaises( - errors.ReverterError, self.reverter.register_undo_command, - True, ["command"]) + with pytest.raises(errors.ReverterError): + self.reverter.register_undo_command(True, ["command"]) @mock.patch("certbot.util.run_script") def test_run_undo_commands(self, mock_run): @@ -174,7 +172,7 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): self.reverter.revert_temporary_config() - self.assertEqual(mock_run.call_count, 2) + assert mock_run.call_count == 2 def test_recovery_routine_in_progress_failure(self): self.reverter.add_to_checkpoint(self.sets[0], "perm save") @@ -182,7 +180,8 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): # pylint: disable=protected-access self.reverter._recover_checkpoint = mock.MagicMock( side_effect=errors.ReverterError) - self.assertRaises(errors.ReverterError, self.reverter.recovery_routine) + with pytest.raises(errors.ReverterError): + self.reverter.recovery_routine() def test_recover_checkpoint_revert_temp_failures(self): @@ -194,8 +193,8 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): self.reverter.add_to_temp_checkpoint(self.sets[0], "config1 save") - self.assertRaises( - errors.ReverterError, self.reverter.revert_temporary_config) + with pytest.raises(errors.ReverterError): + self.reverter.revert_temporary_config() def test_recover_checkpoint_rollback_failure(self): mock_recover = mock.MagicMock( @@ -206,38 +205,38 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): self.reverter.add_to_checkpoint(self.sets[0], "config1 save") self.reverter.finalize_checkpoint("Title") - self.assertRaises( - errors.ReverterError, self.reverter.rollback_checkpoints, 1) + with pytest.raises(errors.ReverterError): + self.reverter.rollback_checkpoints(1) def test_recover_checkpoint_copy_failure(self): self.reverter.add_to_temp_checkpoint(self.sets[0], "save1") with mock.patch("certbot.reverter.shutil.copy2") as mock_copy2: mock_copy2.side_effect = OSError("bad copy") - self.assertRaises( - errors.ReverterError, self.reverter.revert_temporary_config) + with pytest.raises(errors.ReverterError): + self.reverter.revert_temporary_config() def test_recover_checkpoint_rm_failure(self): self.reverter.add_to_temp_checkpoint(self.sets[0], "temp save") with mock.patch("certbot.reverter.shutil.rmtree") as mock_rmtree: mock_rmtree.side_effect = OSError("Cannot remove tree") - self.assertRaises( - errors.ReverterError, self.reverter.revert_temporary_config) + with pytest.raises(errors.ReverterError): + self.reverter.revert_temporary_config() @mock.patch("certbot.reverter.logger.warning") def test_recover_checkpoint_missing_new_files(self, mock_warn): self.reverter.register_file_creation( True, os.path.join(self.dir1, "missing_file.txt")) self.reverter.revert_temporary_config() - self.assertEqual(mock_warn.call_count, 1) + assert mock_warn.call_count == 1 @mock.patch("certbot.reverter.os.remove") def test_recover_checkpoint_remove_failure(self, mock_remove): self.reverter.register_file_creation(True, self.config1) mock_remove.side_effect = OSError("Can't remove") - self.assertRaises( - errors.ReverterError, self.reverter.revert_temporary_config) + with pytest.raises(errors.ReverterError): + self.reverter.revert_temporary_config() def test_recovery_routine_temp_and_perm(self): # Register a new perm checkpoint file @@ -265,12 +264,12 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): # Now Run tests # These were new files.. they should be removed - self.assertFalse(os.path.isfile(config3)) - self.assertFalse(os.path.isfile(config4)) + assert not os.path.isfile(config3) + assert not os.path.isfile(config4) # Check to make sure everything got rolled back appropriately - self.assertEqual(read_in(self.config1), "directive-dir1") - self.assertEqual(read_in(self.config2), "directive-dir2") + assert read_in(self.config1) == "directive-dir1" + assert read_in(self.config2) == "directive-dir2" class TestFullCheckpointsReverter(test_util.ConfigTestCase): @@ -278,6 +277,7 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): def setUp(self): super().setUp() from certbot.reverter import Reverter + # Disable spurious errors... logging.disable(logging.CRITICAL) @@ -294,42 +294,41 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): logging.disable(logging.NOTSET) def test_rollback_improper_inputs(self): - self.assertRaises( - errors.ReverterError, self.reverter.rollback_checkpoints, "-1") - self.assertRaises( - errors.ReverterError, self.reverter.rollback_checkpoints, -1000) - self.assertRaises( - errors.ReverterError, self.reverter.rollback_checkpoints, "one") + with pytest.raises(errors.ReverterError): + self.reverter.rollback_checkpoints("-1") + with pytest.raises(errors.ReverterError): + self.reverter.rollback_checkpoints(-1000) + with pytest.raises(errors.ReverterError): + self.reverter.rollback_checkpoints("one") def test_rollback_finalize_checkpoint_valid_inputs(self): config3 = self._setup_three_checkpoints() # Check resulting backup directory - self.assertEqual(len(os.listdir(self.config.backup_dir)), 3) + assert len(os.listdir(self.config.backup_dir)) == 3 # Check rollbacks # First rollback self.reverter.rollback_checkpoints(1) - self.assertEqual(read_in(self.config1), "update config1") - self.assertEqual(read_in(self.config2), "update config2") + assert read_in(self.config1) == "update config1" + assert read_in(self.config2) == "update config2" # config3 was not included in checkpoint - self.assertEqual(read_in(config3), "Final form config3") + assert read_in(config3) == "Final form config3" # Second rollback self.reverter.rollback_checkpoints(1) - self.assertEqual(read_in(self.config1), "update config1") - self.assertEqual(read_in(self.config2), "directive-dir2") - self.assertFalse(os.path.isfile(config3)) + assert read_in(self.config1) == "update config1" + assert read_in(self.config2) == "directive-dir2" + assert not os.path.isfile(config3) # One dir left... check title all_dirs = os.listdir(self.config.backup_dir) - self.assertEqual(len(all_dirs), 1) - self.assertIn( - "First Checkpoint", get_save_notes( - os.path.join(self.config.backup_dir, all_dirs[0]))) + assert len(all_dirs) == 1 + assert "First Checkpoint" in get_save_notes( + os.path.join(self.config.backup_dir, all_dirs[0])) # Final rollback self.reverter.rollback_checkpoints(1) - self.assertEqual(read_in(self.config1), "directive-dir1") + assert read_in(self.config1) == "directive-dir1" def test_finalize_checkpoint_no_in_progress(self): # No need to warn for this... just make sure there are no errors. @@ -340,8 +339,8 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.reverter.add_to_checkpoint(self.sets[0], "perm save") mock_move.side_effect = OSError("cannot move") - self.assertRaises( - errors.ReverterError, self.reverter.finalize_checkpoint, "Title") + with pytest.raises(errors.ReverterError): + self.reverter.finalize_checkpoint("Title") @mock.patch("certbot.reverter.filesystem.replace") def test_finalize_checkpoint_no_rename_directory(self, mock_replace): @@ -349,28 +348,28 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.reverter.add_to_checkpoint(self.sets[0], "perm save") mock_replace.side_effect = OSError - self.assertRaises( - errors.ReverterError, self.reverter.finalize_checkpoint, "Title") + with pytest.raises(errors.ReverterError): + self.reverter.finalize_checkpoint("Title") @mock.patch("certbot.reverter.logger") def test_rollback_too_many(self, mock_logger): # Test no exist warning... self.reverter.rollback_checkpoints(1) - self.assertEqual(mock_logger.warning.call_count, 1) + assert mock_logger.warning.call_count == 1 # Test Generic warning self._setup_three_checkpoints() mock_logger.warning.call_count = 0 self.reverter.rollback_checkpoints(4) - self.assertEqual(mock_logger.warning.call_count, 1) + assert mock_logger.warning.call_count == 1 def test_multi_rollback(self): config3 = self._setup_three_checkpoints() self.reverter.rollback_checkpoints(3) - self.assertEqual(read_in(self.config1), "directive-dir1") - self.assertEqual(read_in(self.config2), "directive-dir2") - self.assertFalse(os.path.isfile(config3)) + assert read_in(self.config1) == "directive-dir1" + assert read_in(self.config2) == "directive-dir2" + assert not os.path.isfile(config3) def _setup_three_checkpoints(self): """Generate some finalized checkpoints.""" @@ -453,4 +452,4 @@ def update_file(filename, string): if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 3a1f2b7b4..34ea95246 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -3,10 +3,12 @@ import datetime import shutil import stat +import sys import unittest from unittest import mock import configobj +import pytest import pytz import certbot @@ -49,8 +51,7 @@ class RelevantValuesTest(unittest.TestCase): mock_option_was_set.return_value = True self.values["certbot_foo:bar_baz"] = 42 - self.assertEqual( - self._call(self.values.copy()), self.values) + assert self._call(self.values.copy()) == self.values @mock.patch("certbot._internal.cli.option_was_set") def test_option_set(self, mock_option_was_set): @@ -62,7 +63,7 @@ class RelevantValuesTest(unittest.TestCase): expected_relevant_values = self.values.copy() self.values["hello"] = "there" - self.assertEqual(self._call(self.values), expected_relevant_values) + assert self._call(self.values) == expected_relevant_values @mock.patch("certbot._internal.cli.option_was_set") def test_option_unset(self, mock_option_was_set): @@ -71,18 +72,18 @@ class RelevantValuesTest(unittest.TestCase): expected_relevant_values = self.values.copy() self.values["rsa_key_size"] = 2048 - self.assertEqual(self._call(self.values), expected_relevant_values) + assert self._call(self.values) == expected_relevant_values @mock.patch("certbot._internal.cli.set_by_cli") def test_deprecated_item(self, unused_mock_set_by_cli): # deprecated items should never be relevant to store expected_relevant_values = self.values.copy() self.values["manual_public_ip_logging_ok"] = None - self.assertEqual(self._call(self.values), expected_relevant_values) + assert self._call(self.values) == expected_relevant_values self.values["manual_public_ip_logging_ok"] = True - self.assertEqual(self._call(self.values), expected_relevant_values) + assert self._call(self.values) == expected_relevant_values self.values["manual_public_ip_logging_ok"] = False - self.assertEqual(self._call(self.values), expected_relevant_values) + assert self._call(self.values) == expected_relevant_values class BaseRenewableCertTest(test_util.ConfigTestCase): @@ -153,11 +154,10 @@ class RenewableCertTests(BaseRenewableCertTest): """Tests for certbot._internal.storage.""" def test_initialization(self): - self.assertEqual(self.test_rc.lineagename, "example.org") + assert self.test_rc.lineagename == "example.org" for kind in ALL_FOUR: - self.assertEqual( - getattr(self.test_rc, kind), os.path.join( - self.config.config_dir, "live", "example.org", kind + ".pem")) + assert getattr(self.test_rc, kind) == os.path.join( + self.config.config_dir, "live", "example.org", kind + ".pem") def test_renewal_bad_config(self): """Test that the RenewableCert constructor will complain if @@ -168,11 +168,11 @@ class RenewableCertTests(BaseRenewableCertTest): broken = os.path.join(self.config.config_dir, "broken.conf") with open(broken, "w") as f: f.write("[No closing bracket for you!") - self.assertRaises(errors.CertStorageError, storage.RenewableCert, - broken, self.config) + with pytest.raises(errors.CertStorageError): + storage.RenewableCert(broken, self.config) os.unlink(broken) - self.assertRaises(errors.CertStorageError, storage.RenewableCert, - "fun", self.config) + with pytest.raises(errors.CertStorageError): + storage.RenewableCert("fun", self.config) def test_renewal_incomplete_config(self): """Test that the RenewableCert constructor will complain if @@ -185,18 +185,18 @@ class RenewableCertTests(BaseRenewableCertTest): config["fullchain"] = "imaginary_fullchain.pem" config.filename = os.path.join(self.config.config_dir, "imaginary_config.conf") config.write() - self.assertRaises(errors.CertStorageError, storage.RenewableCert, - config.filename, self.config) + with pytest.raises(errors.CertStorageError): + storage.RenewableCert(config.filename, self.config) def test_no_renewal_version(self): from certbot._internal import storage self._write_out_ex_kinds() - self.assertNotIn("version", self.config_file) + assert "version" not in self.config_file with mock.patch("certbot._internal.storage.logger") as mock_logger: storage.RenewableCert(self.config_file.filename, self.config) - self.assertIs(mock_logger.warning.called, False) + assert mock_logger.warning.called is False def test_renewal_newer_version(self): from certbot._internal import storage @@ -207,68 +207,68 @@ class RenewableCertTests(BaseRenewableCertTest): with mock.patch("certbot._internal.storage.logger") as mock_logger: storage.RenewableCert(self.config_file.filename, self.config) - self.assertTrue(mock_logger.info.called) - self.assertIn("version", mock_logger.info.call_args[0][0]) + assert mock_logger.info.called + assert "version" in mock_logger.info.call_args[0][0] def test_consistent(self): # pylint: disable=protected-access oldcert = self.test_rc.cert self.test_rc.cert = "relative/path" # Absolute path for item requirement - self.assertFalse(self.test_rc._consistent()) + assert not self.test_rc._consistent() self.test_rc.cert = oldcert # Items must exist requirement - self.assertFalse(self.test_rc._consistent()) + assert not self.test_rc._consistent() # Items must be symlinks requirements fill_with_sample_data(self.test_rc) - self.assertFalse(self.test_rc._consistent()) + assert not self.test_rc._consistent() unlink_all(self.test_rc) # Items must point to desired place if they are relative for kind in ALL_FOUR: os.symlink(os.path.join("..", kind + "17.pem"), getattr(self.test_rc, kind)) - self.assertFalse(self.test_rc._consistent()) + assert not self.test_rc._consistent() unlink_all(self.test_rc) # Items must point to desired place if they are absolute for kind in ALL_FOUR: os.symlink(os.path.join(self.config.config_dir, kind + "17.pem"), getattr(self.test_rc, kind)) - self.assertFalse(self.test_rc._consistent()) + assert not self.test_rc._consistent() unlink_all(self.test_rc) # Items must point to things that exist for kind in ALL_FOUR: os.symlink(os.path.join("..", "..", "archive", "example.org", kind + "17.pem"), getattr(self.test_rc, kind)) - self.assertFalse(self.test_rc._consistent()) + assert not self.test_rc._consistent() # This version should work fill_with_sample_data(self.test_rc) - self.assertTrue(self.test_rc._consistent()) + assert self.test_rc._consistent() # Items must point to things that follow the naming convention os.unlink(self.test_rc.fullchain) os.symlink(os.path.join("..", "..", "archive", "example.org", "fullchain_17.pem"), self.test_rc.fullchain) with open(self.test_rc.fullchain, "w") as f: f.write("wrongly-named fullchain") - self.assertFalse(self.test_rc._consistent()) + assert not self.test_rc._consistent() def test_current_target(self): # Relative path logic self._write_out_kind("cert", 17) - self.assertTrue(os.path.samefile(self.test_rc.current_target("cert"), + assert os.path.samefile(self.test_rc.current_target("cert"), os.path.join(self.config.config_dir, "archive", "example.org", - "cert17.pem"))) + "cert17.pem")) # Absolute path logic os.unlink(self.test_rc.cert) os.symlink(os.path.join(self.config.config_dir, "archive", "example.org", "cert17.pem"), self.test_rc.cert) with open(self.test_rc.cert, "w") as f: f.write("cert") - self.assertTrue(os.path.samefile(self.test_rc.current_target("cert"), + assert os.path.samefile(self.test_rc.current_target("cert"), os.path.join(self.config.config_dir, "archive", "example.org", - "cert17.pem"))) + "cert17.pem")) def test_current_version(self): for ver in (1, 5, 10, 20): @@ -276,33 +276,33 @@ class RenewableCertTests(BaseRenewableCertTest): os.unlink(self.test_rc.cert) os.symlink(os.path.join("..", "..", "archive", "example.org", "cert10.pem"), self.test_rc.cert) - self.assertEqual(self.test_rc.current_version("cert"), 10) + assert self.test_rc.current_version("cert") == 10 def test_no_current_version(self): - self.assertIsNone(self.test_rc.current_version("cert")) + assert self.test_rc.current_version("cert") is None def test_latest_and_next_versions(self): for ver in range(1, 6): for kind in ALL_FOUR: self._write_out_kind(kind, ver) - self.assertEqual(self.test_rc.latest_common_version(), 5) - self.assertEqual(self.test_rc.next_free_version(), 6) + assert self.test_rc.latest_common_version() == 5 + assert self.test_rc.next_free_version() == 6 # Having one kind of file of a later version doesn't change the # result self._write_out_kind("privkey", 7) - self.assertEqual(self.test_rc.latest_common_version(), 5) + assert self.test_rc.latest_common_version() == 5 # ... although it does change the next free version - self.assertEqual(self.test_rc.next_free_version(), 8) + assert self.test_rc.next_free_version() == 8 # Nor does having three out of four change the result self._write_out_kind("cert", 7) self._write_out_kind("fullchain", 7) - self.assertEqual(self.test_rc.latest_common_version(), 5) + assert self.test_rc.latest_common_version() == 5 # If we have everything from a much later version, it does change # the result for kind in ALL_FOUR: self._write_out_kind(kind, 17) - self.assertEqual(self.test_rc.latest_common_version(), 17) - self.assertEqual(self.test_rc.next_free_version(), 18) + assert self.test_rc.latest_common_version() == 17 + assert self.test_rc.next_free_version() == 18 @mock.patch("certbot._internal.storage.logger") def test_ensure_deployed(self, mock_logger): @@ -311,54 +311,54 @@ class RenewableCertTests(BaseRenewableCertTest): self.test_rc.latest_common_version = mock.Mock() mock_has_pending.return_value = False - self.assertIs(self.test_rc.ensure_deployed(), True) - self.assertEqual(mock_update.call_count, 0) - self.assertEqual(mock_logger.warning.call_count, 0) + assert self.test_rc.ensure_deployed() is True + assert mock_update.call_count == 0 + assert mock_logger.warning.call_count == 0 mock_has_pending.return_value = True - self.assertIs(self.test_rc.ensure_deployed(), False) - self.assertEqual(mock_update.call_count, 1) - self.assertEqual(mock_logger.warning.call_count, 1) + assert self.test_rc.ensure_deployed() is False + assert mock_update.call_count == 1 + assert mock_logger.warning.call_count == 1 def test_update_link_to(self): for ver in range(1, 6): for kind in ALL_FOUR: self._write_out_kind(kind, ver) - self.assertEqual(ver, self.test_rc.current_version(kind)) + assert ver == self.test_rc.current_version(kind) # pylint: disable=protected-access self.test_rc._update_link_to("cert", 3) self.test_rc._update_link_to("privkey", 2) - self.assertEqual(3, self.test_rc.current_version("cert")) - self.assertEqual(2, self.test_rc.current_version("privkey")) - self.assertEqual(5, self.test_rc.current_version("chain")) - self.assertEqual(5, self.test_rc.current_version("fullchain")) + assert 3 == self.test_rc.current_version("cert") + assert 2 == self.test_rc.current_version("privkey") + assert 5 == self.test_rc.current_version("chain") + assert 5 == self.test_rc.current_version("fullchain") # Currently we are allowed to update to a version that doesn't exist self.test_rc._update_link_to("chain", 3000) # However, current_version doesn't allow querying the resulting # version (because it's a broken link). - self.assertEqual(os.path.basename(filesystem.readlink(self.test_rc.chain)), - "chain3000.pem") + assert os.path.basename(filesystem.readlink(self.test_rc.chain)) == \ + "chain3000.pem" def test_version(self): self._write_out_kind("cert", 12) # TODO: We should probably test that the directory is still the # same, but it's tricky because we can get an absolute # path out when we put a relative path in. - self.assertEqual("cert8.pem", - os.path.basename(self.test_rc.version("cert", 8))) + assert "cert8.pem" == \ + os.path.basename(self.test_rc.version("cert", 8)) def test_update_all_links_to_success(self): for ver in range(1, 6): for kind in ALL_FOUR: self._write_out_kind(kind, ver) - self.assertEqual(ver, self.test_rc.current_version(kind)) - self.assertEqual(self.test_rc.latest_common_version(), 5) + assert ver == self.test_rc.current_version(kind) + assert self.test_rc.latest_common_version() == 5 for ver in range(1, 6): self.test_rc.update_all_links_to(ver) for kind in ALL_FOUR: - self.assertEqual(ver, self.test_rc.current_version(kind)) - self.assertEqual(self.test_rc.latest_common_version(), 5) + assert ver == self.test_rc.current_version(kind) + assert self.test_rc.latest_common_version() == 5 def test_update_all_links_to_partial_failure(self): def unlink_or_raise(path, real_unlink=os.unlink): @@ -371,10 +371,11 @@ class RenewableCertTests(BaseRenewableCertTest): self._write_out_ex_kinds() with mock.patch("certbot._internal.storage.os.unlink") as mock_unlink: mock_unlink.side_effect = unlink_or_raise - self.assertRaises(ValueError, self.test_rc.update_all_links_to, 12) + with pytest.raises(ValueError): + self.test_rc.update_all_links_to(12) for kind in ALL_FOUR: - self.assertEqual(self.test_rc.current_version(kind), 12) + assert self.test_rc.current_version(kind) == 12 def test_update_all_links_to_full_failure(self): def unlink_or_raise(path, real_unlink=os.unlink): @@ -386,35 +387,37 @@ class RenewableCertTests(BaseRenewableCertTest): self._write_out_ex_kinds() with mock.patch("certbot._internal.storage.os.unlink") as mock_unlink: mock_unlink.side_effect = unlink_or_raise - self.assertRaises(ValueError, self.test_rc.update_all_links_to, 12) + with pytest.raises(ValueError): + self.test_rc.update_all_links_to(12) for kind in ALL_FOUR: - self.assertEqual(self.test_rc.current_version(kind), 11) + assert self.test_rc.current_version(kind) == 11 def test_has_pending_deployment(self): for ver in range(1, 6): for kind in ALL_FOUR: self._write_out_kind(kind, ver) - self.assertEqual(ver, self.test_rc.current_version(kind)) + assert ver == self.test_rc.current_version(kind) for ver in range(1, 6): self.test_rc.update_all_links_to(ver) for kind in ALL_FOUR: - self.assertEqual(ver, self.test_rc.current_version(kind)) + assert ver == self.test_rc.current_version(kind) if ver < 5: - self.assertTrue(self.test_rc.has_pending_deployment()) + assert self.test_rc.has_pending_deployment() else: - self.assertFalse(self.test_rc.has_pending_deployment()) + assert not self.test_rc.has_pending_deployment() def test_names(self): # Trying the current version self._write_out_kind("cert", 12, test_util.load_vector("cert-san_512.pem")) - self.assertEqual(self.test_rc.names(), - ["example.com", "www.example.com"]) + assert self.test_rc.names() == \ + ["example.com", "www.example.com"] # Trying missing cert os.unlink(self.test_rc.cert) - self.assertRaises(errors.CertStorageError, self.test_rc.names) + with pytest.raises(errors.CertStorageError): + self.test_rc.names() @mock.patch("certbot._internal.storage.cli") @mock.patch("certbot._internal.storage.datetime") @@ -459,16 +462,16 @@ class RenewableCertTests(BaseRenewableCertTest): sometime = datetime.datetime.utcfromtimestamp(current_time) mock_datetime.datetime.utcnow.return_value = sometime self.test_rc.configuration["renew_before_expiry"] = interval - self.assertEqual(self.test_rc.should_autorenew(), result) + assert self.test_rc.should_autorenew() == result def test_autorenewal_is_enabled(self): self.test_rc.configuration["renewalparams"] = {} - self.assertTrue(self.test_rc.autorenewal_is_enabled()) + assert self.test_rc.autorenewal_is_enabled() self.test_rc.configuration["renewalparams"]["autorenew"] = "True" - self.assertTrue(self.test_rc.autorenewal_is_enabled()) + assert self.test_rc.autorenewal_is_enabled() self.test_rc.configuration["renewalparams"]["autorenew"] = "False" - self.assertFalse(self.test_rc.autorenewal_is_enabled()) + assert not self.test_rc.autorenewal_is_enabled() @mock.patch("certbot._internal.storage.cli") @mock.patch("certbot._internal.storage.RenewableCert.ocsp_revoked") @@ -478,13 +481,13 @@ class RenewableCertTests(BaseRenewableCertTest): mock_cli.set_by_cli.return_value = False # Autorenewal turned off self.test_rc.configuration["renewalparams"] = {"autorenew": "False"} - self.assertFalse(self.test_rc.should_autorenew()) + assert not self.test_rc.should_autorenew() self.test_rc.configuration["renewalparams"]["autorenew"] = "True" for kind in ALL_FOUR: self._write_out_kind(kind, 12) # Mandatory renewal on the basis of OCSP revocation mock_ocsp.return_value = True - self.assertTrue(self.test_rc.should_autorenew()) + assert self.test_rc.should_autorenew() mock_ocsp.return_value = False @mock.patch("certbot._internal.storage.relevant_values") @@ -497,58 +500,53 @@ class RenewableCertTests(BaseRenewableCertTest): for kind in ALL_FOUR: self._write_out_kind(kind, ver) self.test_rc.update_all_links_to(3) - self.assertEqual( - 6, self.test_rc.save_successor(3, b'new cert', None, - b'new chain', self.config)) + assert 6 == self.test_rc.save_successor(3, b'new cert', None, + b'new chain', self.config) with open(self.test_rc.version("cert", 6)) as f: - self.assertEqual(f.read(), "new cert") + assert f.read() == "new cert" with open(self.test_rc.version("chain", 6)) as f: - self.assertEqual(f.read(), "new chain") + assert f.read() == "new chain" with open(self.test_rc.version("fullchain", 6)) as f: - self.assertEqual(f.read(), "new cert" + "new chain") + assert f.read() == "new cert" + "new chain" # version 6 of the key should be a link back to version 3 - self.assertFalse(os.path.islink(self.test_rc.version("privkey", 3))) - self.assertTrue(os.path.islink(self.test_rc.version("privkey", 6))) + assert not os.path.islink(self.test_rc.version("privkey", 3)) + assert os.path.islink(self.test_rc.version("privkey", 6)) # Let's try two more updates - self.assertEqual( - 7, self.test_rc.save_successor(6, b'again', None, - b'newer chain', self.config)) - self.assertEqual( - 8, self.test_rc.save_successor(7, b'hello', None, - b'other chain', self.config)) + assert 7 == self.test_rc.save_successor(6, b'again', None, + b'newer chain', self.config) + assert 8 == self.test_rc.save_successor(7, b'hello', None, + b'other chain', self.config) # All of the subsequent versions should link directly to the original # privkey. for i in (6, 7, 8): - self.assertTrue(os.path.islink(self.test_rc.version("privkey", i))) - self.assertEqual("privkey3.pem", os.path.basename(filesystem.readlink( - self.test_rc.version("privkey", i)))) + assert os.path.islink(self.test_rc.version("privkey", i)) + assert "privkey3.pem" == os.path.basename(filesystem.readlink( + self.test_rc.version("privkey", i))) for kind in ALL_FOUR: - self.assertEqual(self.test_rc.available_versions(kind), list(range(1, 9))) - self.assertEqual(self.test_rc.current_version(kind), 3) + assert self.test_rc.available_versions(kind) == list(range(1, 9)) + assert self.test_rc.current_version(kind) == 3 # Test updating from latest version rather than old version self.test_rc.update_all_links_to(8) - self.assertEqual( - 9, self.test_rc.save_successor(8, b'last', None, - b'attempt', self.config)) + assert 9 == self.test_rc.save_successor(8, b'last', None, + b'attempt', self.config) for kind in ALL_FOUR: - self.assertEqual(self.test_rc.available_versions(kind), - list(range(1, 10))) - self.assertEqual(self.test_rc.current_version(kind), 8) + assert self.test_rc.available_versions(kind) == \ + list(range(1, 10)) + assert self.test_rc.current_version(kind) == 8 with open(self.test_rc.version("fullchain", 9)) as f: - self.assertEqual(f.read(), "last" + "attempt") + assert f.read() == "last" + "attempt" temp_config_file = os.path.join(self.config.renewal_configs_dir, self.test_rc.lineagename) + ".conf.new" with open(temp_config_file, "w") as f: f.write("We previously crashed while writing me :(") # Test updating when providing a new privkey. The key should # be saved in a new file rather than creating a new symlink. - self.assertEqual( - 10, self.test_rc.save_successor(9, b'with', b'a', - b'key', self.config)) - self.assertTrue(os.path.exists(self.test_rc.version("privkey", 10))) - self.assertFalse(os.path.islink(self.test_rc.version("privkey", 10))) - self.assertFalse(os.path.exists(temp_config_file)) + assert 10 == self.test_rc.save_successor(9, b'with', b'a', + b'key', self.config) + assert os.path.exists(self.test_rc.version("privkey", 10)) + assert not os.path.islink(self.test_rc.version("privkey", 10)) + assert not os.path.exists(temp_config_file) @test_util.skip_on_windows('Group/everybody permissions are not maintained on Windows.') @mock.patch("certbot._internal.storage.relevant_values") @@ -559,18 +557,18 @@ class RenewableCertTests(BaseRenewableCertTest): for kind in ALL_FOUR: self._write_out_kind(kind, 1) self.test_rc.update_all_links_to(1) - self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 1), 0o600)) + assert filesystem.check_mode(self.test_rc.version("privkey", 1), 0o600) filesystem.chmod(self.test_rc.version("privkey", 1), 0o444) # If no new key, permissions should be the same (we didn't write any keys) self.test_rc.save_successor(1, b"newcert", None, b"new chain", self.config) - self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 2), 0o444)) + assert filesystem.check_mode(self.test_rc.version("privkey", 2), 0o444) # If new key, permissions should be kept as 644 self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config) - self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 3), 0o644)) + assert filesystem.check_mode(self.test_rc.version("privkey", 3), 0o644) # If permissions reverted, next renewal will also revert permissions of new key filesystem.chmod(self.test_rc.version("privkey", 3), 0o400) self.test_rc.save_successor(3, b"newcert", b"new_privkey", b"new chain", self.config) - self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 4), 0o600)) + assert filesystem.check_mode(self.test_rc.version("privkey", 4), 0o600) @mock.patch("certbot._internal.storage.relevant_values") @mock.patch("certbot._internal.storage.filesystem.copy_ownership_and_apply_mode") @@ -582,9 +580,9 @@ class RenewableCertTests(BaseRenewableCertTest): self._write_out_kind(kind, 1) self.test_rc.update_all_links_to(1) self.test_rc.save_successor(1, b"newcert", None, b"new chain", self.config) - self.assertIs(mock_ownership.called, False) + assert mock_ownership.called is False self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config) - self.assertTrue(mock_ownership.called) + assert mock_ownership.called @mock.patch("certbot._internal.storage.relevant_values") def test_new_lineage(self, mock_rv): @@ -599,41 +597,40 @@ class RenewableCertTests(BaseRenewableCertTest): # This consistency check tests most relevant properties about the # newly created cert lineage. # pylint: disable=protected-access - self.assertTrue(result._consistent()) - self.assertTrue(os.path.exists(os.path.join( - self.config.renewal_configs_dir, "the-lineage.com.conf"))) - self.assertTrue(os.path.exists(os.path.join( - self.config.live_dir, "README"))) - self.assertTrue(os.path.exists(os.path.join( - self.config.live_dir, "the-lineage.com", "README"))) - self.assertTrue(filesystem.check_mode(result.key_path, 0o600)) + assert result._consistent() + assert os.path.exists(os.path.join( + self.config.renewal_configs_dir, "the-lineage.com.conf")) + assert os.path.exists(os.path.join( + self.config.live_dir, "README")) + assert os.path.exists(os.path.join( + self.config.live_dir, "the-lineage.com", "README")) + assert filesystem.check_mode(result.key_path, 0o600) with open(result.fullchain, "rb") as f: - self.assertEqual(f.read(), b"cert" + b"chain") + assert f.read() == b"cert" + b"chain" # Let's do it again and make sure it makes a different lineage result = storage.RenewableCert.new_lineage( "the-lineage.com", b"cert2", b"privkey2", b"chain2", self.config) - self.assertTrue(os.path.exists(os.path.join( - self.config.renewal_configs_dir, "the-lineage.com-0001.conf"))) - self.assertTrue(os.path.exists(os.path.join( - self.config.live_dir, "the-lineage.com-0001", "README"))) + assert os.path.exists(os.path.join( + self.config.renewal_configs_dir, "the-lineage.com-0001.conf")) + assert os.path.exists(os.path.join( + self.config.live_dir, "the-lineage.com-0001", "README")) # Allow write to existing but empty dir filesystem.mkdir(os.path.join(self.config.default_archive_dir, "the-lineage.com-0002")) result = storage.RenewableCert.new_lineage( "the-lineage.com", b"cert3", b"privkey3", b"chain3", self.config) - self.assertTrue(os.path.exists(os.path.join( - self.config.live_dir, "the-lineage.com-0002", "README"))) - self.assertTrue(filesystem.check_mode(result.key_path, 0o600)) + assert os.path.exists(os.path.join( + self.config.live_dir, "the-lineage.com-0002", "README")) + assert filesystem.check_mode(result.key_path, 0o600) # Now trigger the detection of already existing files shutil.copytree(os.path.join(self.config.live_dir, "the-lineage.com"), os.path.join(self.config.live_dir, "the-lineage.com-0003")) - self.assertRaises(errors.CertStorageError, - storage.RenewableCert.new_lineage, "the-lineage.com", + with pytest.raises(errors.CertStorageError): + storage.RenewableCert.new_lineage("the-lineage.com", b"cert4", b"privkey4", b"chain4", self.config) shutil.copytree(os.path.join(self.config.live_dir, "the-lineage.com"), os.path.join(self.config.live_dir, "other-example.com")) - self.assertRaises(errors.CertStorageError, - storage.RenewableCert.new_lineage, - "other-example.com", b"cert5", + with pytest.raises(errors.CertStorageError): + storage.RenewableCert.new_lineage("other-example.com", b"cert5", b"privkey5", b"chain5", self.config) # Make sure it can accept renewal parameters result = storage.RenewableCert.new_lineage( @@ -655,39 +652,36 @@ class RenewableCertTests(BaseRenewableCertTest): storage.RenewableCert.new_lineage( "the-lineage.com", b"cert2", b"privkey2", b"chain2", self.config) - self.assertTrue(os.path.exists( + assert os.path.exists( os.path.join( - self.config.renewal_configs_dir, "the-lineage.com.conf"))) - self.assertTrue(os.path.exists(os.path.join( - self.config.live_dir, "the-lineage.com", "privkey.pem"))) - self.assertTrue(os.path.exists(os.path.join( - self.config.default_archive_dir, "the-lineage.com", "privkey1.pem"))) + self.config.renewal_configs_dir, "the-lineage.com.conf")) + assert os.path.exists(os.path.join( + self.config.live_dir, "the-lineage.com", "privkey.pem")) + assert os.path.exists(os.path.join( + self.config.default_archive_dir, "the-lineage.com", "privkey1.pem")) @mock.patch("certbot._internal.storage.util.unique_lineage_name") def test_invalid_config_filename(self, mock_uln): from certbot._internal import storage mock_uln.return_value = "this_does_not_end_with_dot_conf", "yikes" - self.assertRaises(errors.CertStorageError, - storage.RenewableCert.new_lineage, "example.com", + with pytest.raises(errors.CertStorageError): + storage.RenewableCert.new_lineage("example.com", "cert", "privkey", "chain", self.config) def test_bad_kind(self): - self.assertRaises( - errors.CertStorageError, self.test_rc.current_target, "elephant") - self.assertRaises( - errors.CertStorageError, self.test_rc.current_version, "elephant") - self.assertRaises( - errors.CertStorageError, self.test_rc.version, "elephant", 17) - self.assertRaises( - errors.CertStorageError, - self.test_rc.available_versions, "elephant") - self.assertRaises( - errors.CertStorageError, - self.test_rc.newest_available_version, "elephant") + with pytest.raises(errors.CertStorageError): + self.test_rc.current_target("elephant") + with pytest.raises(errors.CertStorageError): + self.test_rc.current_version("elephant") + with pytest.raises(errors.CertStorageError): + self.test_rc.version("elephant", 17) + with pytest.raises(errors.CertStorageError): + self.test_rc.available_versions("elephant") + with pytest.raises(errors.CertStorageError): + self.test_rc.newest_available_version("elephant") # pylint: disable=protected-access - self.assertRaises( - errors.CertStorageError, - self.test_rc._update_link_to, "elephant", 17) + with pytest.raises(errors.CertStorageError): + self.test_rc._update_link_to("elephant", 17) @mock.patch("certbot.ocsp.RevocationChecker.ocsp_revoked_by_paths") def test_ocsp_revoked(self, mock_checker): @@ -700,24 +694,24 @@ class RenewableCertTests(BaseRenewableCertTest): # Test with cert revoked mock_checker.return_value = True - self.assertTrue(self.test_rc.ocsp_revoked(version)) - self.assertEqual(mock_checker.call_args[0][0], expected_cert_path) - self.assertEqual(mock_checker.call_args[0][1], expected_chain_path) + assert self.test_rc.ocsp_revoked(version) + assert mock_checker.call_args[0][0] == expected_cert_path + assert mock_checker.call_args[0][1] == expected_chain_path # Test with cert not revoked mock_checker.return_value = False - self.assertFalse(self.test_rc.ocsp_revoked(version)) - self.assertEqual(mock_checker.call_args[0][0], expected_cert_path) - self.assertEqual(mock_checker.call_args[0][1], expected_chain_path) + assert not self.test_rc.ocsp_revoked(version) + assert mock_checker.call_args[0][0] == expected_cert_path + assert mock_checker.call_args[0][1] == expected_chain_path # Test with error mock_checker.side_effect = ValueError with mock.patch("certbot._internal.storage.logger.warning") as logger: - self.assertFalse(self.test_rc.ocsp_revoked(version)) - self.assertEqual(mock_checker.call_args[0][0], expected_cert_path) - self.assertEqual(mock_checker.call_args[0][1], expected_chain_path) + assert not self.test_rc.ocsp_revoked(version) + assert mock_checker.call_args[0][0] == expected_cert_path + assert mock_checker.call_args[0][1] == expected_chain_path log_msg = logger.call_args[0][0] - self.assertIn("An error occurred determining the OCSP status", log_msg) + assert "An error occurred determining the OCSP status" in log_msg def test_add_time_interval(self): from certbot._internal import storage @@ -758,38 +752,36 @@ class RenewableCertTests(BaseRenewableCertTest): for parameters, excepted in intended.items(): base_time, interval = parameters - self.assertEqual(storage.add_time_interval(base_time, interval), - excepted) + assert storage.add_time_interval(base_time, interval) == \ + excepted def test_server(self): self.test_rc.configuration["renewalparams"] = {} - self.assertIsNone(self.test_rc.server) + assert self.test_rc.server is None rp = self.test_rc.configuration["renewalparams"] rp["server"] = "https://acme.example/dir" - self.assertEqual(self.test_rc.server, "https://acme.example/dir") + assert self.test_rc.server == "https://acme.example/dir" def test_is_test_cert(self): self.test_rc.configuration["renewalparams"] = {} rp = self.test_rc.configuration["renewalparams"] - self.assertIs(self.test_rc.is_test_cert, False) + assert self.test_rc.is_test_cert is False rp["server"] = "https://acme-staging-v02.api.letsencrypt.org/directory" - self.assertIs(self.test_rc.is_test_cert, True) + assert self.test_rc.is_test_cert is True rp["server"] = "https://staging.someotherca.com/directory" - self.assertIs(self.test_rc.is_test_cert, True) + assert self.test_rc.is_test_cert is True rp["server"] = "https://acme-v01.api.letsencrypt.org/directory" - self.assertIs(self.test_rc.is_test_cert, False) + assert self.test_rc.is_test_cert is False rp["server"] = "https://acme-v02.api.letsencrypt.org/directory" - self.assertIs(self.test_rc.is_test_cert, False) + assert self.test_rc.is_test_cert is False def test_missing_cert(self): from certbot._internal import storage - self.assertRaises(errors.CertStorageError, - storage.RenewableCert, - self.config_file.filename, self.config) + with pytest.raises(errors.CertStorageError): + storage.RenewableCert(self.config_file.filename, self.config) os.symlink("missing", self.config_file[ALL_FOUR[0]]) - self.assertRaises(errors.CertStorageError, - storage.RenewableCert, - self.config_file.filename, self.config) + with pytest.raises(errors.CertStorageError): + storage.RenewableCert(self.config_file.filename, self.config) def test_write_renewal_config(self): # Mostly tested by the process of creating and updating lineages, @@ -813,16 +805,16 @@ class RenewableCertTests(BaseRenewableCertTest): with open(temp2, "r") as f: content = f.read() # useful value was updated - self.assertIn("useful = new_value", content) + assert "useful = new_value" in content # associated comment was preserved - self.assertIn("A useful value", content) + assert "A useful value" in content # useless value was deleted - self.assertNotIn("useless", content) + assert "useless" not in content # check version was stored - self.assertIn("version = {0}".format(certbot.__version__), content) + assert "version = {0}".format(certbot.__version__) in content # ensure permissions are copied - self.assertEqual(stat.S_IMODE(os.lstat(temp).st_mode), - stat.S_IMODE(os.lstat(temp2).st_mode)) + assert stat.S_IMODE(os.lstat(temp).st_mode) == \ + stat.S_IMODE(os.lstat(temp2).st_mode) def test_update_symlinks(self): from certbot._internal import storage @@ -833,12 +825,29 @@ class RenewableCertTests(BaseRenewableCertTest): archive_path = os.path.join(archive_dir_path, basename) open(archive_path, 'a').close() os.symlink(os.path.join(self.config.config_dir, basename), live_path) - self.assertRaises(errors.CertStorageError, - storage.RenewableCert, self.config_file.filename, + with pytest.raises(errors.CertStorageError): + storage.RenewableCert(self.config_file.filename, self.config) storage.RenewableCert(self.config_file.filename, self.config, update_symlinks=True) + def test_truncate(self): + # It should not do anything when there's less than 5 cert history + for kind in ALL_FOUR: + self._write_out_kind(kind, 1) + with mock.patch('certbot.compat.os.unlink') as mock_unlink: + self.test_rc.truncate() + mock_unlink.assert_not_called() + + # It should truncate the excess when there's more than 5 cert history + for kind in ALL_FOUR: + for i in range(2, 8): + self._write_out_kind(kind, i) + with mock.patch('certbot.compat.os.unlink') as mock_unlink: + self.test_rc.truncate() + assert mock_unlink.call_count == 1 * len(ALL_FOUR) + assert "1.pem" in mock_unlink.call_args_list[0][0][0] + class DeleteFilesTest(BaseRenewableCertTest): """Tests for certbot._internal.storage.delete_files""" def setUp(self): @@ -850,12 +859,12 @@ class DeleteFilesTest(BaseRenewableCertTest): with open(kind_path, 'a'): pass self.config_file.write() - self.assertTrue(os.path.exists(os.path.join( - self.config.renewal_configs_dir, "example.org.conf"))) - self.assertTrue(os.path.exists(os.path.join( - self.config.live_dir, "example.org"))) - self.assertTrue(os.path.exists(os.path.join( - self.config.config_dir, "archive", "example.org"))) + assert os.path.exists(os.path.join( + self.config.renewal_configs_dir, "example.org.conf")) + assert os.path.exists(os.path.join( + self.config.live_dir, "example.org")) + assert os.path.exists(os.path.join( + self.config.config_dir, "archive", "example.org")) def _call(self): from certbot._internal import storage @@ -865,69 +874,71 @@ class DeleteFilesTest(BaseRenewableCertTest): def test_delete_all_files(self): self._call() - self.assertFalse(os.path.exists(os.path.join( - self.config.renewal_configs_dir, "example.org.conf"))) - self.assertFalse(os.path.exists(os.path.join( - self.config.live_dir, "example.org"))) - self.assertFalse(os.path.exists(os.path.join( - self.config.config_dir, "archive", "example.org"))) + assert not os.path.exists(os.path.join( + self.config.renewal_configs_dir, "example.org.conf")) + assert not os.path.exists(os.path.join( + self.config.live_dir, "example.org")) + assert not os.path.exists(os.path.join( + self.config.config_dir, "archive", "example.org")) def test_bad_renewal_config(self): with open(self.config_file.filename, 'a') as config_file: config_file.write("asdfasfasdfasdf") - self.assertRaises(errors.CertStorageError, self._call) - self.assertTrue(os.path.exists(os.path.join( - self.config.live_dir, "example.org"))) - self.assertFalse(os.path.exists(os.path.join( - self.config.renewal_configs_dir, "example.org.conf"))) + with pytest.raises(errors.CertStorageError): + self._call() + assert os.path.exists(os.path.join( + self.config.live_dir, "example.org")) + assert not os.path.exists(os.path.join( + self.config.renewal_configs_dir, "example.org.conf")) def test_no_renewal_config(self): os.remove(self.config_file.filename) - self.assertRaises(errors.CertStorageError, self._call) - self.assertTrue(os.path.exists(os.path.join( - self.config.live_dir, "example.org"))) - self.assertFalse(os.path.exists(self.config_file.filename)) + with pytest.raises(errors.CertStorageError): + self._call() + assert os.path.exists(os.path.join( + self.config.live_dir, "example.org")) + assert not os.path.exists(self.config_file.filename) def test_no_cert_file(self): os.remove(os.path.join( self.config.live_dir, "example.org", "cert.pem")) self._call() - self.assertFalse(os.path.exists(self.config_file.filename)) - self.assertFalse(os.path.exists(os.path.join( - self.config.live_dir, "example.org"))) - self.assertFalse(os.path.exists(os.path.join( - self.config.config_dir, "archive", "example.org"))) + assert not os.path.exists(self.config_file.filename) + assert not os.path.exists(os.path.join( + self.config.live_dir, "example.org")) + assert not os.path.exists(os.path.join( + self.config.config_dir, "archive", "example.org")) def test_no_readme_file(self): os.remove(os.path.join( self.config.live_dir, "example.org", "README")) self._call() - self.assertFalse(os.path.exists(self.config_file.filename)) - self.assertFalse(os.path.exists(os.path.join( - self.config.live_dir, "example.org"))) - self.assertFalse(os.path.exists(os.path.join( - self.config.config_dir, "archive", "example.org"))) + assert not os.path.exists(self.config_file.filename) + assert not os.path.exists(os.path.join( + self.config.live_dir, "example.org")) + assert not os.path.exists(os.path.join( + self.config.config_dir, "archive", "example.org")) def test_livedir_not_empty(self): with open(os.path.join( self.config.live_dir, "example.org", "other_file"), 'a'): pass self._call() - self.assertFalse(os.path.exists(self.config_file.filename)) - self.assertTrue(os.path.exists(os.path.join( - self.config.live_dir, "example.org"))) - self.assertFalse(os.path.exists(os.path.join( - self.config.config_dir, "archive", "example.org"))) + assert not os.path.exists(self.config_file.filename) + assert os.path.exists(os.path.join( + self.config.live_dir, "example.org")) + assert not os.path.exists(os.path.join( + self.config.config_dir, "archive", "example.org")) def test_no_archive(self): archive_dir = os.path.join(self.config.config_dir, "archive", "example.org") os.rmdir(archive_dir) self._call() - self.assertFalse(os.path.exists(self.config_file.filename)) - self.assertFalse(os.path.exists(os.path.join( - self.config.live_dir, "example.org"))) - self.assertFalse(os.path.exists(archive_dir)) + assert not os.path.exists(self.config_file.filename) + assert not os.path.exists(os.path.join( + self.config.live_dir, "example.org")) + assert not os.path.exists(archive_dir) class CertPathForCertNameTest(BaseRenewableCertTest): """Test for certbot._internal.storage.cert_path_for_cert_name""" @@ -944,10 +955,11 @@ class CertPathForCertNameTest(BaseRenewableCertTest): return cert_path_for_cert_name(cli_config, certname) def test_simple_cert_name(self): - self.assertEqual(self._call(self.config, 'example.org'), self.fullchain) + assert self._call(self.config, 'example.org') == self.fullchain def test_no_such_cert_name(self): - self.assertRaises(errors.CertStorageError, self._call, self.config, 'fake-example.org') + with pytest.raises(errors.CertStorageError): + self._call(self.config, 'fake-example.org') if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index e9b5ddef2..9377ed6b7 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -7,6 +7,8 @@ import sys import unittest from unittest import mock +import pytest + from certbot import errors from certbot.compat import filesystem from certbot.compat import os @@ -27,7 +29,7 @@ class EnvNoSnapForExternalCallsTest(unittest.TestCase): env_copy_dict['SNAP'] = 'RANDOM_NONSENSE_GARBAGE' env_copy_dict['CERTBOT_SNAPPED'] = 'True' with mock.patch('certbot.compat.os.environ.copy', return_value=env_copy_dict): - self.assertEqual(self._call()['PATH'], original_path) + assert self._call()['PATH'] == original_path def test_noop(self): env_copy_dict_unmodified = os.environ.copy() @@ -38,13 +40,13 @@ class EnvNoSnapForExternalCallsTest(unittest.TestCase): # contains neither necessary key env_copy_dict.pop('SNAP', None) env_copy_dict.pop('CERTBOT_SNAPPED', None) - self.assertEqual(self._call()['PATH'], env_copy_dict_unmodified['PATH']) + assert self._call()['PATH'] == env_copy_dict_unmodified['PATH'] # contains only one necessary key env_copy_dict['SNAP'] = 'RANDOM_NONSENSE_GARBAGE' - self.assertEqual(self._call()['PATH'], env_copy_dict_unmodified['PATH']) + assert self._call()['PATH'] == env_copy_dict_unmodified['PATH'] del env_copy_dict['SNAP'] env_copy_dict['CERTBOT_SNAPPED'] = 'True' - self.assertEqual(self._call()['PATH'], env_copy_dict_unmodified['PATH']) + assert self._call()['PATH'] == env_copy_dict_unmodified['PATH'] class RunScriptTest(unittest.TestCase): @@ -62,20 +64,22 @@ class RunScriptTest(unittest.TestCase): mock_run().stderr = "stderr" out, err = self._call(["test"]) - self.assertEqual(out, "stdout") - self.assertEqual(err, "stderr") + assert out == "stdout" + assert err == "stderr" @mock.patch("certbot.util.subprocess.run") def test_bad_process(self, mock_run): mock_run.side_effect = OSError - self.assertRaises(errors.SubprocessError, self._call, ["test"]) + with pytest.raises(errors.SubprocessError): + self._call(["test"]) @mock.patch("certbot.util.subprocess.run") def test_failure(self, mock_run): mock_run().returncode = 1 - self.assertRaises(errors.SubprocessError, self._call, ["test"]) + with pytest.raises(errors.SubprocessError): + self._call(["test"]) class ExeExistsTest(unittest.TestCase): @@ -88,11 +92,11 @@ class ExeExistsTest(unittest.TestCase): def test_exe_exists(self): with mock.patch("certbot.util.filesystem.is_executable", return_value=True): - self.assertTrue(self._call("/path/to/exe")) + assert self._call("/path/to/exe") def test_exe_not_exists(self): with mock.patch("certbot.util.filesystem.is_executable", return_value=False): - self.assertFalse(self._call("/path/to/exe")) + assert not self._call("/path/to/exe") class LockDirUntilExit(test_util.TempDirTestCase): @@ -117,17 +121,18 @@ class LockDirUntilExit(test_util.TempDirTestCase): self._call(subdir) self._call(subdir) - self.assertEqual(mock_register.call_count, 1) + assert mock_register.call_count == 1 registered_func = mock_register.call_args[0][0] from certbot import util + # Despite lock_dir_until_exit has been called twice to subdir, its lock should have been # added only once. So we expect to have two lock references: for self.tempdir and subdir - self.assertEqual(len(util._LOCKS), 2) # pylint: disable=protected-access + assert len(util._LOCKS) == 2 # pylint: disable=protected-access registered_func() # Exception should not be raised # Logically, logger.debug, that would be invoked in case of unlock failure, # should never been called. - self.assertEqual(mock_logger.debug.call_count, 0) + assert mock_logger.debug.call_count == 0 class SetUpCoreDirTest(test_util.TempDirTestCase): @@ -141,13 +146,14 @@ class SetUpCoreDirTest(test_util.TempDirTestCase): def test_success(self, mock_lock): new_dir = os.path.join(self.tempdir, 'new') self._call(new_dir, 0o700, False) - self.assertTrue(os.path.exists(new_dir)) - self.assertEqual(mock_lock.call_count, 1) + assert os.path.exists(new_dir) + assert mock_lock.call_count == 1 @mock.patch('certbot.util.make_or_verify_dir') def test_failure(self, mock_make_or_verify): mock_make_or_verify.side_effect = OSError - self.assertRaises(errors.Error, self._call, self.tempdir, 0o700, False) + with pytest.raises(errors.Error): + self._call(self.tempdir, 0o700, False) class MakeOrVerifyDirTest(test_util.TempDirTestCase): @@ -171,20 +177,22 @@ class MakeOrVerifyDirTest(test_util.TempDirTestCase): def test_creates_dir_when_missing(self): path = os.path.join(self.tempdir, "bar") self._call(path, 0o650) - self.assertTrue(os.path.isdir(path)) - self.assertTrue(filesystem.check_mode(path, 0o650)) + assert os.path.isdir(path) + assert filesystem.check_mode(path, 0o650) def test_existing_correct_mode_does_not_fail(self): self._call(self.path, 0o600) - self.assertTrue(filesystem.check_mode(self.path, 0o600)) + assert filesystem.check_mode(self.path, 0o600) def test_existing_wrong_mode_fails(self): - self.assertRaises(errors.Error, self._call, self.path, 0o400) + with pytest.raises(errors.Error): + self._call(self.path, 0o400) def test_reraises_os_error(self): with mock.patch.object(filesystem, "makedirs") as makedirs: makedirs.side_effect = OSError() - self.assertRaises(OSError, self._call, "bar", 12312312) + with pytest.raises(OSError): + self._call("bar", 12312312) class UniqueFileTest(test_util.TempDirTestCase): @@ -204,13 +212,13 @@ class UniqueFileTest(test_util.TempDirTestCase): fd.write("bar") fd.close() with open(name) as f: - self.assertEqual(f.read(), "bar") + assert f.read() == "bar" def test_right_mode(self): fd1, name1 = self._call(0o700) fd2, name2 = self._call(0o600) - self.assertTrue(filesystem.check_mode(name1, 0o700)) - self.assertTrue(filesystem.check_mode(name2, 0o600)) + assert filesystem.check_mode(name1, 0o700) + assert filesystem.check_mode(name2, 0o600) fd1.close() fd2.close() @@ -219,20 +227,20 @@ class UniqueFileTest(test_util.TempDirTestCase): fd2, name2 = self._call() fd3, name3 = self._call() - self.assertNotEqual(name1, name2) - self.assertNotEqual(name1, name3) - self.assertNotEqual(name2, name3) + assert name1 != name2 + assert name1 != name3 + assert name2 != name3 - self.assertEqual(os.path.dirname(name1), self.tempdir) - self.assertEqual(os.path.dirname(name2), self.tempdir) - self.assertEqual(os.path.dirname(name3), self.tempdir) + assert os.path.dirname(name1) == self.tempdir + assert os.path.dirname(name2) == self.tempdir + assert os.path.dirname(name3) == self.tempdir basename1 = os.path.basename(name2) - self.assertTrue(basename1.endswith("foo.txt")) + assert basename1.endswith("foo.txt") basename2 = os.path.basename(name2) - self.assertTrue(basename2.endswith("foo.txt")) + assert basename2.endswith("foo.txt") basename3 = os.path.basename(name3) - self.assertTrue(basename3.endswith("foo.txt")) + assert basename3.endswith("foo.txt") fd1.close() fd2.close() @@ -255,8 +263,8 @@ class UniqueLineageNameTest(test_util.TempDirTestCase): def test_basic(self): f, path = self._call("wow") - self.assertIsInstance(f, file_type) - self.assertEqual(os.path.join(self.tempdir, "wow.conf"), path) + assert isinstance(f, file_type) + assert os.path.join(self.tempdir, "wow.conf") == path f.close() def test_multiple(self): @@ -264,15 +272,16 @@ class UniqueLineageNameTest(test_util.TempDirTestCase): for _ in range(10): items.append(self._call("wow")) f, name = items[-1] - self.assertIsInstance(f, file_type) - self.assertIsInstance(name, str) - self.assertIn("wow-0009.conf", name) + assert isinstance(f, file_type) + assert isinstance(name, str) + assert "wow-0009.conf" in name for f, _ in items: f.close() def test_failure(self): with mock.patch("certbot.compat.filesystem.open", side_effect=OSError(errno.EIO)): - self.assertRaises(OSError, self._call, "wow") + with pytest.raises(OSError): + self._call("wow") class SafelyRemoveTest(test_util.TempDirTestCase): @@ -291,17 +300,18 @@ class SafelyRemoveTest(test_util.TempDirTestCase): with open(self.path, "w"): pass # just create the file self._call() - self.assertFalse(os.path.exists(self.path)) + assert not os.path.exists(self.path) def test_missing(self): self._call() # no error, yay! - self.assertFalse(os.path.exists(self.path)) + assert not os.path.exists(self.path) def test_other_error_passthrough(self): with mock.patch("certbot.util.os.remove") as mock_remove: mock_remove.side_effect = OSError - self.assertRaises(OSError, self._call) + with pytest.raises(OSError): + self._call() class SafeEmailTest(unittest.TestCase): @@ -318,7 +328,7 @@ class SafeEmailTest(unittest.TestCase): "abc_def.jdk@hotmail.museum", ] for addr in addrs: - self.assertTrue(self._call(addr), "%s failed." % addr) + assert self._call(addr), "%s failed." % addr def test_invalid_emails(self): addrs = [ @@ -327,7 +337,7 @@ class SafeEmailTest(unittest.TestCase): "~/abc_def.jdk@hotmail.museum", ] for addr in addrs: - self.assertFalse(self._call(addr), "%s failed." % addr) + assert not self._call(addr), "%s failed." % addr class AddDeprecatedArgumentTest(unittest.TestCase): @@ -343,17 +353,17 @@ class AddDeprecatedArgumentTest(unittest.TestCase): self._call("--old-option", 0) with mock.patch("warnings.warn") as mock_warn: self.parser.parse_args(["--old-option"]) - self.assertEqual(mock_warn.call_count, 1) - self.assertIn("is deprecated", mock_warn.call_args[0][0]) - self.assertIn("--old-option", mock_warn.call_args[0][0]) + assert mock_warn.call_count == 1 + assert "is deprecated" in mock_warn.call_args[0][0] + assert "--old-option" in mock_warn.call_args[0][0] def test_warning_with_arg(self): self._call("--old-option", 1) with mock.patch("warnings.warn") as mock_warn: self.parser.parse_args(["--old-option", "42"]) - self.assertEqual(mock_warn.call_count, 1) - self.assertIn("is deprecated", mock_warn.call_args[0][0]) - self.assertIn("--old-option", mock_warn.call_args[0][0]) + assert mock_warn.call_count == 1 + assert "is deprecated" in mock_warn.call_args[0][0] + assert "--old-option" in mock_warn.call_args[0][0] def test_help(self): self._call("--old-option", 2) @@ -363,7 +373,7 @@ class AddDeprecatedArgumentTest(unittest.TestCase): self.parser.parse_args(["-h"]) except SystemExit: pass - self.assertNotIn("--old-option", stdout.getvalue()) + assert "--old-option" not in stdout.getvalue() def test_set_constant(self): """Test when ACTION_TYPES_THAT_DONT_NEED_A_VALUE is a set. @@ -386,8 +396,7 @@ class AddDeprecatedArgumentTest(unittest.TestCase): mock_configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE = typ() self._call("--old-option", 1) self._call("--old-option2", 2) - self.assertEqual( - len(mock_configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE), 1) + assert len(mock_configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE) == 1 class EnforceLeValidity(unittest.TestCase): @@ -397,32 +406,36 @@ class EnforceLeValidity(unittest.TestCase): return enforce_le_validity(domain) def test_sanity(self): - self.assertRaises(errors.ConfigurationError, self._call, u"..") + with pytest.raises(errors.ConfigurationError): + self._call(u"..") def test_invalid_chars(self): - self.assertRaises( - errors.ConfigurationError, self._call, u"hello_world.example.com") + with pytest.raises(errors.ConfigurationError): + self._call(u"hello_world.example.com") def test_leading_hyphen(self): - self.assertRaises( - errors.ConfigurationError, self._call, u"-a.example.com") + with pytest.raises(errors.ConfigurationError): + self._call(u"-a.example.com") def test_trailing_hyphen(self): - self.assertRaises( - errors.ConfigurationError, self._call, u"a-.example.com") + with pytest.raises(errors.ConfigurationError): + self._call(u"a-.example.com") def test_one_label(self): - self.assertRaises(errors.ConfigurationError, self._call, u"com") + with pytest.raises(errors.ConfigurationError): + self._call(u"com") def test_valid_domain(self): - self.assertEqual(self._call(u"example.com"), u"example.com") + assert self._call(u"example.com") == u"example.com" def test_input_with_scheme(self): - self.assertRaises(errors.ConfigurationError, self._call, u"http://example.com") - self.assertRaises(errors.ConfigurationError, self._call, u"https://example.com") + with pytest.raises(errors.ConfigurationError): + self._call(u"http://example.com") + with pytest.raises(errors.ConfigurationError): + self._call(u"https://example.com") def test_valid_input_with_scheme_name(self): - self.assertEqual(self._call(u"http.example.com"), u"http.example.com") + assert self._call(u"http.example.com") == u"http.example.com" class EnforceDomainSanityTest(unittest.TestCase): @@ -433,17 +446,17 @@ class EnforceDomainSanityTest(unittest.TestCase): return enforce_domain_sanity(domain) def test_nonascii_str(self): - self.assertRaises(errors.ConfigurationError, self._call, - u"eichh\u00f6rnchen.example.com".encode("utf-8")) + with pytest.raises(errors.ConfigurationError): + self._call(u"eichh\u00f6rnchen.example.com".encode("utf-8")) def test_nonascii_unicode(self): - self.assertRaises(errors.ConfigurationError, self._call, - u"eichh\u00f6rnchen.example.com") + with pytest.raises(errors.ConfigurationError): + self._call(u"eichh\u00f6rnchen.example.com") def test_too_long(self): long_domain = u"a"*256 - self.assertRaises(errors.ConfigurationError, self._call, - long_domain) + with pytest.raises(errors.ConfigurationError): + self._call(long_domain) def test_not_too_long(self): not_too_long_domain = u"{0}.{1}.{2}.{3}".format("a"*63, "b"*63, "c"*63, "d"*63) @@ -451,23 +464,23 @@ class EnforceDomainSanityTest(unittest.TestCase): def test_empty_label(self): empty_label_domain = u"fizz..example.com" - self.assertRaises(errors.ConfigurationError, self._call, - empty_label_domain) + with pytest.raises(errors.ConfigurationError): + self._call(empty_label_domain) def test_empty_trailing_label(self): empty_trailing_label_domain = u"example.com.." - self.assertRaises(errors.ConfigurationError, self._call, - empty_trailing_label_domain) + with pytest.raises(errors.ConfigurationError): + self._call(empty_trailing_label_domain) def test_long_label_1(self): long_label_domain = u"a"*64 - self.assertRaises(errors.ConfigurationError, self._call, - long_label_domain) + with pytest.raises(errors.ConfigurationError): + self._call(long_label_domain) def test_long_label_2(self): long_label_domain = u"{0}.{1}.com".format(u"a"*64, u"b"*63) - self.assertRaises(errors.ConfigurationError, self._call, - long_label_domain) + with pytest.raises(errors.ConfigurationError): + self._call(long_label_domain) def test_not_long_label(self): not_too_long_label_domain = u"{0}.{1}.com".format(u"a"*63, u"b"*63) @@ -475,8 +488,8 @@ class EnforceDomainSanityTest(unittest.TestCase): def test_empty_domain(self): empty_domain = u"" - self.assertRaises(errors.ConfigurationError, self._call, - empty_domain) + with pytest.raises(errors.ConfigurationError): + self._call(empty_domain) def test_punycode_ok(self): # Punycode is now legal, so no longer an error; instead check @@ -496,12 +509,12 @@ class IsWildcardDomainTest(unittest.TestCase): return is_wildcard_domain(domain) def test_no_wildcard(self): - self.assertFalse(self._call(self.no_wildcard)) - self.assertFalse(self._call(self.no_wildcard.encode())) + assert not self._call(self.no_wildcard) + assert not self._call(self.no_wildcard.encode()) def test_wildcard(self): - self.assertTrue(self._call(self.wildcard)) - self.assertTrue(self._call(self.wildcard.encode())) + assert self._call(self.wildcard) + assert self._call(self.wildcard.encode()) class OsInfoTest(unittest.TestCase): @@ -513,8 +526,8 @@ class OsInfoTest(unittest.TestCase): import certbot.util as cbutil m_distro.like.return_value = "first debian third" id_likes = cbutil.get_systemd_os_like() - self.assertEqual(len(id_likes), 3) - self.assertIn("debian", id_likes) + assert len(id_likes) == 3 + assert "debian" in id_likes @mock.patch("certbot.util.distro") @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") @@ -525,11 +538,11 @@ class OsInfoTest(unittest.TestCase): m_distro.version.return_value = "1.0" # empty value on first call for fallback to "get_python_os_info" in get_os_info_ua m_distro.name.side_effect = ["", "something", "something"] - self.assertEqual(cbutil.get_os_info_ua(), - " ".join(cbutil.get_python_os_info(pretty=True))) + assert cbutil.get_os_info_ua() == \ + " ".join(cbutil.get_python_os_info(pretty=True)) m_distro.name.side_effect = ["whatever"] - self.assertEqual(cbutil.get_os_info_ua(), "whatever") + assert cbutil.get_os_info_ua() == "whatever" @mock.patch("certbot.util.distro") @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") @@ -539,36 +552,36 @@ class OsInfoTest(unittest.TestCase): m_distro.id.return_value = "name" m_distro.version.return_value = "version" mock_platform.return_value = "linux" - self.assertEqual(cbutil.get_os_info(), ("name", "version")) + assert cbutil.get_os_info() == ("name", "version") m_distro.id.return_value = "something" m_distro.version.return_value = "else" - self.assertEqual(cbutil.get_os_info(), ("something", "else")) + assert cbutil.get_os_info() == ("something", "else") def test_non_systemd_os_info(self): import certbot.util as cbutil with mock.patch('certbot.util._USE_DISTRO', False): with mock.patch('platform.system_alias', return_value=('NonSystemD', '42', '42')): - self.assertEqual(cbutil.get_python_os_info()[0], 'nonsystemd') + assert cbutil.get_python_os_info()[0] == 'nonsystemd' with mock.patch('platform.system_alias', return_value=('darwin', '', '')): with mock.patch("subprocess.run") as run_mock: run_mock().stdout = '42.42.42' - self.assertEqual(cbutil.get_python_os_info()[0], 'darwin') - self.assertEqual(cbutil.get_python_os_info()[1], '42.42.42') + assert cbutil.get_python_os_info()[0] == 'darwin' + assert cbutil.get_python_os_info()[1] == '42.42.42' with mock.patch('platform.system_alias', return_value=('freebsd', '9.3-RC3-p1', '')): - self.assertEqual(cbutil.get_python_os_info(), ("freebsd", "9")) + assert cbutil.get_python_os_info() == ("freebsd", "9") with mock.patch('platform.system_alias', return_value=('windows', '', '')): with mock.patch('platform.win32_ver', return_value=('4242', '95', '2', '')): - self.assertEqual(cbutil.get_python_os_info(), - ("windows", "95")) + assert cbutil.get_python_os_info() == \ + ("windows", "95") @mock.patch("certbot.util.distro") @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") @@ -576,7 +589,7 @@ class OsInfoTest(unittest.TestCase): import certbot.util as cbutil m_distro.id.return_value = "" m_distro.version.return_value = "" - self.assertEqual(cbutil.get_python_os_info()[0], "linux") + assert cbutil.get_python_os_info()[0] == "linux" @mock.patch("certbot.util.distro") @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") @@ -584,7 +597,7 @@ class OsInfoTest(unittest.TestCase): import certbot.util as cbutil m_distro.id.return_value = "testdist" m_distro.version.return_value = "42" - self.assertEqual(cbutil.get_python_os_info(), ("testdist", "42")) + assert cbutil.get_python_os_info() == ("testdist", "42") class AtexitRegisterTest(unittest.TestCase): @@ -605,7 +618,7 @@ class AtexitRegisterTest(unittest.TestCase): def test_not_called(self): self._test_common(initial_pid=-1) - self.assertIs(self.func.called, False) + assert self.func.called is False def _test_common(self, initial_pid): with mock.patch('certbot.util._INITIAL_PID', initial_pid): @@ -613,7 +626,7 @@ class AtexitRegisterTest(unittest.TestCase): self._call(self.func, *self.args, **self.kwargs) # _INITIAL_PID must be mocked when calling atexit_func - self.assertTrue(mock_atexit.register.called) + assert mock_atexit.register.called args, kwargs = mock_atexit.register.call_args atexit_func = args[0] atexit_func(*args[1:], **kwargs) @@ -640,17 +653,17 @@ class ParseLooseVersionTest(unittest.TestCase): ('0.960923', '2.2beta29'), ('1.13++', '5.5.kw')) for v1, v2 in comparisons: - self.assertLess(self._call(v1), self._call(v2)) + assert self._call(v1) < self._call(v2) def test_equal(self): - self.assertEqual(self._call('8.02'), self._call('8.02')) + assert self._call('8.02') == self._call('8.02') def test_greater_than(self): comparisons = (('161', '3.10a'), ('3.2.pl0', '3.1.1.6')) for v1, v2 in comparisons: - self.assertGreater(self._call(v1), self._call(v2)) + assert self._call(v1) > self._call(v2) if __name__ == "__main__": - unittest.main() # pragma: no cover + sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover diff --git a/letstest/letstest/multitester.py b/letstest/letstest/multitester.py index a74e3f01d..c213a8637 100644 --- a/letstest/letstest/multitester.py +++ b/letstest/letstest/multitester.py @@ -41,10 +41,9 @@ import urllib.request as urllib_request import boto3 from botocore.exceptions import ClientError -import yaml - from fabric import Config from fabric import Connection +import yaml # Command line parser #------------------------------------------------------------------------------- diff --git a/letstest/scripts/version.py b/letstest/scripts/version.py index 6e538b032..dd8f1e3f3 100755 --- a/letstest/scripts/version.py +++ b/letstest/scripts/version.py @@ -5,7 +5,10 @@ Provides a simple utility for determining the Certbot version number """ from __future__ import print_function -from os.path import abspath, dirname, join + +from os.path import abspath +from os.path import dirname +from os.path import join import re diff --git a/pytest.ini b/pytest.ini index e498766ec..75421bf4a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -15,7 +15,18 @@ # certbot-dns-rfc2136. # 2) pytest-cov uses deprecated functionality in pytest-xdist, to be resolved by # https://github.com/pytest-dev/pytest-cov/issues/557. +# 3) requests-toolbelt<0.10.1 can cause this warning to be raised during our +# unit tests. This warning should be ignored until our (transitive) +# dependency on requests-toolbelt is removed or our pinned version can be +# updated. +# 4) Ignore our own PendingDeprecationWarning about update_symlinks soon to be dropped. +# See https://github.com/certbot/certbot/issues/6284. +# 5) Ignore pkg_resources.declare_namespace used in a large number of Google's Python +# libs. e.g. https://github.com/googleapis/google-auth-library-python/issues/1229. filterwarnings = error ignore:decodestring\(\) is a deprecated alias:DeprecationWarning:dns ignore:.*rsyncdir:DeprecationWarning + ignore:'urllib3.contrib.pyopenssl:DeprecationWarning:requests_toolbelt + ignore:update_symlinks is deprecated:PendingDeprecationWarning + ignore:.*declare_namespace\('google:DeprecationWarning diff --git a/tests/lock_test.py b/tests/lock_test.py index a1e706c2f..09e11b25f 100644 --- a/tests/lock_test.py +++ b/tests/lock_test.py @@ -134,7 +134,7 @@ def set_up_command(config_dir: str, logs_dir: str, work_dir: str, nginx_dir: str return ( 'certbot --cert-path {0} --key-path {1} --config-dir {2} ' '--logs-dir {3} --work-dir {4} --nginx-server-root {5} --debug ' - '--force-renewal --nginx -vv '.format( + '--force-renewal --nginx -vv --no-random-sleep-on-renew '.format( test_util.vector_path('cert.pem'), test_util.vector_path('rsa512_key.pem'), config_dir, logs_dir, work_dir, nginx_dir).split()) diff --git a/tests/modification-check.py b/tests/modification-check.py index d685225a8..1d56d3442 100755 --- a/tests/modification-check.py +++ b/tests/modification-check.py @@ -4,7 +4,6 @@ import hashlib import os - # Relative to the root of the Certbot repo, these files are expected to exist # and have the SHA-256 hashes contained in this dictionary. These hashes were # taken from our v1.14.0 tag which was the last release we intended to make diff --git a/tools/1.32.x/requirements.txt b/tools/1.32.x/requirements.txt deleted file mode 100644 index 429eaf192..000000000 --- a/tools/1.32.x/requirements.txt +++ /dev/null @@ -1,194 +0,0 @@ -# This is a copy of tools/requirements.txt from the 1.32.x branch that I -# placed here so it will be in our master branch and GitHub will scan it for -# security vulnerabilities while we still have 1.x snap users. If we're notified -# about problems in our dependencies here and want to upgrade, we should do so -# by updating tools/requirements.txt on the 1.32.x branch, doing a point release -# from that branch, and copying any updates to tools/requirements.txt on that -# branch to this file on the master branch. -# -# This file is named requirements.txt so it is scanned by GitHub. See -# https://docs.github.com/en/github/visualizing-repository-data-with-graphs/about-the-dependency-graph#supported-package-ecosystems -# for more info. -alabaster==0.7.12 ; python_version >= "3.7" and python_version < "4.0" -apacheconfig==0.3.2 ; python_version >= "3.7" and python_version < "4.0" -appnope==0.1.3 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "darwin" -astroid==2.11.7 ; python_version >= "3.7" and python_version < "4.0" -attrs==22.1.0 ; python_version >= "3.7" and python_version < "4.0" -awscli==1.27.30 ; python_version >= "3.7" and python_version < "4.0" -azure-devops==6.0.0b4 ; python_version >= "3.7" and python_version < "4.0" -babel==2.11.0 ; python_version >= "3.7" and python_version < "4.0" -backcall==0.2.0 ; python_version >= "3.7" and python_version < "4.0" -backports-cached-property==1.0.2 ; python_version >= "3.7" and python_version < "3.8" -bcrypt==4.0.1 ; python_version >= "3.7" and python_version < "4.0" -beautifulsoup4==4.11.1 ; python_version >= "3.7" and python_version < "4.0" -bleach==5.0.1 ; python_version >= "3.7" and python_version < "4.0" -boto3==1.26.30 ; python_version >= "3.7" and python_version < "4.0" -botocore==1.29.30 ; python_version >= "3.7" and python_version < "4.0" -cachecontrol==0.12.11 ; python_version >= "3.7" and python_version < "4.0" -cachetools==5.2.0 ; python_version >= "3.7" and python_version < "4.0" -cachy==0.3.0 ; python_version >= "3.7" and python_version < "4.0" -certifi==2022.12.7 ; python_version >= "3.7" and python_version < "4" -cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0" -charset-normalizer==2.1.1 ; python_version >= "3.7" and python_version < "4" -cleo==1.0.0a5 ; python_version >= "3.7" and python_version < "4.0" -cloudflare==2.11.1 ; python_version >= "3.7" and python_version < "4.0" -colorama==0.4.4 ; python_version >= "3.7" and python_version < "4.0" -commonmark==0.9.1 ; python_version >= "3.7" and python_version < "4.0" -configargparse==1.5.3 ; python_version >= "3.7" and python_version < "4.0" -configobj==5.0.6 ; python_version >= "3.7" and python_version < "4.0" -coverage==6.5.0 ; python_version >= "3.7" and python_version < "4.0" -crashtest==0.3.1 ; python_version >= "3.7" and python_version < "4.0" -cryptography==38.0.4 ; python_version >= "3.7" and python_version < "4.0" -cython==0.29.32 ; python_version >= "3.7" and python_version < "4.0" -decorator==5.1.1 ; python_version >= "3.7" and python_version < "4.0" -dill==0.3.6 ; python_version >= "3.7" and python_version < "4.0" -distlib==0.3.6 ; python_version >= "3.7" and python_version < "4.0" -distro==1.8.0 ; python_version >= "3.7" and python_version < "4.0" -dns-lexicon==3.11.7 ; python_version >= "3.7" and python_version < "4.0" -dnspython==2.2.1 ; python_version >= "3.7" and python_version < "4.0" -docutils==0.16 ; python_version >= "3.7" and python_version < "4.0" -dulwich==0.20.50 ; python_version >= "3.7" and python_version < "4.0" -exceptiongroup==1.0.4 ; python_version >= "3.7" and python_version < "3.11" -execnet==1.9.0 ; python_version >= "3.7" and python_version < "4.0" -fabric==2.7.1 ; python_version >= "3.7" and python_version < "4.0" -filelock==3.8.2 ; python_version >= "3.7" and python_version < "4.0" -google-api-core==2.11.0 ; python_version >= "3.7" and python_version < "4.0" -google-api-python-client==2.70.0 ; python_version >= "3.7" and python_version < "4.0" -google-auth-httplib2==0.1.0 ; python_version >= "3.7" and python_version < "4.0" -google-auth==2.15.0 ; python_version >= "3.7" and python_version < "4.0" -googleapis-common-protos==1.57.0 ; python_version >= "3.7" and python_version < "4.0" -html5lib==1.1 ; python_version >= "3.7" and python_version < "4.0" -httplib2==0.21.0 ; python_version >= "3.7" and python_version < "4.0" -idna==3.4 ; python_version >= "3.7" and python_version < "4" -imagesize==1.4.1 ; python_version >= "3.7" and python_version < "4.0" -importlib-metadata==4.13.0 ; python_version >= "3.7" and python_version < "4.0" -importlib-resources==5.10.1 ; python_version >= "3.7" and python_version < "3.9" -iniconfig==1.1.1 ; python_version >= "3.7" and python_version < "4.0" -invoke==1.7.3 ; python_version >= "3.7" and python_version < "4.0" -ipdb==0.13.11 ; python_version >= "3.7" and python_version < "4.0" -ipython==7.34.0 ; python_version >= "3.7" and python_version < "4.0" -isodate==0.6.1 ; python_version >= "3.7" and python_version < "4.0" -isort==5.11.2 ; python_version >= "3.7" and python_version < "4.0" -jaraco-classes==3.2.3 ; python_version >= "3.7" and python_version < "4.0" -jedi==0.18.2 ; python_version >= "3.7" and python_version < "4.0" -jeepney==0.8.0 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "linux" -jinja2==3.1.2 ; python_version >= "3.7" and python_version < "4.0" -jmespath==1.0.1 ; python_version >= "3.7" and python_version < "4.0" -josepy==1.13.0 ; python_version >= "3.7" and python_version < "4.0" -jsonlines==3.1.0 ; python_version >= "3.7" and python_version < "4.0" -jsonpickle==2.2.0 ; python_version >= "3.7" and python_version < "4.0" -jsonschema==4.17.3 ; python_version >= "3.7" and python_version < "4.0" -keyring==23.11.0 ; python_version >= "3.7" and python_version < "4.0" -lazy-object-proxy==1.8.0 ; python_version >= "3.7" and python_version < "4.0" -lockfile==0.12.2 ; python_version >= "3.7" and python_version < "4.0" -markupsafe==2.1.1 ; python_version >= "3.7" and python_version < "4.0" -matplotlib-inline==0.1.6 ; python_version >= "3.7" and python_version < "4.0" -mccabe==0.7.0 ; python_version >= "3.7" and python_version < "4.0" -mock==4.0.3 ; python_version >= "3.7" and python_version < "4.0" -more-itertools==9.0.0 ; python_version >= "3.7" and python_version < "4.0" -msgpack==1.0.4 ; python_version >= "3.7" and python_version < "4.0" -msrest==0.6.21 ; python_version >= "3.7" and python_version < "4.0" -mypy-extensions==0.4.3 ; python_version >= "3.7" and python_version < "4.0" -mypy==0.982 ; python_version >= "3.7" and python_version < "4.0" -oauth2client==4.1.3 ; python_version >= "3.7" and python_version < "4.0" -oauthlib==3.2.2 ; python_version >= "3.7" and python_version < "4.0" -packaging==22.0 ; python_version >= "3.7" and python_version < "4.0" -paramiko==2.12.0 ; python_version >= "3.7" and python_version < "4.0" -parsedatetime==2.6 ; python_version >= "3.7" and python_version < "4.0" -parso==0.8.3 ; python_version >= "3.7" and python_version < "4.0" -pathlib2==2.3.7.post1 ; python_version >= "3.7" and python_version < "4.0" -pexpect==4.8.0 ; python_version >= "3.7" and python_version < "4.0" -pickleshare==0.7.5 ; python_version >= "3.7" and python_version < "4.0" -pip==22.3.1 ; python_version >= "3.7" and python_version < "4.0" -pkginfo==1.9.2 ; python_version >= "3.7" and python_version < "4.0" -pkgutil-resolve-name==1.3.10 ; python_version >= "3.7" and python_version < "3.9" -platformdirs==2.6.0 ; python_version >= "3.7" and python_version < "4.0" -pluggy==1.0.0 ; python_version >= "3.7" and python_version < "4.0" -ply==3.11 ; python_version >= "3.7" and python_version < "4.0" -poetry-core==1.3.2 ; python_version >= "3.7" and python_version < "4.0" -poetry-plugin-export==1.2.0 ; python_version >= "3.7" and python_version < "4.0" -poetry==1.2.2 ; python_version >= "3.7" and python_version < "4.0" -prompt-toolkit==3.0.36 ; python_version >= "3.7" and python_version < "4.0" -protobuf==4.21.12 ; python_version >= "3.7" and python_version < "4.0" -ptyprocess==0.7.0 ; python_version >= "3.7" and python_version < "4.0" -py==1.11.0 ; python_version >= "3.7" and python_version < "4.0" -pyasn1-modules==0.2.8 ; python_version >= "3.7" and python_version < "4.0" -pyasn1==0.4.8 ; python_version >= "3.7" and python_version < "4.0" -pycparser==2.21 ; python_version >= "3.7" and python_version < "4.0" -pygments==2.13.0 ; python_version >= "3.7" and python_version < "4.0" -pylev==1.4.0 ; python_version >= "3.7" and python_version < "4.0" -pylint==2.13.9 ; python_version >= "3.7" and python_version < "4.0" -pynacl==1.5.0 ; python_version >= "3.7" and python_version < "4.0" -pynsist==2.7 ; python_version >= "3.7" and python_version < "4.0" -pyopenssl==22.1.0 ; python_version >= "3.7" and python_version < "4.0" -pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0" -pyrfc3339==1.1 ; python_version >= "3.7" and python_version < "4.0" -pyrsistent==0.19.2 ; python_version >= "3.7" and python_version < "4.0" -pytest-cov==4.0.0 ; python_version >= "3.7" and python_version < "4.0" -pytest-xdist==3.1.0 ; python_version >= "3.7" and python_version < "4.0" -pytest==7.2.0 ; python_version >= "3.7" and python_version < "4.0" -python-augeas==1.1.0 ; python_version >= "3.7" and python_version < "4.0" -python-dateutil==2.8.2 ; python_version >= "3.7" and python_version < "4.0" -python-digitalocean==1.17.0 ; python_version >= "3.7" and python_version < "4.0" -pytz==2022.6 ; python_version >= "3.7" and python_version < "4.0" -pywin32-ctypes==0.2.0 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" -pywin32==305 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" -pyyaml==5.4.1 ; python_version >= "3.7" and python_version < "4.0" -readme-renderer==37.3 ; python_version >= "3.7" and python_version < "4.0" -requests-download==0.1.2 ; python_version >= "3.7" and python_version < "4.0" -requests-file==1.5.1 ; python_version >= "3.7" and python_version < "4.0" -requests-oauthlib==1.3.1 ; python_version >= "3.7" and python_version < "4.0" -requests-toolbelt==0.9.1 ; python_version >= "3.7" and python_version < "4.0" -requests==2.28.1 ; python_version >= "3.7" and python_version < "4" -rfc3986==2.0.0 ; python_version >= "3.7" and python_version < "4.0" -rich==12.6.0 ; python_version >= "3.7" and python_version < "4.0" -rsa==4.7.2 ; python_version >= "3.7" and python_version < "4" -s3transfer==0.6.0 ; python_version >= "3.7" and python_version < "4.0" -secretstorage==3.3.3 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "linux" -semantic-version==2.10.0 ; python_version >= "3.7" and python_version < "4.0" -setuptools-rust==1.5.2 ; python_version >= "3.7" and python_version < "4.0" -setuptools==65.6.3 ; python_version >= "3.7" and python_version < "4.0" -shellingham==1.5.0 ; python_version >= "3.7" and python_version < "4.0" -six==1.16.0 ; python_version >= "3.7" and python_version < "4.0" -snowballstemmer==2.2.0 ; python_version >= "3.7" and python_version < "4.0" -soupsieve==2.3.2.post1 ; python_version >= "3.7" and python_version < "4.0" -sphinx-rtd-theme==1.1.1 ; python_version >= "3.7" and python_version < "4.0" -sphinx==5.1.1 ; python_version >= "3.7" and python_version < "4.0" -sphinxcontrib-applehelp==1.0.2 ; python_version >= "3.7" and python_version < "4.0" -sphinxcontrib-devhelp==1.0.2 ; python_version >= "3.7" and python_version < "4.0" -sphinxcontrib-htmlhelp==2.0.0 ; python_version >= "3.7" and python_version < "4.0" -sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.7" and python_version < "4.0" -sphinxcontrib-qthelp==1.0.3 ; python_version >= "3.7" and python_version < "4.0" -sphinxcontrib-serializinghtml==1.1.5 ; python_version >= "3.7" and python_version < "4.0" -tldextract==3.4.0 ; python_version >= "3.7" and python_version < "4.0" -tomli==2.0.1 ; python_version >= "3.7" and python_full_version <= "3.11.0a6" -tomlkit==0.11.6 ; python_version >= "3.7" and python_version < "4.0" -tox==3.27.1 ; python_version >= "3.7" and python_version < "4.0" -traitlets==5.7.1 ; python_version >= "3.7" and python_version < "4.0" -twine==4.0.2 ; python_version >= "3.7" and python_version < "4.0" -typed-ast==1.5.4 ; python_version >= "3.7" and python_version < "3.8" -types-cryptography==3.3.23.2 ; python_version >= "3.7" and python_version < "4.0" -types-mock==4.0.15.2 ; python_version >= "3.7" and python_version < "4.0" -types-pyopenssl==22.1.0.2 ; python_version >= "3.7" and python_version < "4.0" -types-pyrfc3339==1.1.1.1 ; python_version >= "3.7" and python_version < "4.0" -types-python-dateutil==2.8.19.4 ; python_version >= "3.7" and python_version < "4.0" -types-pytz==2022.6.0.1 ; python_version >= "3.7" and python_version < "4.0" -types-requests==2.28.11.5 ; python_version >= "3.7" and python_version < "4.0" -types-setuptools==65.6.0.2 ; python_version >= "3.7" and python_version < "4.0" -types-six==1.16.21.4 ; python_version >= "3.7" and python_version < "4.0" -types-urllib3==1.26.25.4 ; python_version >= "3.7" and python_version < "4.0" -typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0" -uritemplate==4.1.1 ; python_version >= "3.7" and python_version < "4.0" -urllib3==1.26.13 ; python_version >= "3.7" and python_version < "4.0" -virtualenv==20.17.1 ; python_version >= "3.7" and python_version < "4.0" -wcwidth==0.2.5 ; python_version >= "3.7" and python_version < "4.0" -webencodings==0.5.1 ; python_version >= "3.7" and python_version < "4.0" -wheel==0.38.4 ; python_version >= "3.7" and python_version < "4.0" -wrapt==1.14.1 ; python_version >= "3.7" and python_version < "4.0" -xattr==0.9.9 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "darwin" -yarg==0.1.9 ; python_version >= "3.7" and python_version < "4.0" -zipp==3.11.0 ; python_version >= "3.7" and python_version < "4.0" -zope-component==5.0.1 ; python_version >= "3.7" and python_version < "4.0" -zope-event==4.6 ; python_version >= "3.7" and python_version < "4.0" -zope-hookable==5.4 ; python_version >= "3.7" and python_version < "4.0" -zope-interface==5.5.2 ; python_version >= "3.7" and python_version < "4.0" diff --git a/tools/docker/README.md b/tools/docker/README.md index 799ddcd0b..19a69935f 100644 --- a/tools/docker/README.md +++ b/tools/docker/README.md @@ -20,12 +20,18 @@ DNS plugin Docker images to Docker Hub. High-level behavior ------------------- -Running `./build.sh all && ./deploy.sh all` causes the Docker -images to be built and deployed to Docker Hub for all supported architectures -where `` is the base of the tag that should be given to the given images. -The tag should either be `nightly` or a git version tag like `v0.34.0`. The -given tag is only the base of the tag because the CPU architecture is also -added to the tag. +Running `./build.sh all` causes the Docker images to be built for all +supported architectures, where `` is the base of the tag that should be +given to the generated images. The tag should either be `nightly` or a git +version tag like `v2.2.0`. The given tag is only the base of the tag because +the CPU architecture is also added to the tag. For version tags above `v2.0.0`, +Additional tags for `latest` are also generated. The generated images are stored +in the local docker image cache. + +Running `./deploy_by_arch.sh all && ./deploy_multiarch.sh ` will +push the previously generated images to Docker Hub and then generate multi-arch +manifests for easy access to the underlying images appropriate for a given +architecture. Configuration ------------- @@ -33,4 +39,4 @@ Configuration To run these scripts you need: 1. An x86_64 machine with Docker installed and the Docker daemon running. You probably don't want to use the docker snap as these scripts have failed when using that in the past. -2. To be logged into Docker Hub with an account able to push to the Certbot and Certbot DNS Docker images on Docker Hub. +2. To be logged into Docker Hub with an account able to push to the Certbot and Certbot DNS Docker images on Docker Hub. Altering the value of `DOCKER_HUB_ORG` in `lib/common` will allow you to push to your own account for testing. diff --git a/tools/docker/build.sh b/tools/docker/build.sh index e3ff5707a..3b4916060 100755 --- a/tools/docker/build.sh +++ b/tools/docker/build.sh @@ -79,7 +79,7 @@ for TARGET_ARCH in "${ALL_REQUESTED_ARCH[@]}"; do pushd "${REPO_ROOT}" DownloadQemuStatic "${TARGET_ARCH}" QEMU_ARCH=$(GetQemuArch "${TARGET_ARCH}") - docker build \ + DOCKER_BUILDKIT=0 docker build \ --build-arg TARGET_ARCH="${TARGET_ARCH}" \ --build-arg QEMU_ARCH="${QEMU_ARCH}" \ -f "${WORK_DIR}/core/Dockerfile" \ @@ -97,7 +97,7 @@ for plugin in "${CERTBOT_PLUGINS[@]}"; do for TARGET_ARCH in "${ALL_REQUESTED_ARCH[@]}"; do QEMU_ARCH=$(GetQemuArch "${TARGET_ARCH}") BASE_IMAGE="${DOCKER_HUB_ORG}/certbot:${TARGET_ARCH}-${TAG_BASE}" - docker build \ + DOCKER_BUILDKIT=0 docker build \ --build-arg BASE_IMAGE="${BASE_IMAGE}" \ --build-arg QEMU_ARCH="${QEMU_ARCH}" \ -f "${WORK_DIR}/plugin/Dockerfile" \ diff --git a/tools/docker/deploy.sh b/tools/docker/deploy_by_arch.sh similarity index 64% rename from tools/docker/deploy.sh rename to tools/docker/deploy_by_arch.sh index f9446a991..4323086a1 100755 --- a/tools/docker/deploy.sh +++ b/tools/docker/deploy_by_arch.sh @@ -4,7 +4,7 @@ IFS=$'\n\t' # This script deploys new versions of Certbot and Certbot plugin docker images. -# Usage: ./deploy.sh [TAG] [all|amd64|arm32v6|arm64v8] +# Usage: ./deploy_by_arch.sh [TAG] [all|amd64|arm32v6|arm64v8] # with the [TAG] value corresponding the base of the tag to give the Docker # images and the 2nd value being the architecture to build snaps for. # Values should be something like `v0.34.0` or `nightly`. The given value is @@ -24,19 +24,16 @@ ParseRequestedArch "${2}" # Creates and pushes all Docker images aliases for the requested architectures # set in the environment variable ALL_REQUESTED_ARCH. If the value of the # global variable TAG_BASE is a 2.0.0 or greater version tag such as v2.1.0, -# the "latest" tag is also updated. Tags without the architecture part are also -# created for the default architecture. -# As an example, for amd64 (the default architecture) and the tag v0.35.0, the -# following tags would be created: -# - certbot/certbot:v0.35.0 -# - certbot/certbot:latest -# - certbot/certbot:amd64-latest -# For the architecture arm32v6 and the tag v0.35.0, only the following tag -# would be created: -# - certbot/certbot:arm32v6-latest -# For other tags such as "nightly", aliases are only created for the default -# architecture where the tag "nightly" would be used without an architecture -# part. +# tags for "latest" are also created. Tags such as "nightly" do not recieve +# "latest" tags. +# As an example, for the tag v2.2.0 and the default set of all target +# architectures as of writing this, the following tags would be created: +# - certbot/certbot:amd64-v2.2.0 <- image +# - certbot/certbot:arm32v6-v2.2.0 <- image +# - certbot/certbot:arm64v8-v2.2.0 <- image +# - certbot/certbot:amd64-latest <- image +# - certbot/certbot:arm32v6-latest <- image +# - certbot/certbot:arm64v8-latest <- image # Usage: TagAndPushForAllRequestedArch [IMAGE NAME] # where [IMAGE NAME] is the name of the Docker image in the Docker repository # such as "certbot" or "dns-cloudflare". @@ -51,19 +48,9 @@ TagAndPushForAllRequestedArch() { # added them, we haven't had another timeout, so until we experience # another timeout & can get the deubg logs, we're leaving them in. docker --debug push "${DOCKER_REPO}:${TARGET_ARCH}-${TAG_BASE}" - - # If TAG_BASE is a valid tag for version 2.0.0 or greater if [[ "${TAG_BASE}" =~ ^v([2-9]|[1-9][0-9]+)\.[0-9]+\.[0-9]+$ ]]; then docker tag "${DOCKER_REPO}:${TARGET_ARCH}-${TAG_BASE}" "${DOCKER_REPO}:${TARGET_ARCH}-latest" docker --debug push "${DOCKER_REPO}:${TARGET_ARCH}-latest" - if [ "${TARGET_ARCH}" == "${DEFAULT_ARCH}" ]; then - docker tag "${DOCKER_REPO}:${TARGET_ARCH}-${TAG_BASE}" "${DOCKER_REPO}:latest" - docker --debug push "${DOCKER_REPO}:latest" - fi - fi - if [ "${TARGET_ARCH}" == "${DEFAULT_ARCH}" ]; then - docker tag "${DOCKER_REPO}:${TARGET_ARCH}-${TAG_BASE}" "${DOCKER_REPO}:${TAG_BASE}" - docker --debug push "${DOCKER_REPO}:${TAG_BASE}" fi done } diff --git a/tools/docker/deploy_multiarch.sh b/tools/docker/deploy_multiarch.sh new file mode 100755 index 000000000..cfbf886a6 --- /dev/null +++ b/tools/docker/deploy_multiarch.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -euxo pipefail +IFS=$'\n\t' + +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +TAG_BASE="$1" # Eg. v0.35.0 or nightly +if [ -z "$TAG_BASE" ]; then + echo "We cannot tag Docker images with an empty string!" >&2 + exit 1 +fi +source "$WORK_DIR/lib/common" + +# Creates multiarch manifests for TAG_BASE, and 'latest' if TAG_BASE > 2.0.0 +# - certbot/certbot:v2.2.0 <- multiarch manifest +# - certbot/certbot:latest <- multiarch manifest +MakeMultiarchManifestForAllTargetArch() { + DOCKER_REPO="${DOCKER_HUB_ORG}/${1}" + SRC_IMAGES=() + for TARGET_ARCH in "${ALL_TARGET_ARCH[@]}"; do + SRC_IMAGES+=("${DOCKER_REPO}:${TARGET_ARCH}-${TAG_BASE}") + done + docker buildx imagetools create -t ${DOCKER_REPO}:${TAG_BASE} "${SRC_IMAGES[@]}" + if [[ "${TAG_BASE}" =~ ^v([2-9]|[1-9][0-9]+)\.[0-9]+\.[0-9]+$ ]]; then + docker buildx imagetools create -t ${DOCKER_REPO}:latest "${SRC_IMAGES[@]}" + fi +} + + +# Step 1: Certbot core Docker +MakeMultiarchManifestForAllTargetArch "certbot" + +# Step 2: Certbot DNS plugins Docker images +for plugin in "${CERTBOT_PLUGINS[@]}"; do + MakeMultiarchManifestForAllTargetArch "${plugin}" +done diff --git a/tools/finish_release.py b/tools/finish_release.py index 0a8400ea9..723f38004 100755 --- a/tools/finish_release.py +++ b/tools/finish_release.py @@ -4,7 +4,7 @@ Post-release script to publish artifacts created from Azure Pipelines. This currently includes: -* Moving snaps from the candidate/beta channel to the stable channel +* Moving snaps from the beta channel to the stable channel * Publishing the Windows installer in a GitHub release Setup: @@ -26,30 +26,34 @@ release with that name already exists. """ import argparse +import getpass import glob import os.path import re import subprocess import sys import tempfile -import getpass -from azure.devops.connection import Connection from zipfile import ZipFile +from azure.devops.connection import Connection import requests # Path to the root directory of the Certbot repository containing this script REPO_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -# This list contains the names of all Certbot DNS plugins -PLUGIN_SNAPS = [os.path.basename(path) for path in glob.glob(os.path.join(REPO_ROOT, 'certbot-dns-*'))] +# This list contains the names of all Certbot DNS plugins. We used to have a +# CloudXNS plugin and since it's possible devs still have that directory +# locally, we filter it out here. If it's included in this list, this script +# will crash later when it fails to find a CloudXNS snap on the snap store with +# the current version since we no longer build it. +PLUGIN_SNAPS = [os.path.basename(path) + for path in glob.glob(os.path.join(REPO_ROOT, 'certbot-dns-*')) + if not path.endswith('certbot-dns-cloudxns')] # This list contains the name of all Certbot snaps that should be published to # the stable channel. ALL_SNAPS = ['certbot'] + PLUGIN_SNAPS # This is the count of the architectures currently supported by our snaps used # for sanity checking. SNAP_ARCH_COUNT = 3 -# The percentage of users the 2.0 Certbot snap should be deployed to. -PROGRESSIVE_RELEASE_PERCENTAGE = 5 def parse_args(args): @@ -66,8 +70,7 @@ def parse_args(args): # Use the file's docstring for the help text and don't let argparse reformat it. parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('--css', type=str, help='hostname of code signing server') - parser.add_argument('--progressive-only', action='store_true', help='only do a Certbot 2.0 progressive snap release') + parser.add_argument('--css', type=str, required=True, help='hostname of code signing server') return parser.parse_args(args) @@ -171,7 +174,7 @@ def promote_snaps(snaps, source_channel, version, progressive_percentage=None): print(e.stdout) raise -def fetch_version_number(major_version): +def fetch_version_number(major_version=None): """Retrieve version number for release from Azure Pipelines :param major_version: only consider releases for the specified major @@ -198,29 +201,14 @@ def main(args): parsed_args = parse_args(args) css = parsed_args.css - version = fetch_version_number('2' if parsed_args.progressive_only else None) + version = fetch_version_number() # Once the GitHub release has been published, trying to publish it # again fails. Publishing the snaps can be done multiple times though # so we do that first to make it easier to run the script again later # if something goes wrong. - # - # We only publish all snaps to the stable channel for 1.x releases. For 2.x - # releases, we only progressively release the base Certbot snap and update - # the Windows installer. Once we feel confident enough about Certbot 2.x, - # we should stop doing 1.x releases and unconditionally publish all snaps - # and the Windows installer. - if version.startswith('1.'): - promote_snaps(ALL_SNAPS, 'candidate', version) - elif not parsed_args.progressive_only and parsed_args.css is None: - # Fail fast if we weren't given a --css argument because we're going to - # need it later. - raise ValueError('Please provide the --css command line argument') - else: - promote_snaps(['certbot'], 'beta', version, - progressive_percentage=PROGRESSIVE_RELEASE_PERCENTAGE) - if not parsed_args.progressive_only: - publish_windows(css) + promote_snaps(ALL_SNAPS, 'beta', version) + publish_windows(css) if __name__ == "__main__": main(sys.argv[1:]) diff --git a/tools/install_and_test.py b/tools/install_and_test.py index 1c84de96f..37864397e 100755 --- a/tools/install_and_test.py +++ b/tools/install_and_test.py @@ -10,6 +10,7 @@ import re import subprocess import sys + def call_with_print(command): print(command) subprocess.check_call(command, shell=True) diff --git a/tools/requirements.txt b/tools/requirements.txt index 37630073a..45db89e65 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -5,65 +5,64 @@ # requirements.txt so that is scanned by GitHub. See # https://docs.github.com/en/github/visualizing-repository-data-with-graphs/about-the-dependency-graph#supported-package-ecosystems # for more info. -alabaster==0.7.12 ; python_version >= "3.7" and python_version < "4.0" +alabaster==0.7.13 ; python_version >= "3.7" and python_version < "4.0" apacheconfig==0.3.2 ; python_version >= "3.7" and python_version < "4.0" appnope==0.1.3 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "darwin" -astroid==2.12.13 ; python_full_version >= "3.7.2" and python_version < "4.0" +astroid==2.13.5 ; python_full_version >= "3.7.2" and python_version < "4.0" attrs==22.2.0 ; python_version >= "3.7" and python_version < "4.0" azure-devops==6.0.0b4 ; python_version >= "3.7" and python_version < "4.0" babel==2.11.0 ; python_version >= "3.7" and python_version < "4.0" backcall==0.2.0 ; python_version >= "3.7" and python_version < "4.0" backports-cached-property==1.0.2 ; python_version >= "3.7" and python_version < "3.8" bcrypt==4.0.1 ; python_version >= "3.7" and python_version < "4.0" -beautifulsoup4==4.11.1 ; python_version >= "3.7" and python_version < "4.0" -bleach==5.0.1 ; python_version >= "3.7" and python_version < "4.0" -boto3==1.26.42 ; python_version >= "3.7" and python_version < "4.0" -botocore==1.29.42 ; python_version >= "3.7" and python_version < "4.0" +beautifulsoup4==4.11.2 ; python_version >= "3.7" and python_version < "4.0" +bleach==6.0.0 ; python_version >= "3.7" and python_version < "4.0" +boto3==1.26.65 ; python_version >= "3.7" and python_version < "4.0" +botocore==1.29.65 ; python_version >= "3.7" and python_version < "4.0" cachecontrol==0.12.11 ; python_version >= "3.7" and python_version < "4.0" -cachetools==5.2.0 ; python_version >= "3.7" and python_version < "4.0" +cachetools==5.3.0 ; python_version >= "3.7" and python_version < "4.0" cachy==0.3.0 ; python_version >= "3.7" and python_version < "4.0" certifi==2022.12.7 ; python_version >= "3.7" and python_version < "4" cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0" -charset-normalizer==2.1.1 ; python_version >= "3.7" and python_version < "4" +charset-normalizer==3.0.1 ; python_version >= "3.7" and python_version < "4" cleo==1.0.0a5 ; python_version >= "3.7" and python_version < "4.0" cloudflare==2.11.1 ; python_version >= "3.7" and python_version < "4.0" colorama==0.4.6 ; python_version < "4.0" and sys_platform == "win32" and python_version >= "3.7" or python_version >= "3.7" and python_version < "4.0" and platform_system == "Windows" -commonmark==0.9.1 ; python_version >= "3.7" and python_version < "4.0" configargparse==1.5.3 ; python_version >= "3.7" and python_version < "4.0" -configobj==5.0.6 ; python_version >= "3.7" and python_version < "4.0" -coverage==7.0.3 ; python_version >= "3.7" and python_version < "4.0" +configobj==5.0.8 ; python_version >= "3.7" and python_version < "4.0" +coverage==7.1.0 ; python_version >= "3.7" and python_version < "4.0" crashtest==0.3.1 ; python_version >= "3.7" and python_version < "4.0" -cryptography==39.0.0 ; python_version >= "3.7" and python_version < "4.0" -cython==0.29.32 ; python_version >= "3.7" and python_version < "4.0" +cryptography==39.0.1 ; python_version >= "3.7" and python_version < "4.0" +cython==0.29.33 ; python_version >= "3.7" and python_version < "4.0" decorator==5.1.1 ; python_version >= "3.7" and python_version < "4.0" dill==0.3.6 ; python_full_version >= "3.7.2" and python_version < "4.0" distlib==0.3.6 ; python_version >= "3.7" and python_version < "4.0" distro==1.8.0 ; python_version >= "3.7" and python_version < "4.0" dns-lexicon==3.11.7 ; python_version >= "3.7" and python_version < "4.0" -dnspython==2.2.1 ; python_version >= "3.7" and python_version < "4.0" +dnspython==2.3.0 ; python_version >= "3.7" and python_version < "4.0" docutils==0.17.1 ; python_version >= "3.7" and python_version < "4.0" dulwich==0.20.50 ; python_version >= "3.7" and python_version < "4.0" exceptiongroup==1.1.0 ; python_version >= "3.7" and python_version < "3.11" execnet==1.9.0 ; python_version >= "3.7" and python_version < "4.0" -fabric==2.7.1 ; python_version >= "3.7" and python_version < "4.0" +fabric==3.0.0 ; python_version >= "3.7" and python_version < "4.0" filelock==3.9.0 ; python_version >= "3.7" and python_version < "4.0" google-api-core==2.11.0 ; python_version >= "3.7" and python_version < "4.0" -google-api-python-client==2.70.0 ; python_version >= "3.7" and python_version < "4.0" +google-api-python-client==2.77.0 ; python_version >= "3.7" and python_version < "4.0" google-auth-httplib2==0.1.0 ; python_version >= "3.7" and python_version < "4.0" -google-auth==2.15.0 ; python_version >= "3.7" and python_version < "4.0" -googleapis-common-protos==1.57.0 ; python_version >= "3.7" and python_version < "4.0" +google-auth==2.16.0 ; python_version >= "3.7" and python_version < "4.0" +googleapis-common-protos==1.58.0 ; python_version >= "3.7" and python_version < "4.0" html5lib==1.1 ; python_version >= "3.7" and python_version < "4.0" httplib2==0.21.0 ; python_version >= "3.7" and python_version < "4.0" idna==3.4 ; python_version >= "3.7" and python_version < "4" imagesize==1.4.1 ; python_version >= "3.7" and python_version < "4.0" importlib-metadata==4.13.0 ; python_version >= "3.7" and python_version < "4.0" importlib-resources==5.10.2 ; python_version >= "3.7" and python_version < "3.9" -iniconfig==1.1.1 ; python_version >= "3.7" and python_version < "4.0" -invoke==1.7.3 ; python_version >= "3.7" and python_version < "4.0" +iniconfig==2.0.0 ; python_version >= "3.7" and python_version < "4.0" +invoke==2.0.0 ; python_version >= "3.7" and python_version < "4.0" ipdb==0.13.11 ; python_version >= "3.7" and python_version < "4.0" ipython==7.34.0 ; python_version >= "3.7" and python_version < "4.0" isodate==0.6.1 ; python_version >= "3.7" and python_version < "4.0" -isort==5.11.4 ; python_full_version >= "3.7.2" and python_version < "4.0" +isort==5.11.5 ; python_full_version >= "3.7.2" and python_version < "4.0" jaraco-classes==3.2.3 ; python_version >= "3.7" and python_version < "4.0" jedi==0.18.2 ; python_version >= "3.7" and python_version < "4.0" jeepney==0.8.0 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "linux" @@ -74,27 +73,28 @@ jsonlines==3.1.0 ; python_version >= "3.7" and python_version < "4.0" jsonpickle==3.0.1 ; python_version >= "3.7" and python_version < "4.0" jsonschema==4.17.3 ; python_version >= "3.7" and python_version < "4.0" keyring==23.13.1 ; python_version >= "3.7" and python_version < "4.0" -lazy-object-proxy==1.8.0 ; python_full_version >= "3.7.2" and python_version < "4.0" +lazy-object-proxy==1.9.0 ; python_full_version >= "3.7.2" and python_version < "4.0" lockfile==0.12.2 ; python_version >= "3.7" and python_version < "4.0" -markupsafe==2.1.1 ; python_version >= "3.7" and python_version < "4.0" +markdown-it-py==2.1.0 ; python_version >= "3.7" and python_version < "4.0" +markupsafe==2.1.2 ; python_version >= "3.7" and python_version < "4.0" matplotlib-inline==0.1.6 ; python_version >= "3.7" and python_version < "4.0" mccabe==0.7.0 ; python_full_version >= "3.7.2" and python_version < "4.0" +mdurl==0.1.2 ; python_version >= "3.7" and python_version < "4.0" more-itertools==9.0.0 ; python_version >= "3.7" and python_version < "4.0" msgpack==1.0.4 ; python_version >= "3.7" and python_version < "4.0" msrest==0.6.21 ; python_version >= "3.7" and python_version < "4.0" -mypy-extensions==0.4.3 ; python_version >= "3.7" and python_version < "4.0" -mypy==0.991 ; python_version >= "3.7" and python_version < "4.0" +mypy-extensions==1.0.0 ; python_version >= "3.7" and python_version < "4.0" +mypy==1.0.0 ; python_version >= "3.7" and python_version < "4.0" oauth2client==4.1.3 ; python_version >= "3.7" and python_version < "4.0" oauthlib==3.2.2 ; python_version >= "3.7" and python_version < "4.0" -packaging==22.0 ; python_version >= "3.7" and python_version < "4.0" -paramiko==2.12.0 ; python_version >= "3.7" and python_version < "4.0" +packaging==23.0 ; python_version >= "3.7" and python_version < "4.0" +paramiko==3.0.0 ; python_version >= "3.7" and python_version < "4.0" parsedatetime==2.6 ; python_version >= "3.7" and python_version < "4.0" parso==0.8.3 ; python_version >= "3.7" and python_version < "4.0" -pathlib2==2.3.7.post1 ; python_version >= "3.7" and python_version < "4.0" pexpect==4.8.0 ; python_version >= "3.7" and python_version < "4.0" pickleshare==0.7.5 ; python_version >= "3.7" and python_version < "4.0" -pip==22.3.1 ; python_version >= "3.7" and python_version < "4.0" -pkginfo==1.9.3 ; python_version >= "3.7" and python_version < "4.0" +pip==23.0 ; python_version >= "3.7" and python_version < "4.0" +pkginfo==1.9.6 ; python_version >= "3.7" and python_version < "4.0" pkgutil-resolve-name==1.3.10 ; python_version >= "3.7" and python_version < "3.9" platformdirs==2.6.2 ; python_version < "4.0" and python_version >= "3.7" pluggy==1.0.0 ; python_version >= "3.7" and python_version < "4.0" @@ -119,12 +119,12 @@ pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0" pyrfc3339==1.1 ; python_version >= "3.7" and python_version < "4.0" pyrsistent==0.19.3 ; python_version >= "3.7" and python_version < "4.0" pytest-cov==4.0.0 ; python_version >= "3.7" and python_version < "4.0" -pytest-xdist==3.1.0 ; python_version >= "3.7" and python_version < "4.0" -pytest==7.2.0 ; python_version >= "3.7" and python_version < "4.0" +pytest-xdist==3.2.0 ; python_version >= "3.7" and python_version < "4.0" +pytest==7.2.1 ; python_version >= "3.7" and python_version < "4.0" python-augeas==1.1.0 ; python_version >= "3.7" and python_version < "4.0" python-dateutil==2.8.2 ; python_version >= "3.7" and python_version < "4.0" python-digitalocean==1.17.0 ; python_version >= "3.7" and python_version < "4.0" -pytz==2022.7 ; python_version >= "3.7" and python_version < "4.0" +pytz==2022.7.1 ; python_version >= "3.7" and python_version < "4.0" pywin32-ctypes==0.2.0 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" pywin32==305 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" pyyaml==6.0 ; python_version >= "3.7" and python_version < "4.0" @@ -133,15 +133,15 @@ requests-download==0.1.2 ; python_version >= "3.7" and python_version < "4.0" requests-file==1.5.1 ; python_version >= "3.7" and python_version < "4.0" requests-oauthlib==1.3.1 ; python_version >= "3.7" and python_version < "4.0" requests-toolbelt==0.9.1 ; python_version >= "3.7" and python_version < "4.0" -requests==2.28.1 ; python_version >= "3.7" and python_version < "4" +requests==2.28.2 ; python_version >= "3.7" and python_version < "4" rfc3986==2.0.0 ; python_version >= "3.7" and python_version < "4.0" -rich==13.0.0 ; python_version >= "3.7" and python_version < "4.0" +rich==13.3.1 ; python_version >= "3.7" and python_version < "4.0" rsa==4.9 ; python_version >= "3.7" and python_version < "4" s3transfer==0.6.0 ; python_version >= "3.7" and python_version < "4.0" secretstorage==3.3.3 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "linux" semantic-version==2.10.0 ; python_version >= "3.7" and python_version < "4.0" setuptools-rust==1.5.2 ; python_version >= "3.7" and python_version < "4.0" -setuptools==65.6.3 ; python_version >= "3.7" and python_version < "4.0" +setuptools==67.2.0 ; python_version >= "3.7" and python_version < "4.0" shellingham==1.5.0.post1 ; python_version >= "3.7" and python_version < "4.0" six==1.16.0 ; python_version >= "3.7" and python_version < "4.0" snowballstemmer==2.2.0 ; python_version >= "3.7" and python_version < "4.0" @@ -158,26 +158,26 @@ tldextract==3.4.0 ; python_version >= "3.7" and python_version < "4.0" tomli==2.0.1 ; python_version >= "3.7" and python_full_version <= "3.11.0a6" tomlkit==0.11.6 ; python_version < "4.0" and python_version >= "3.7" tox==3.28.0 ; python_version >= "3.7" and python_version < "4.0" -traitlets==5.8.0 ; python_version >= "3.7" and python_version < "4.0" +traitlets==5.9.0 ; python_version >= "3.7" and python_version < "4.0" twine==4.0.2 ; python_version >= "3.7" and python_version < "4.0" typed-ast==1.5.4 ; python_version < "3.8" and python_version >= "3.7" -types-cryptography==3.3.23.2 ; python_version >= "3.7" and python_version < "4.0" -types-pyopenssl==23.0.0.0 ; python_version >= "3.7" and python_version < "4.0" +types-docutils==0.19.1.3 ; python_version >= "3.7" and python_version < "4.0" +types-pyopenssl==23.0.0.2 ; python_version >= "3.7" and python_version < "4.0" types-pyrfc3339==1.1.1.1 ; python_version >= "3.7" and python_version < "4.0" -types-python-dateutil==2.8.19.5 ; python_version >= "3.7" and python_version < "4.0" -types-pytz==2022.7.0.0 ; python_version >= "3.7" and python_version < "4.0" -types-requests==2.28.11.7 ; python_version >= "3.7" and python_version < "4.0" -types-setuptools==65.6.0.2 ; python_version >= "3.7" and python_version < "4.0" +types-python-dateutil==2.8.19.6 ; python_version >= "3.7" and python_version < "4.0" +types-pytz==2022.7.1.0 ; python_version >= "3.7" and python_version < "4.0" +types-requests==2.28.11.12 ; python_version >= "3.7" and python_version < "4.0" +types-setuptools==67.1.0.2 ; python_version >= "3.7" and python_version < "4.0" types-six==1.16.21.4 ; python_version >= "3.7" and python_version < "4.0" -types-urllib3==1.26.25.4 ; python_version >= "3.7" and python_version < "4.0" +types-urllib3==1.26.25.5 ; python_version >= "3.7" and python_version < "4.0" typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0" uritemplate==4.1.1 ; python_version >= "3.7" and python_version < "4.0" -urllib3==1.26.13 ; python_version >= "3.7" and python_version < "4.0" -virtualenv==20.17.1 ; python_version >= "3.7" and python_version < "4.0" -wcwidth==0.2.5 ; python_version >= "3.7" and python_version < "4.0" +urllib3==1.26.14 ; python_version >= "3.7" and python_version < "4.0" +virtualenv==20.18.0 ; python_version >= "3.7" and python_version < "4.0" +wcwidth==0.2.6 ; python_version >= "3.7" and python_version < "4.0" webencodings==0.5.1 ; python_version >= "3.7" and python_version < "4.0" wheel==0.38.4 ; python_version >= "3.7" and python_version < "4.0" wrapt==1.14.1 ; python_full_version >= "3.7.2" and python_version < "4.0" xattr==0.9.9 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "darwin" yarg==0.1.9 ; python_version >= "3.7" and python_version < "4.0" -zipp==3.11.0 ; python_version >= "3.7" and python_version < "4.0" +zipp==3.12.1 ; python_version >= "3.7" and python_version < "4.0" diff --git a/tools/snap/build_remote.py b/tools/snap/build_remote.py index 7a4eea1b0..092acfa3e 100755 --- a/tools/snap/build_remote.py +++ b/tools/snap/build_remote.py @@ -48,7 +48,7 @@ def _snap_log_name(target: str, arch: str): def _execute_build( target: str, archs: Set[str], status: Dict[str, Dict[str, str]], - workspace: str) -> Tuple[int, List[str]]: + workspace: str, output_lock: Lock) -> Tuple[int, List[str]]: # snapcraft remote-build accepts a --build-id flag with snapcraft version # 5.0+. We make use of this feature to set a unique build ID so a fresh @@ -68,18 +68,28 @@ def _execute_build( environ['XDG_CACHE_HOME'] = tempdir process = subprocess.Popen([ 'snapcraft', 'remote-build', '--launchpad-accept-public-upload', - '--build-on', ','.join(archs), '--build-id', build_id], + '--build-for', ','.join(archs), '--build-id', build_id], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, env=environ, cwd=workspace) + killed = False process_output: List[str] = [] for line in process.stdout: process_output.append(line) _extract_state(target, line, status) - if any(state for state in status[target].values() if state == 'Chroot problem'): - # On this error the snapcraft process stales. Let's finish it. + if not killed and any(state for state in status[target].values() if state == 'Chroot problem'): + # On this error the snapcraft process hangs. Let's finish it. + # + # killed is used to stop us from executing this code path + # multiple times per build that encounters "Chroot problem". + with output_lock: + print('Chroot problem encountered for build ' + f'{target} for {",".join(archs)}.\n' + 'Launchpad seems to be unable to recover from this ' + 'state so we are terminating the build.') process.kill() + killed = True process_state = process.wait() @@ -89,8 +99,6 @@ def _execute_build( def _build_snap( target: str, archs: Set[str], status: Dict[str, Dict[str, str]], running: Dict[str, bool], output_lock: Lock) -> bool: - status[target] = {arch: '...' for arch in archs} - if target == 'certbot': workspace = CERTBOT_DIR else: @@ -99,7 +107,10 @@ def _build_snap( build_success = False retry = 3 while retry: - exit_code, process_output = _execute_build(target, archs, status, workspace) + # Let's reset the status before each build so we're not starting with + # old state values. + status[target] = {arch: '...' for arch in archs} + exit_code, process_output = _execute_build(target, archs, status, workspace, output_lock) with output_lock: print(f'Build {target} for {",".join(archs)} (attempt {4-retry}/3) ended with ' f'exit code {exit_code}.') diff --git a/tox.ini b/tox.ini index a3436e95a..2bdd5e25c 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ [tox] skipsdist = true -envlist = {py3-cover,lint,mypy}-{win,posix} +envlist = {cover,lint,mypy}-{win,posix} [base] # pip installs the requested packages in editable mode