Testing

django-ca uses pytest for running the test suite:

$ pytest -v

This will generate a code coverage report in docs/build/html/.

Test coverage

The test suite must ensure 100% test coverage. Completely excluding code from test coverage is only allowed when absolutely necessary.

Conditional pragmas

In addition to the standard # pragma: no cover and # pragma: no branch, the test suite adds pragmas to exclude code based on the Python version or library versions. For example:

if sys.version_info >= (3, 8):  # pragma: only py>=3.8
   from typing import Literal
else:  # pragma: only py<3.8
   from typing_extensions import Literal

If you have branches that are only relevant for some versions, there’s also pragmas for that:

if sys.version_info >= (3, 8):  # pragma: py>=3.8 branch
   print("Do something that's only useful in Python 3.8 or newer.")
if django.VERSION[:2] >= (3, 2):  # pragma: django>=3.2 branch
   print("Do something that's only useful in Django 3.2 or newer.")

You can use all operators (<, <=, ==, !=, >, >=), and we add pragma for the versions of Python, Django, cryptography.

Please check ca/django_ca/tests/base/pragmas.py for a tested file that includes all supported pragmas. Correctly using the pragmas is mandatory, as they are also used for finding outdated code when older versions are deprecated.

pytest

Fixtures

Pytest fixtures used throughout the code base.

django_ca.tests.base.fixtures.any_cert(request: SubRequest) Iterator[Certificate][source]

Parametrized fixture for absolutely any certificate name.

django_ca.tests.base.fixtures.ca(request: SubRequest) Iterator[CertificateAuthority][source]

Parametrized fixture for all certificate authorities known to the test suite.

django_ca.tests.base.fixtures.ca_name(request: SubRequest) Iterator[str][source]

Fixture for a name suitable for a CA.

django_ca.tests.base.fixtures.certificate_policies(request: SubRequest, certificate_policies_value: CertificatePolicies) Iterator[Extension[CertificatePolicies]][source]

Parametrized fixture yielding different x509.Extension[x509.CertificatePolicies] objects.

django_ca.tests.base.fixtures.certificate_policies_value(request: SubRequest) Iterator[CertificatePolicies][source]

Parametrized fixture with different CertificatePolicies objects.

django_ca.tests.base.fixtures.clear_cache() Iterator[None][source]

Fixture to clear the cache after the test.

django_ca.tests.base.fixtures.ed_ca(request: SubRequest) Iterator[CertificateAuthority][source]

Parametrized fixture for CAs with an Edwards-curve algorithm (ed448, ed25519).

django_ca.tests.base.fixtures.hostname(ca_name: str) Iterator[str][source]

Fixture for a hostname.

The value is unique for each test, and it includes the CA name, which includes the test name.

django_ca.tests.base.fixtures.interesting_cert(request: SubRequest) Iterator[Certificate][source]

Parametrized fixture for “interesting” certificates.

A function using this fixture will be called once for each interesting certificate.

django_ca.tests.base.fixtures.key_backend(request: SubRequest) Iterator[StoragesBackend][source]

Return a StoragesBackend for creating a new CA.

django_ca.tests.base.fixtures.precertificate_signed_certificate_timestamps_pub(request: SubRequest) Iterator[Certificate][source]

Parametrized fixture for certificates that have a PrecertSignedCertificateTimestamps extension.

django_ca.tests.base.fixtures.rfc4514_subject(subject: Name) Iterator[str][source]

Fixture for an RFC 4514 formatted name to use for a subject.

The common name is based on hostname() and identical to subject().

django_ca.tests.base.fixtures.secondary_backend(request: SubRequest) Iterator[StoragesBackend][source]

Return a StoragesBackend for the secondary key backend.

django_ca.tests.base.fixtures.signed_certificate_timestamp_pub(request: SubRequest) Iterator[Certificate][source]

Parametrized fixture for certificates that have any SCT extension.

django_ca.tests.base.fixtures.signed_certificate_timestamps_pub(request: SubRequest) Iterator[Certificate][source]

