From 04c64c30e69f7b0c3b1dcf681c126ff60c06858a Mon Sep 17 00:00:00 2001 From: deepseek-v4-pro Date: Fri, 14 Aug 2026 11:50:34 +0000 Subject: [PATCH] test: ChannelsSessionEnv delegation coverage (R-09) Add MockEnv and delegation tests for all ChannelsSessionEnv methods: invoke_with_policy, contains, peer_ids, peer_contains, peer_operations, invoke_peer. Each test verifies the method delegates to the base OperationEnv correctly. R-11 (run_open_wrapper refactoring) and R-12 (stub handlers) are intentional and require no action for v0.1.0. Verification: - 542 tests passed, 0 failed - cargo clippy --all-targets -- -D warnings: clean - cargo fmt --check: clean - cargo doc --no-deps: clean (0 warnings) - cargo publish --dry-run --allow-dirty: succeeds - env.rs line coverage: 96.43% (was 51.28%) - overall line coverage: 94.20% --- src/channels/env.rs | 179 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 1 deletion(-) diff --git a/src/channels/env.rs b/src/channels/env.rs index f9bb1c1..d461c4f 100644 --- a/src/channels/env.rs +++ b/src/channels/env.rs @@ -109,9 +109,119 @@ impl ChannelOperationEnv for ChannelsSessionEnv { mod tests { use super::*; use crate::channels::mux::MuxRunner; - use crate::registry::env::LocalOperationEnv; + use crate::core::types::Capabilities; + use crate::protocol::wire::ResponseEnvelope; + use crate::registry::context::{AbortPolicy, OperationContext, ScopedPeerEnv}; + use crate::registry::env::{LocalOperationEnv, PeerId, PeerRef}; + use parking_lot::Mutex; + use std::collections::HashMap; use tokio::io::duplex; + struct MockEnv { + contains_val: bool, + peer_ids_val: Vec, + peer_contains_val: bool, + peer_operations_val: Vec, + invoke_with_policy_called: Mutex, + invoke_peer_called: Mutex, + } + + impl MockEnv { + fn new() -> Self { + Self { + contains_val: true, + peer_ids_val: vec!["peer-a".to_string(), "peer-b".to_string()], + peer_contains_val: true, + peer_operations_val: vec!["op1".to_string(), "op2".to_string()], + invoke_with_policy_called: Mutex::new(false), + invoke_peer_called: Mutex::new(false), + } + } + } + + #[async_trait::async_trait] + impl OperationEnv for MockEnv { + async fn invoke_with_policy( + &self, + _namespace: &str, + _operation: &str, + _input: serde_json::Value, + parent: &OperationContext, + _policy: AbortPolicy, + ) -> ResponseEnvelope { + *self.invoke_with_policy_called.lock() = true; + ResponseEnvelope::ok( + parent.request_id.clone(), + serde_json::Value::String("mock".into()), + ) + } + + fn contains(&self, _name: &str) -> bool { + self.contains_val + } + + fn peer_ids(&self) -> Vec { + self.peer_ids_val.clone() + } + + fn peer_contains(&self, _peer: &PeerId, _name: &str) -> bool { + self.peer_contains_val + } + + fn peer_operations(&self, _peer: &PeerId) -> Vec { + self.peer_operations_val.clone() + } + + async fn invoke_peer( + &self, + _peer: &PeerRef, + _namespace: &str, + _operation: &str, + _input: serde_json::Value, + parent: &OperationContext, + _policy: AbortPolicy, + ) -> ResponseEnvelope { + *self.invoke_peer_called.lock() = true; + ResponseEnvelope::ok( + parent.request_id.clone(), + serde_json::Value::String("peer".into()), + ) + } + } + + fn make_test_context() -> OperationContext { + OperationContext { + request_id: "req-1".to_string(), + parent_request_id: None, + identity: None, + handler_identity: None, + forwarded_for: None, + capabilities: Capabilities::new(), + metadata: HashMap::new(), + scoped_env: ScopedPeerEnv::empty(), + env: Arc::new(MockEnv::new()), + abort_policy: AbortPolicy::default(), + deadline: None, + internal: false, + ownership: None, + } + } + + fn make_env() -> ChannelsSessionEnv { + let mock = Arc::new(MockEnv::new()); + let (_client, server) = duplex(64); + let (_reader, writer) = tokio::io::split(server); + let (handle, runner) = MuxRunner::new(Box::new(writer)); + tokio::spawn(async move { + let _ = runner.run().await; + }); + let manager = ChannelManager::with_defaults(handle, None); + ChannelsSessionEnv { + base: mock, + manager, + } + } + #[tokio::test] async fn channels_session_env_delegates_to_base() { let registry = Arc::new(crate::registry::registration::OperationRegistry::new()); @@ -132,4 +242,71 @@ mod tests { fn assert_operation_env() {} assert_operation_env::(); } + + #[tokio::test] + async fn invoke_with_policy_delegates_to_base() { + let env = make_env(); + let ctx = make_test_context(); + let result = env + .invoke_with_policy( + "ns", + "op", + serde_json::Value::Null, + &ctx, + AbortPolicy::default(), + ) + .await; + assert!(result.result.is_ok()); + assert_eq!( + result.result.unwrap(), + serde_json::Value::String("mock".into()) + ); + } + + #[tokio::test] + async fn contains_delegates_to_base() { + let env = make_env(); + assert!(env.contains("any-op")); + } + + #[tokio::test] + async fn peer_ids_delegates_to_base() { + let env = make_env(); + let ids = env.peer_ids(); + assert_eq!(ids, vec!["peer-a".to_string(), "peer-b".to_string()]); + } + + #[tokio::test] + async fn peer_contains_delegates_to_base() { + let env = make_env(); + assert!(env.peer_contains(&"peer-a".to_string(), "any-op")); + } + + #[tokio::test] + async fn peer_operations_delegates_to_base() { + let env = make_env(); + let ops = env.peer_operations(&"peer-a".to_string()); + assert_eq!(ops, vec!["op1".to_string(), "op2".to_string()]); + } + + #[tokio::test] + async fn invoke_peer_delegates_to_base() { + let env = make_env(); + let ctx = make_test_context(); + let result = env + .invoke_peer( + &PeerRef::Any, + "ns", + "op", + serde_json::Value::Null, + &ctx, + AbortPolicy::default(), + ) + .await; + assert!(result.result.is_ok()); + assert_eq!( + result.result.unwrap(), + serde_json::Value::String("peer".into()) + ); + } }