test: implement coverage #005 Tier-A suggestions (S1-S4, S8)

Add 165 tests covering the directly-testable surface identified in
coverage review #005. Workspace coverage rises 87.1% -> 91.2%
(5759/6615 -> 6505/7135); all 389 tests pass, clippy clean.

- S1 (connection.rs): dispatch_envelope across all five event-type arms
  for Call + Subscribe, plus SubscriptionStream poll_next branches and
  SubscriptionStream::closed.
- S2 (types.rs): map_quinn/iroh_connection_error for TimedOut/Reset/
  ApplicationClosed/other, plus HandlerError + StreamError Debug/Display/
  source for every variant.
- S3 (config.rs): Ed25519SecretKey from_bytes/as_bytes round-trip,
  sign+verify, tampered-message rejection, Debug non-leakage.
- S4 (endpoint.rs): build_rustls_server_config RawKey/SelfSigned/Acme
  arms, build_quinn_server_config_from_rustls, load_private_key/
  load_cert_chain error paths, has_iroh_identity branches,
  AcceptAnyCertVerifier trait methods, Ed25519SigningKey trait impls
  (choose_scheme both branches, algorithm, public_key, sign, scheme),
  RawKeyCertResolver + AlknetEndpoint Debug. endpoint.rs 56% -> 73%.
- S8 (vault protocol.rs): the existing redacted-deserialize test passed
  for the wrong reason (JSON string failed Vec<u8> coercion before the
  guard). Two new tests exercise the guard directly via a [REDACTED] byte
  array (rejected) and a real payload (accepted). protocol.rs -> 100%.

Deferred to follow-up: S5 (loopback quinn integration test, the real
unlock for accept/dispatch/stream paths), S6 (ACME event-loop extraction),
S7 (adapter abort arm). Review #005 updated with the resolution.
This commit is contained in:
2026-06-25 05:43:59 +00:00
parent 32dcc05658
commit 011db05a52
6 changed files with 841 additions and 3 deletions

View File