Parametrized fixture for certificates that have a SignedCertificateTimestamps extension.

Note

There are no certificates with this extension right now, so this fixture is in fact never run.

django_ca.tests.base.fixtures.subject(hostname: str) Iterator[Name][source]

Fixture for a Name to use for a subject.

The common name is based on hostname() and identical to rfc4514_subject().

django_ca.tests.base.fixtures.tmpcadir(tmp_path: Path, settings: SettingsWrapper) Iterator[Path][source]

Fixture to create a temporary directory for storing files using the StoragesBackend.

django_ca.tests.base.fixtures.usable_ca(request: SubRequest) Iterator[CertificateAuthority][source]

Parametrized fixture for every usable CA (with usable private key).

django_ca.tests.base.fixtures.usable_ca_name(request: SubRequest) Iterator[CertificateAuthority][source]

Parametrized fixture for the name of every usable CA.

django_ca.tests.base.fixtures.usable_cas(request: SubRequest) Iterator[list[CertificateAuthority]][source]

Fixture for all usable CAs as a list.

django_ca.tests.base.fixtures.usable_cert(request: SubRequest) Iterator[Certificate][source]

Parametrized fixture for every {ca}-cert certificate.

The name of the certificate can be retrieved from the non-standard test_name property of the certificate.

Generated fixtures

{name}_pub - Certificate

Certificate loaded from test fixture data.

Available for every CA generated in the test fixtures and every certificate. Examples: root_pub, root_cert_pub, profile_server_pub. Contributed certificates are prefixed with contrib_ (see below).

{ca_name} - CertificateAuthority

Certificate authority model without usable private key files.

Available for every CA generated in the test fixtures. Using this fixture enables database access.

{cert} - Certificate

Certificate model for certificates generated in test fixture data.

contrib_{ca_name} - CertificateAuthority

Certificate authority model for a contributed certificate.

Examples: contrib_godaddy_g2_root, contrib_geotrust and contrib_startssl_class3.

contrib_{ca_name}_cert - Certificate

Certificate model for contributed certificates loaded from test fixture data.

Examples: contrib_godaddy_g2_root_cert, contrib_geotrust_cert and contrib_startssl_class3_cert.

contrib_{ca_name}_cert_pub - Certificate

Certificate for contributed certificates loaded from test fixture data.

Examples: contrib_godaddy_g2_root_cert_pub, contrib_geotrust_cert_pub and contrib_startssl_class3_cert_pub.

contrib_{ca_name}_pub - Certificate

Certificate for contributed certificate authorities loaded from test fixture data.

Examples: contrib_godaddy_g2_root_pub, contrib_geotrust_pub and contrib_startssl_class3_pub.

usable_{ca_name} - CertificateAuthority

Certificate authority model with usable private key files.

Available for every CA generated in the test fixtures.

Mocks

Mocks used in the test suite.

django_ca.tests.base.mocks.mock_signal(signal: Signal) Iterator[Mock][source]

Context manager to attach a mock to the given Django signal.

Note that this mock does not mock that the signal is sent, but just attaches a mock to the Signal. Any other signal handler connected would still be called.

Assertions

django_ca.tests.base.assertions collects assertions used throughout the entire test suite.

django_ca.tests.base.assertions.assert_authority_key_identifier(issuer: CertificateAuthority, cert: X509CertMixin) None[source]

Assert the AuthorityKeyIdentifier extension of issuer.

This assertion tests that AuthorityKeyIdentifier extension of cert matches the SubjectKeyIdentifier extension of issuer.

django_ca.tests.base.assertions.assert_ca_properties(ca: ~django_ca.models.CertificateAuthority, name: str, parent: ~django_ca.models.CertificateAuthority | None = None, private_key_type: type[~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey | ~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey | ~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey | ~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey | ~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey] = <class 'cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey'>, acme_enabled: bool = False, acme_profile: str | None = None, acme_requires_contact: bool = True, crl_number: str = '{"scope": {}}', password: bytes | None = None) None[source]

