Repository navigation
Share the card doubles behind one adapter - #20
Merged
Merged
Conversation
The suite carries two transmit conventions: RawConnection style, which CardSession drives, and a transmit-callable returning Response, used by the SCP doubles because open_channel takes a bare callable. ResponseAdapter bridges the second into the first, so a secure-channel double can sit behind connect() and be driven by a real CardSession with the redactor, the APDU log and 6Cxx/61xx chaining all running. QueueConn existed in three byte-identical copies and _RecordingConn in a fourth file; both now live in _cardfakes alongside the wiring helpers. Two tests hold the arrangement up. An AST guard asserts that only cli.context, cli.commands.doctor and cli.commands.readers bind PC/SC entry points, since a module importing them later would bypass a fixture and reach a real reader in CI. The rest drive `piv admin authenticate` through a real SCP03 mutual authentication and assert the handshake appears on the wire with a fresh host challenge each run, so the double cannot decay into answering 9000. No src changes. admin.py goes 29% to 53%, scp03.py 86% to 90%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Share the card doubles behind one adapter
Test infrastructure only. No
srcchanges.Changes
tests/unit/_cardfakes.py:ResponseAdapter,QueueConn,RecordingConn, and thewiring helpers
wire_session,wire_pcsc,run,run_both_modes.QueueConnhad three byte-identical copies (test_desfire,test_ev2,test_fido) and_RecordingConna fourth intest_keyimport; all four now import from the shared module.tests/unit/test_card_fakes_seam.py: an import-boundary guard and four tests driving areal SCP03 mutual authentication.
Constraints
Two transmit conventions exist and both are kept.
RawConnectionstyle,transmit(list[int]) -> (list[int], sw1, sw2), is whatCardSessiondrives and what mostdoubles implement. The SCP doubles instead take
APDU | bytesand returnResponse,because
open_channeltakes a bare callable rather than a connection.ResponseAdapterbridges the second into the first, which is what lets a secure-channel double sit behind
connect()and be driven by a realCardSession, with the redactor, the--apdu-logfileand 6Cxx/61xx chaining all running rather than bypassed.
The substitution point depends on the module. 63 call sites go through
AppContext.open_session, whichwire_sessionpatches.doctorandreadersinstead dofrom ...transport.pcsc import connect, binding the names at import time, so they needwire_pcscagainst their own namespace. A module that starts importing that way later wouldslip past a fixture patched only at
open_sessionand reach a real reader in CI, sotest_only_known_modules_bind_pcsc_entry_pointswalks the AST of everysrcmodule andasserts the set is exactly
{cli/context.py, cli/commands/doctor.py, cli/commands/readers.py}.A second guard asserts
smartcardis imported nowhere buttransport/pcsc.py.Two tests exist to stop the double decaying into "answers 9000". A fake that returns
success for everything would make tests pass on command streams a real card rejects, which
is the main risk of testing against one. So one test asserts INITIALIZE UPDATE, EXTERNAL
AUTHENTICATE and a CLA 0x04 wrapped command all appear on the wire, and another asserts the
host challenge differs between runs.
os.urandomis deliberately not patched: the cardderives its cryptogram from the challenge it receives, so the handshake is computed rather
than replayed. That is also why a recorded transcript cannot cover this path.
_cardfakesis importable by basename. There is no__init__.pyundertests/, sopytest's prepend import mode puts
tests/unitonsys.pathunder bothpython -m pytestand the bare
pytestCI uses. This was checked under both entry points. The leadingunderscore keeps the module out of collection.
conftest.MockConnectionand the transcriptstay as they are; they remain the right tool for the detector's recorded-card assertions.
Verification
ruff check,ruff format --check,mypy,sphinx-build -W --keep-goingpass. The suitegoes 357 to 362, project coverage 61.16% to 61.75%,
applets/piv/admin.py29% to 53% andtransport/scp03.py86% to 90%, with 64 fewer lines of duplicated test code.No changelog entry: nothing in
srcchanged.🤖 Generated with Claude Code