diff --git a/tests/test_trace_adapter_refusals.py b/tests/test_trace_adapter_refusals.py index 956e6d0..1578b79 100644 --- a/tests/test_trace_adapter_refusals.py +++ b/tests/test_trace_adapter_refusals.py @@ -23,6 +23,7 @@ import json import sys import unittest +from unittest import mock from dataclasses import replace from pathlib import Path @@ -176,25 +177,35 @@ def refuse(name, *args, **kwargs): builtins.__import__ = original def test_a_failure_inside_official_signing_is_reported_as_one(self): - """A key that satisfies the shape check and then fails when used. + """A key that satisfies the type check and then fails when used. - `object()` would not reach here: it is refused earlier for having no - `sign`, so a case built that way passes on the wrong refusal. - """ - key = self.key + `object()` would not reach here, and neither does a duck type: since + agentrust-trace 0.10.0 the signing path requires a real + Ed25519PrivateKey rather than anything carrying a `sign` method, so a + hand-rolled stand-in is refused for the wrong reason and this test + would pass on a refusal it is not about. - class FailsWhenUsed: - def sign(self, *args, **kwargs): - raise RuntimeError("hardware signer unavailable") + A mock specced to Ed25519PrivateKey satisfies that isinstance check and + still fails when actually used, which is the case this covers: a real + key whose hardware backing is unavailable at the moment of signing. + """ + # Imported here rather than at module scope: this class is skipped + # below Python 3.11, where the trace extra and therefore cryptography + # are not installed at all, and a module-level import would break + # collection before the skip can apply. + from cryptography.hazmat.primitives.asymmetric.ed25519 import ( + Ed25519PrivateKey, + ) - def public_key(self): - return key.public_key() + FailsWhenUsed = mock.MagicMock(spec=Ed25519PrivateKey) + FailsWhenUsed.sign.side_effect = RuntimeError("hardware signer unavailable") + FailsWhenUsed.public_key.return_value = self.key.public_key() snapshot = self.snapshot( [fixture("policy-decision.json"), fixture("data-flow.json")] ) with self.assertRaises(TraceFinalizationError) as caught: - finalize_trace(snapshot, self.config, signing_key=FailsWhenUsed()) + finalize_trace(snapshot, self.config, signing_key=FailsWhenUsed) message = str(caught.exception) self.assertIn("official TRACE signing or validation failed", message) self.assertIn("hardware signer unavailable", message)