@@ -354,7 +354,7 @@ mod tests {
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Mutex as StdMutex;
use std::time::Duration;
use std::time::{Duration, Instant};
struct StubConnection {
alpn: &'static [u8],
@@ -582,4 +582,199 @@ mod tests {
}
assert!(captured.read().contains_key("worker/exec"));
}
// --- dispatch_envelope -------------------------------------------------
fn empty_pending() -> Arc<Mutex<PendingRequestMap>> {
Arc::new(Mutex::new(PendingRequestMap::new()))
}
#[tokio::test]
async fn dispatch_envelope_responded_resolves_call_receiver() {
let pending = empty_pending();
let rx = pending
.lock()
.register_call("req-1".to_string(), Instant::now() + Duration::from_secs(30), None);
let envelope = EventEnvelope::responded("req-1", serde_json::json!({"v": 42}));
dispatch_envelope(&pending, envelope);
assert!(!pending.lock().contains("req-1"));
let result = tokio::time::timeout(Duration::from_millis(100), rx).await;
match result {
Ok(Ok(Ok(value))) => assert_eq!(value, serde_json::json!({"v": 42})),
other => panic!("expected Ok({{v:42}}), got {other:?}"),
}
}
#[tokio::test]
async fn dispatch_envelope_responded_pushes_to_subscribe_channel() {
let pending = empty_pending();
let mut rx = pending
.lock()
.register_subscribe("sub-1".to_string(), None, None);
dispatch_envelope(&pending, EventEnvelope::responded("sub-1", serde_json::json!("first")));
dispatch_envelope(&pending, EventEnvelope::responded("sub-1", serde_json::json!("second")));
assert!(pending.lock().contains("sub-1"));
let a = tokio::time::timeout(Duration::from_millis(100), rx.recv()).await;
let b = tokio::time::timeout(Duration::from_millis(100), rx.recv()).await;
match (a, b) {
(Ok(Some(Ok(x))), Ok(Some(Ok(y)))) => {
assert_eq!(x, serde_json::json!("first"));
assert_eq!(y, serde_json::json!("second"));
}
other => panic!("expected two Ok values, got {other:?}"),
}
}
#[tokio::test]
async fn dispatch_envelope_completed_removes_entry() {
let pending = empty_pending();
let _rx = pending
.lock()
.register_subscribe("sub-2".to_string(), None, None);
assert!(pending.lock().contains("sub-2"));
dispatch_envelope(&pending, EventEnvelope::completed("sub-2"));
assert!(!pending.lock().contains("sub-2"));
}
#[tokio::test]
async fn dispatch_envelope_aborted_removes_entry() {
let pending = empty_pending();
let _rx = pending.lock().register_call(
"req-2".to_string(),
Instant::now() + Duration::from_secs(30),
None,
);
assert!(pending.lock().contains("req-2"));
dispatch_envelope(&pending, EventEnvelope::aborted("req-2"));
assert!(!pending.lock().contains("req-2"));
}
#[tokio::test]
async fn dispatch_envelope_error_resolves_call_with_error() {
let pending = empty_pending();
let rx = pending.lock().register_call(
"req-3".to_string(),
Instant::now() + Duration::from_secs(30),
None,
);
let err = CallError::new("FILE_NOT_FOUND", "missing", false);
dispatch_envelope(&pending, EventEnvelope::error("req-3", &err));
assert!(!pending.lock().contains("req-3"));
let result = tokio::time::timeout(Duration::from_millis(100), rx).await;
match result {
Ok(Ok(Err(e))) => {
assert_eq!(e.code, "FILE_NOT_FOUND");
assert!(!e.retryable);
}
other => panic!("expected Err(FILE_NOT_FOUND), got {other:?}"),
}
}
#[tokio::test]
async fn dispatch_envelope_error_pushes_error_to_subscribe_channel() {
let pending = empty_pending();
let mut rx = pending
.lock()
.register_subscribe("sub-3".to_string(), None, None);
let err = CallError::new("RATE_LIMITED", "slow down", true);
dispatch_envelope(&pending, EventEnvelope::error("sub-3", &err));
assert!(!pending.lock().contains("sub-3"));
let result = tokio::time::timeout(Duration::from_millis(100), rx.recv()).await;
match result {
Ok(Some(Err(e))) => {
assert_eq!(e.code, "RATE_LIMITED");
assert!(e.retryable);
}
other => panic!("expected Err(RATE_LIMITED), got {other:?}"),
}
}
#[tokio::test]
async fn dispatch_envelope_error_with_invalid_payload_is_no_op() {
let pending = empty_pending();
let _rx = pending.lock().register_call(
"req-4".to_string(),
Instant::now() + Duration::from_secs(30),
None,
);
let malformed = EventEnvelope::new(EVENT_ERROR, "req-4", serde_json::json!("not-an-object"));
dispatch_envelope(&pending, malformed);
assert!(pending.lock().contains("req-4"));
}
#[tokio::test]
async fn dispatch_envelope_unknown_event_type_is_no_op() {
let pending = empty_pending();
let _rx = pending.lock().register_call(
"req-5".to_string(),
Instant::now() + Duration::from_secs(30),
None,
);
let unknown = EventEnvelope::new("call.mystery", "req-5", serde_json::json!({}));
dispatch_envelope(&pending, unknown);
assert!(pending.lock().contains("req-5"));
}
#[tokio::test]
async fn dispatch_envelope_unknown_request_id_is_no_op() {
let pending = empty_pending();
dispatch_envelope(&pending, EventEnvelope::responded("ghost", serde_json::json!(1)));
dispatch_envelope(&pending, EventEnvelope::completed("ghost"));
dispatch_envelope(&pending, EventEnvelope::aborted("ghost"));
assert!(pending.lock().is_empty());
}
// --- SubscriptionStream ------------------------------------------------
#[tokio::test]
async fn subscription_stream_closed_yields_one_error_then_ends() {
use futures::stream::StreamExt;
let err = CallError::internal("stream closed before send");
let mut stream = SubscriptionStream::closed("req-x".to_string(), err);
let first = stream.next().await;
match first {
Some(env) => {
assert_eq!(env.request_id, "req-x");
assert!(env.result.is_err());
assert_eq!(env.result.unwrap_err().code, "INTERNAL");
}
other => panic!("expected one error envelope, got {other:?}"),
}
let second = stream.next().await;
assert!(second.is_none(), "stream must terminate after the error");
}
#[tokio::test]
async fn subscription_stream_emits_ok_values_then_completes() {
use futures::stream::StreamExt;
let (tx, rx) = mpsc::channel(8);
let mut stream = SubscriptionStream::new("req-y".to_string(), rx);
tx.try_send(Ok(serde_json::json!(1))).unwrap();
tx.try_send(Ok(serde_json::json!(2))).unwrap();
drop(tx);
let a = stream.next().await.unwrap();
assert_eq!(a.request_id, "req-y");
assert_eq!(a.result.unwrap(), serde_json::json!(1));
let b = stream.next().await.unwrap();
assert_eq!(b.result.unwrap(), serde_json::json!(2));
assert!(stream.next().await.is_none(), "stream ends after channel closes");
}
#[tokio::test]
async fn subscription_stream_emits_error_then_terminates() {
use futures::stream::StreamExt;
let (tx, rx) = mpsc::channel(8);
let mut stream = SubscriptionStream::new("req-z".to_string(), rx);
tx.try_send(Ok(serde_json::json!("ok"))).unwrap();
tx.try_send(Err(CallError::timeout("timed out"))).unwrap();
drop(tx);
let first = stream.next().await.unwrap();
assert_eq!(first.result.unwrap(), serde_json::json!("ok"));
let second = stream.next().await.unwrap();
assert_eq!(second.request_id, "req-z");
assert_eq!(second.result.unwrap_err().code, "TIMEOUT");
assert!(stream.next().await.is_none(), "stream terminates after error");
}
}