Assert some basic properties of a CA.

django_ca.tests.base.assertions.assert_certificate(cert: ~django_ca.models.Certificate | ~django_ca.models.CertificateAuthority, subject: ~cryptography.x509.name.Name, algorithm: type[~cryptography.hazmat.primitives.hashes.HashAlgorithm] = <class 'cryptography.hazmat.primitives.hashes.SHA512'>, parent: ~django_ca.models.CertificateAuthority | None = None) None[source]

Assert certificate properties.

django_ca.tests.base.assertions.assert_command_error(msg: str) Iterator[None][source]

Context manager asserting that CommandError is raised.

Parameters:
msgstr

The regex matching the exception message.

django_ca.tests.base.assertions.assert_create_ca_signals(pre: bool = True, post: bool = True) Iterator[tuple[Mock, Mock]][source]

Context manager asserting that the pre_create_ca/post_create_ca signals are (not) called.

django_ca.tests.base.assertions.assert_create_cert_signals(pre: bool = True, post: bool = True) Iterator[tuple[Mock, Mock]][source]

Context manager asserting that the pre_create_cert/post_create_cert signals are (not) called.

django_ca.tests.base.assertions.assert_crl(crl: bytes, expected: Sequence[X509CertMixin] | None = None, signer: CertificateAuthority | None = None, expires: int = 86400, algorithm: HashAlgorithm | None = None, encoding: Encoding = Encoding.PEM, idp: Extension[IssuingDistributionPoint] | None = None, extensions: list[Extension[ExtensionType]] | None = None, crl_number: int = 0, entry_extensions: tuple[list[Extension[ExtensionType]]] | None = None) None[source]

Test the given CRL.

Parameters:
crlbytes

The raw CRL

expectedlist
signer
expires
algorithm
encoding
idp
extensions
crl_number
django_ca.tests.base.assertions.assert_e2e_command_error(cmd: Sequence[str], stdout: str | bytes | Pattern = '', stderr: str | bytes | Pattern = '') None[source]

Assert that the passed command raises a CommandError with the given message.

django_ca.tests.base.assertions.assert_e2e_error(cmd: Sequence[str], stdout: str | bytes | Pattern = '', stderr: str | bytes | Pattern = '', code: int = 2) None[source]

Assert an error was through in an e2e command.

django_ca.tests.base.assertions.assert_extensions(cert: X509CertMixin | Certificate, extensions: Iterable[Extension[ExtensionType]], signer: CertificateAuthority | None = None, expect_defaults: bool = True) None[source]

Assert that cert has the given extensions.

django_ca.tests.base.assertions.assert_improperly_configured(msg: str) Iterator[None][source]

Shortcut for testing that the code raises ImproperlyConfigured with the given message.

django_ca.tests.base.assertions.assert_post_issue_cert(post: Mock, cert: Certificate) None[source]

Assert that the post_issue_cert signal was called with the expected certificate.

django_ca.tests.base.assertions.assert_removed_in_200(match: str | Pattern[str] | None = None) Iterator[None][source]

Assert that a RemovedInDjangoCA200Warning is emitted.

django_ca.tests.base.assertions.assert_revoked(cert: X509CertMixin, reason: str | None = None, compromised: datetime | None = None) None[source]

Assert that the certificate is now revoked.

django_ca.tests.base.assertions.assert_signature(chain: Iterable[CertificateAuthority], cert: Certificate | CertificateAuthority) None[source]

Assert that cert is properly signed by chain.

django_ca.tests.base.assertions.assert_system_exit(code: int) Iterator[None][source]

Assert that SystemExit is raised.

Admin interface

django_ca.tests.admin.assertions collects assertions used when testing the admin interface.

django_ca.tests.admin.assertions.assert_change_response(response: HttpResponse, media_css: tuple[tuple[str, str], ...] = ()) None[source]

