Code review findings on the "skip revoke when .crt is missing" migration
path:
- CliRunner._issue() took the skip path whenever has_issued_cert() was
False, which is also true for a typo'd/nonexistent CN — it would warn,
skip the revoke, and go on to build, package, and email a brand-new
certificate for a CN nobody asked to renew. The skip now only fires when
the CN has a current index.txt entry (via _load_current_certs()); an
unknown CN prints an error and exits 1 with nothing built. The TUI's
_process_cert() doesn't need the same guard — renewal there always opens
on an existing row (cn_readonly pins the CN), so a typo'd CN can't reach
the branch.
- Skipping the revoke leaves pki/reqs/<CN>.req and pki/private/<CN>.key in
place (normally revoke-issued archives both), which makes EasyRSA's
build-client-full abort. Both CliRunner._issue() and
CursesApp._process_cert() now check for those leftovers before building
and fail fast with the exact paths, rather than surfacing EasyRSA's
confusing error after the CA passphrase prompt. Neither path touches the
files itself.
Also: strengthened two under-specified tests (test_main_rejects_bad_days_flag
now checks the resolver's message text, not just "--days", which also
appears in argparse's unrelated error; test_show_cert_form_confirm now pins
the Enter-keypress count so a partial "days" field reversion is caught), and
folded a malformed CLAUDE.md table row into its neighbor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Renewal failed with an empty error box for any CN listed in index.txt
without a corresponding pki/issued/<CN>.crt — the state you get when an
index.txt is carried over from an older EasyRSA install but the issued/
files are not.
Two defects:
1. EasyRSA writes its diagnostics to stdout, not stderr: print() is
`printf '%s\n'`, and both die() and user_error() route through it.
stderr only carries output from the tools EasyRSA shells out to, and
even that is silenced under -S/--silent-ssl. _run_easyrsa built its
message from stderr alone, so every EasyRSA failure reported blank.
_easyrsa_diagnostics() now merges both streams (stderr first, so the
specific openssl message is not what the dialog clips) and drops the
version banner and blank padding.
2. EasyRSA reads the serial out of the .crt itself, so revoke-issued
cannot revoke a CN whose cert file is gone — and there is nothing to
add to the CRL either. has_issued_cert() now gates the revoke and CRL
steps in both CliRunner._issue() and CursesApp._process_cert(); the
workflow warns and goes straight to build-client-full. An explicit
--revoke / TUI `r` still fails loudly rather than silently no-op.
The post-build failure message now keys off whether a revoke actually
happened, not off is_renewal, so it no longer claims "has been revoked"
when nothing was.
Adds tests/test_app_reissue.py: the TUI re-issue path had no coverage at
all, and it is the path this bug was reported from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>