crates/gunnar-cli/tests/issuer_cli.rs
#![cfg(feature = "server")]
//! **`gunnar issuer show` reads through a handle that cannot sign**
//! — identity from a certificate.
//!
//! # Why this file exists at all
//!
//! Moving the control plane to redb bought exactly one thing: the catalog, the
//! identity table and the issuer *certificate* are readable without opening a
//! repository. `gunnar issuer show` is a control-plane read, and the handle it
//! reaches for is [`ServerStores::pki_readonly`], which refuses every
//! secret-store call **by construction**. One edit pointing `show` at
//! [`ServerStores::pki`] instead turns a read that cannot sign into one that
//! can, and nothing else in the suite would say so.
//!
//! # 🔴 What this file USED to claim, and why it stopped
//!
//! Until 2026-08-30 the second half of this test was a *dependency* claim: it
//! asserted `stores.pki()` **fails** here with a message naming the missing
//! storage engine, on the reasoning that the sealed secret store lived in the
//! control *repository* and a repository needs an engine. Both halves of that
//! premise are gone:
//!
//! * the secrets are rows in `control.redb` (`gunnar_core::secrets`), so
//! `pki()` opens no repository and needs no engine — `stores.rs` says so in
//! its own doc: *"since the secrets moved into `control.redb`, 'without an
//! engine' describes both, and the distinction this method carries is a
//! capability one rather than a dependency one"*;
//! * and this test crate is not engine-free either. `gunnar-cli`'s `server`
//! feature **names** `gunnar-server/engine-znippy`, and `gunnar-server`
//! refuses to compile with no engine at all, so an engine-free build of this
//! binary cannot be produced to observe the failure from.
//!
//! So the old assertion was a wrong premise on both sides, and it failed with
//! its own `.expect` message — *"a build with no storage engine cannot open the
//! sealed secret store"* — which read like a product defect and was not one.
//!
//! **This is a deliberate weakening on one axis and a strengthening on the
//! other.** What is no longer asserted here: that the read needs no engine.
//! Nothing in this crate can assert it. What is asserted instead, and could not
//! be before: that the two handles differ **on the same store in the same
//! process** — the writable one mints, the read-only one refuses, and the
//! read-only one then reads back what the writable one wrote. A `pki_readonly`
//! that was quietly `pki` passes neither of the last two.
//!
//! That the read-only handle *refuses* rather than answering "there is no
//! issuer" is enforced in `gunnar_core::Pki::read_only` and proved at the source
//! by `gunnar-core/tests/issuer.rs`; what is asserted **here** is that the CLI's
//! own accessors are those two handles.
use gunnar_cli::server::ServerStores;
/// A `--data-root` with nothing in it, exactly as a first boot has.
fn stores() -> (tempfile::TempDir, ServerStores) {
let dir = tempfile::tempdir().expect("tempdir");
let stores = ServerStores::open(dir.path()).expect("opening the server's stores");
(dir, stores)
}
/// **The read-only handle reads, and it cannot sign — measured against the
/// writable handle on the same store.**
///
/// All of it in one test on purpose. "The read-only handle refused to mint"
/// would also be true of a handle over a broken or empty store, and "the store
/// holds an issuer" would be true of a `pki_readonly` that was secretly `pki`.
/// The pairing is what separates the two.
#[test]
fn the_issuer_is_read_through_a_handle_that_cannot_sign() {
let (_dir, stores) = stores();
let reader = stores
.pki_readonly()
.expect("reading the issuer must not need a repository");
assert!(
reader.issuer().expect("reading the issuer").is_none(),
"a fresh --data-root has no issuer, and that is `None` rather than a fault"
);
assert!(
reader
.trusted_cas()
.expect("reading the CA list")
.is_empty(),
"and no trusted CAs"
);
// ── the capability half: this handle may not write ─────────────────────
reader
.create("nope", gunnar_core::DEFAULT_ISSUER_DAYS)
.expect_err("a read-only handle must not be able to mint an issuer");
assert!(
stores
.pki_readonly()
.expect("re-open")
.issuer()
.expect("reading")
.is_none(),
"and the refused create must have committed nothing"
);
// ── the paired success, and it is what makes the refusal mean something ─
//
// The SAME store, the SAME process, one handle apart. If minting failed
// here too, the refusal above would be about the store rather than about
// the handle, and this file would prove nothing about `issuer show`
// reaching for the right accessor.
let minted = stores
.pki()
.expect("the writable handle opens on the same store")
.create("gunnar-test-issuer", gunnar_core::DEFAULT_ISSUER_DAYS)
.expect("the writable handle mints");
// ── applied output: the read-only handle reads what the other wrote ────
let seen = stores
.pki_readonly()
.expect("re-open the read-only handle")
.issuer()
.expect("reading the issuer")
.expect("the issuer the writable handle just minted must be readable");
assert_eq!(
seen.fingerprint, minted.fingerprint,
"the read-only handle is not reading the table the writable one wrote to, so the two \
are not two views of one store"
);
}