Assert that the passed response is a model change view.

django_ca.tests.admin.assertions.assert_changelist_response(response: HttpResponse, *objects: Model) None[source]

Assert that the passed response is a model changelist view.

django_ca.tests.admin.assertions.assert_css(response: HttpResponse, path: str, media: str = 'all') None[source]

Assert that the HTML from the given response includes the mentioned CSS.

Utility functions

Utility functions used in testing.

django_ca.tests.base.utils.authority_information_access(ca_issuers: Iterable[GeneralName] | None = None, ocsp: Iterable[GeneralName] | None = None, critical: bool = False) Extension[AuthorityInformationAccess][source]

Shortcut for getting a AuthorityInformationAccess extension.

django_ca.tests.base.utils.basic_constraints(ca: bool = False, path_length: int | None = None, critical: bool = True) Extension[BasicConstraints][source]

Shortcut for getting a BasicConstraints extension.

django_ca.tests.base.utils.certificate_policies(*policies: PolicyInformation, critical: bool = False) Extension[CertificatePolicies][source]

Shortcut for getting a Certificate Policy extension.

django_ca.tests.base.utils.cmd(*args: Any, stdout: BytesIO, stderr: BytesIO, **kwargs: Any) tuple[bytes, bytes][source]
django_ca.tests.base.utils.cmd(*args: Any, stdout: BytesIO, stderr: StringIO | None = None, **kwargs: Any) tuple[bytes, str]
django_ca.tests.base.utils.cmd(*args: Any, stdout: StringIO | None = None, stderr: BytesIO, **kwargs: Any) tuple[str, bytes]
django_ca.tests.base.utils.cmd(*args: Any, stdout: StringIO | None = None, stderr: StringIO | None = None, **kwargs: Any) tuple[str, str]

Call to a manage.py command using call_command.

django_ca.tests.base.utils.cmd_e2e(args: Sequence[str], *, stdin: StringIO | bytes | None = None, stdout: StringIO | None = None, stderr: StringIO | None = None) tuple[str, str][source]
django_ca.tests.base.utils.cmd_e2e(args: Sequence[str], *, stdin: StringIO | bytes | None = None, stdout: BytesIO, stderr: StringIO | None = None) tuple[bytes, str]
django_ca.tests.base.utils.cmd_e2e(args: Sequence[str], *, stdin: StringIO | bytes | None = None, stdout: StringIO | None = None, stderr: BytesIO) tuple[str, bytes]
django_ca.tests.base.utils.cmd_e2e(args: Sequence[str], *, stdin: StringIO | bytes | None = None, stdout: BytesIO, stderr: BytesIO) tuple[bytes, bytes]

Call a management command the way manage.py does.

Unlike call_command, this method also tests the argparse configuration of the called command.

django_ca.tests.base.utils.crl_distribution_points(*distribution_points: DistributionPoint, critical: bool = False) Extension[CRLDistributionPoints][source]

Shortcut for getting a CRLDistributionPoint extension.

django_ca.tests.base.utils.distribution_point(full_name: Iterable[GeneralName] | None = None, relative_name: RelativeDistinguishedName | None = None, reasons: frozenset[ReasonFlags] | None = None, crl_issuer: Iterable[GeneralName] | None = None) DistributionPoint[source]

Shortcut for generating a single distribution point.

django_ca.tests.base.utils.dns(name: str) DNSName[source]

Shortcut to get a cryptography.x509.DNSName.

django_ca.tests.base.utils.doctest_module(module: str, name: str | None = None, globs: dict[str, str] | None = None, verbose: bool | None = False, report: bool = False, optionflags: int = 0, extraglobs: dict[str, str] | None = None, raise_on_error: bool = False, exclude_empty: bool = False) TestResults[source]

Shortcut for running doctests in the given Python module.

This function uses a custom OutputChecker to enable the STRIP_WHITESPACE doctest option. This option will remove all whitespace (including newlines) from the both actual and expected output. It is used for formatting actual output with newlines to improve readability.

