Skip to content

Commit 552ed7c

Browse files
authored
Reach 100% coverage (#98)
1 parent 2b69f61 commit 552ed7c

File tree

10 files changed

+143
-41
lines changed

10 files changed

+143
-41
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ ifneq ($(TESTS),)
3333
COV_ARGS :=
3434
else
3535
TEST_ARGS :=
36-
#TODO(dm) We need to enforce this but later
37-
COV_ARGS := --fail-under 0
36+
COV_ARGS := --fail-under 100
3837
endif
3938

4039
.PHONY: all
@@ -56,7 +55,8 @@ lint: $(VENV)/pyvenv.cfg
5655
ruff check
5756
cargo fmt --check --manifest-path rust/Cargo.toml
5857
cargo fmt --check --manifest-path rust/tsp-asn1/Cargo.toml
59-
58+
. $(VENV_BIN)/activate && \
59+
interrogate -c pyproject.toml .
6060

6161
.PHONY: reformat
6262
reformat:

pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ dependencies = ["cryptography>=43,<45"]
2222
[project.optional-dependencies]
2323
doc = []
2424
test = ["pytest", "pytest-cov", "pretend", "coverage[toml]"]
25-
lint = ["ruff >= 0.7,< 0.9"]
25+
lint = ["ruff >= 0.7,< 0.9", "interrogate"]
2626
dev = ["rfc3161-client[test,lint,doc]", "maturin>=1.7,<2.0"]
2727

2828
[project.urls]
@@ -52,3 +52,14 @@ select = ["E", "F", "I", "W", "UP", "TCH"]
5252

5353
[tool.coverage.report]
5454
exclude_also = ["if TYPE_CHECKING:"]
55+
56+
[tool.interrogate]
57+
# don't enforce documentation coverage for testing, the virtual
58+
# environment, or the scripts.
59+
exclude = [
60+
".venv",
61+
"test",
62+
"scripts",
63+
]
64+
ignore-semiprivate = true
65+
fail-under = 100

src/rfc3161_client/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def data(self, data: bytes) -> TimestampRequestBuilder:
4949

5050
def hash_algorithm(self, hash_algorihtm: _AllowedHashTypes) -> TimestampRequestBuilder:
5151
"""Set the Hash algorithm used."""
52-
if hash_algorihtm not in HashAlgorithm:
52+
if not isinstance(hash_algorihtm, HashAlgorithm):
5353
msg = f"{hash_algorihtm} is not a supported hash."
5454
raise TypeError(msg)
5555

src/rfc3161_client/errors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1+
"""Errors."""
2+
3+
14
class VerificationError(Exception):
5+
"""Verification errors are raised when the verification of a Timestamp fails."""
6+
27
pass

src/rfc3161_client/tsp.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Timestamp protocol objects."""
2+
13
from __future__ import annotations
24

35
import abc
@@ -66,6 +68,8 @@ def as_bytes(self) -> bytes:
6668

6769

6870
class PKIStatus(enum.IntEnum):
71+
"""Response status."""
72+
6973
GRANTED = 0
7074
GRANTED_WITH_MODS = 1
7175
REJECTION = 2
@@ -75,6 +79,8 @@ class PKIStatus(enum.IntEnum):
7579

7680

7781
class TimeStampResponse(metaclass=abc.ABCMeta):
82+
"""Timestamp response (per RFC 3161)."""
83+
7884
@property
7985
@abc.abstractmethod
8086
def status(self) -> int:
@@ -108,6 +114,8 @@ def as_bytes(self) -> bytes:
108114

109115

110116
class Accuracy(metaclass=abc.ABCMeta):
117+
"""Accuracy of the timestamp response."""
118+
111119
@property
112120
@abc.abstractmethod
113121
def seconds(self) -> int:
@@ -128,6 +136,8 @@ def micros(self) -> int | None:
128136

129137

130138
class TimeStampTokenInfo(metaclass=abc.ABCMeta):
139+
"""Timestamp token info (per RFC 3161)."""
140+
131141
@property
132142
@abc.abstractmethod
133143
def version(self) -> int:
@@ -178,6 +188,8 @@ def name(self) -> cryptography.x509.GeneralName:
178188

179189

180190
class SignedData(metaclass=abc.ABCMeta):
191+
"""Signed data (CMS - RFC 2630)"""
192+
181193
@property
182194
@abc.abstractmethod
183195
def version(self) -> int:
@@ -205,6 +217,8 @@ def signer_infos(self) -> set[SignerInfo]:
205217

206218

207219
class SignerInfo(metaclass=abc.ABCMeta):
220+
"""Signer info (RFC 2634)."""
221+
208222
@property
209223
@abc.abstractmethod
210224
def version(self) -> int:

src/rfc3161_client/verify.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515

1616
class VerifierBuilder:
17+
"""Builder for a Verifier."""
18+
1719
def __init__(
1820
self,
1921
policy_id: cryptography.x509.ObjectIdentifier | None = None,
@@ -121,11 +123,22 @@ def from_request(cls, tsp_request: TimeStampRequest) -> VerifierBuilder:
121123

122124

123125
class Verifier(metaclass=abc.ABCMeta):
126+
"""Verifier.
127+
128+
This class should not be instantiated directly but through a VerifierBuilder.
129+
"""
130+
124131
@abc.abstractmethod
125-
def verify(self, timestamp_response: TimeStampResponse, hashed_message: bytes) -> bool: ...
132+
def verify(self, timestamp_response: TimeStampResponse, hashed_message: bytes) -> bool:
133+
"""Verify a timestamp response."""
126134

127135

128136
class _Verifier(Verifier):
137+
"""Inner implementation of the Verifier.
138+
139+
This pattern helps us ensure that the Verifier is never created directly.
140+
"""
141+
129142
def __init__(
130143
self,
131144
policy_id: cryptography.x509.ObjectIdentifier | None,
@@ -190,7 +203,9 @@ def _verify_leaf_certs(self, tsp_response: TimeStampResponse) -> bool:
190203
leaf_certificate_bytes = next(iter(tsp_response.signed_data.certificates))
191204
leaf_certificate = cryptography.x509.load_der_x509_certificate(leaf_certificate_bytes)
192205

193-
if self._tsa_certificate is not None and leaf_certificate != self._tsa_certificate:
206+
# Note: The order of comparison is important here since we mock
207+
# _tsa_certificate's __ne__ method in tests, rather than leaf_certificate's
208+
if self._tsa_certificate is not None and self._tsa_certificate != leaf_certificate:
194209
msg = "Embedded certificate does not match the one in the Verification Options."
195210
raise VerificationError(msg)
196211

test/test_base.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
class TestRequestBuilder:
10-
def test_succeeds(self):
10+
def test_succeeds(self) -> None:
1111
message = b"hello"
1212
request = TimestampRequestBuilder().data(message).build()
1313
print(request.nonce)
@@ -17,7 +17,7 @@ def test_succeeds(self):
1717
assert request.nonce is not None
1818
assert request.policy is None
1919

20-
def test_data(self):
20+
def test_data(self) -> None:
2121
with pytest.raises(ValueError):
2222
TimestampRequestBuilder().build()
2323

@@ -27,7 +27,7 @@ def test_data(self):
2727
with pytest.raises(ValueError, match="once"):
2828
TimestampRequestBuilder().data(b"hello").data(b"world")
2929

30-
def test_algorithm_sha256(self):
30+
def test_algorithm_sha256(self) -> None:
3131
message = b"random-message"
3232
request = (
3333
TimestampRequestBuilder().data(message).hash_algorithm(HashAlgorithm.SHA256).build()
@@ -38,7 +38,7 @@ def test_algorithm_sha256(self):
3838
digest.update(message)
3939
assert digest.finalize() == request.message_imprint.message
4040

41-
def test_algorithm_sha512(self):
41+
def test_algorithm_sha512(self) -> None:
4242
message = b"random-message"
4343
request = (
4444
TimestampRequestBuilder().data(message).hash_algorithm(HashAlgorithm.SHA512).build()
@@ -49,15 +49,15 @@ def test_algorithm_sha512(self):
4949
digest.update(message)
5050
assert digest.finalize() == request.message_imprint.message
5151

52-
def test_set_algorithm(self):
53-
with pytest.raises(TypeError):
52+
def test_set_algorithm(self) -> None:
53+
with pytest.raises(TypeError, match="is not a supported hash."):
5454
TimestampRequestBuilder().hash_algorithm("invalid hash algorihtm")
5555

5656
# Default hash algorithm
5757
request = TimestampRequestBuilder().data(b"hello").build()
5858
assert request.message_imprint.hash_algorithm == SHA512_OID
5959

60-
def test_cert_request(self):
60+
def test_cert_request(self) -> None:
6161
with pytest.raises(TypeError):
6262
TimestampRequestBuilder().cert_request(cert_request="not valid")
6363

@@ -67,7 +67,7 @@ def test_cert_request(self):
6767
request = TimestampRequestBuilder().cert_request(cert_request=True).data(b"hello").build()
6868
assert request.cert_req is True
6969

70-
def test_nonce(self):
70+
def test_nonce(self) -> None:
7171
with pytest.raises(TypeError):
7272
TimestampRequestBuilder().nonce(nonce="not valid")
7373

test/test_rust.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .common import SHA256_OID, SHA512_OID
55

66

7-
def test_create_timestamp_request():
7+
def test_create_timestamp_request() -> None:
88
request = create_timestamp_request(
99
data=b"hello", nonce=True, cert=False, hash_algorithm=HashAlgorithm.SHA512
1010
)

test/test_tsp.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010

1111
class TestTimestampResponse:
12-
def test_parsing_good(self):
12+
def test_parsing_good(self) -> None:
1313
timestamp_response = decode_timestamp_response((_FIXTURE / "response.tsr").read_bytes())
1414
assert timestamp_response.status == 0
1515

@@ -27,7 +27,7 @@ def test_parsing_good(self):
2727
assert tst_info.accuracy.millis is None
2828
assert tst_info.accuracy.micros is None
2929

30-
def test_equality(self):
30+
def test_equality(self) -> None:
3131
timestamp_response = decode_timestamp_response((_FIXTURE / "response.tsr").read_bytes())
3232
other_response = decode_timestamp_response((_FIXTURE / "other_response.tsr").read_bytes())
3333

@@ -36,13 +36,13 @@ def test_equality(self):
3636
(_FIXTURE / "response.tsr").read_bytes()
3737
) == decode_timestamp_response((_FIXTURE / "response.tsr").read_bytes())
3838

39-
def test_round_trip(self):
39+
def test_round_trip(self) -> None:
4040
timestamp_response = decode_timestamp_response((_FIXTURE / "response.tsr").read_bytes())
4141
assert timestamp_response == decode_timestamp_response(timestamp_response.as_bytes())
4242

4343

4444
class TestTimestampRequest:
45-
def test_parsing_good(self):
45+
def test_parsing_good(self) -> None:
4646
timestamp_request: TimeStampRequest = parse_timestamp_request(
4747
(_FIXTURE / "request.der").read_bytes()
4848
)
@@ -52,12 +52,12 @@ def test_parsing_good(self):
5252
assert timestamp_request.policy is None
5353
assert timestamp_request.message_imprint.message.hex().startswith("9b71d224bd62f3785d96d")
5454

55-
def test_equality(self):
55+
def test_equality(self) -> None:
5656
timestamp_request = parse_timestamp_request((_FIXTURE / "request.der").read_bytes())
5757
other_request = parse_timestamp_request((_FIXTURE / "other_request.der").read_bytes())
5858

5959
assert timestamp_request != other_request
6060

61-
def test_round_trip(self):
61+
def test_round_trip(self) -> None:
6262
timestamp_request = parse_timestamp_request((_FIXTURE / "request.der").read_bytes())
6363
assert timestamp_request == parse_timestamp_request(timestamp_request.as_bytes())

0 commit comments

Comments
 (0)