This function is otherwise based on doctest.testmod. It differs in that it will interpret module as module path if a str and import the module. The report and verbose flags also default to False, as this provides cleaner output in modules with a lot of doctests.

django_ca.tests.base.utils.extended_key_usage(*usages: ObjectIdentifier, critical: bool = False) Extension[ExtendedKeyUsage][source]

Shortcut for getting an ExtendedKeyUsage extension.

django_ca.tests.base.utils.freshest_crl(*distribution_points: DistributionPoint, critical: bool = False) Extension[FreshestCRL][source]

Shortcut for getting a CRLDistributionPoints extension.

django_ca.tests.base.utils.get_cert_context(name: str) dict[str, Any][source]

Get a dictionary suitable for testing output based on the dictionary in basic.certs.

django_ca.tests.base.utils.get_idp(full_name: Iterable[GeneralName] | None = None, indirect_crl: bool = False, only_contains_attribute_certs: bool = False, only_contains_ca_certs: bool = False, only_contains_user_certs: bool = False, only_some_reasons: frozenset[ReasonFlags] | None = None, relative_name: RelativeDistinguishedName | None = None) Extension[IssuingDistributionPoint][source]

Get an IssuingDistributionPoint extension.

django_ca.tests.base.utils.idp_full_name(ca: CertificateAuthority) list[UniformResourceIdentifier] | None[source]

Get the IDP full name for ca.

django_ca.tests.base.utils.ip(name: IPv4Address | IPv6Address | IPv4Network | IPv6Network) IPAddress[source]

Shortcut to get a cryptography.x509.IPAddress.

django_ca.tests.base.utils.iso_format(value: datetime, timespec: str = 'seconds') str[source]

Convert a timestamp to ISO, with ‘Z’ instead of ‘+00:00’.

django_ca.tests.base.utils.issuer_alternative_name(*names: GeneralName, critical: bool = False) Extension[IssuerAlternativeName][source]

Shortcut for getting a IssuerAlternativeName extension.

django_ca.tests.base.utils.key_usage(**usages: bool) Extension[KeyUsage][source]

Shortcut for getting a KeyUsage extension.

django_ca.tests.base.utils.mock_slug() Iterator[str][source]

Mock random slug generation, yields the static value.

django_ca.tests.base.utils.name_constraints(permitted: Iterable[GeneralName] | None = None, excluded: Iterable[GeneralName] | None = None, critical: bool = True) Extension[NameConstraints][source]

Shortcut for getting a NameConstraints extension.

django_ca.tests.base.utils.ocsp_no_check(critical: bool = False) Extension[OCSPNoCheck][source]

Shortcut for getting a OCSPNoCheck extension.

class django_ca.tests.base.utils.override_tmpcadir(**kwargs)[source]

Sets the CA_DIR directory to a temporary directory.

django_ca.tests.base.utils.precert_poison() Extension[PrecertPoison][source]

Shortcut for getting a PrecertPoison extension.

django_ca.tests.base.utils.rdn(name: Iterable[tuple[ObjectIdentifier, str]]) RelativeDistinguishedName[source]

Shortcut to get a cryptography.x509.RelativeDistinguishedName.

django_ca.tests.base.utils.subject_alternative_name(*names: GeneralName, critical: bool = False) Extension[SubjectAlternativeName][source]

Shortcut for getting a SubjectAlternativeName extension.

django_ca.tests.base.utils.subject_key_identifier(cert: X509CertMixin | Certificate) Extension[SubjectKeyIdentifier][source]

Shortcut for getting a SubjectKeyIdentifier extension.

django_ca.tests.base.utils.tls_feature(*features: TLSFeatureType, critical: bool = False) Extension[TLSFeature][source]

Shortcut for getting a TLSFeature extension.

django_ca.tests.base.utils.uri(url: str) UniformResourceIdentifier[source]

Shortcut to get a cryptography.x509.UniformResourceIdentifier.