use async_trait::async_trait;
use futures_core::stream::BoxStream;
use plexus_auth_core::{
Anonymous, AuthContext, ForwardPolicy, IdentityOnly, PassThrough, Principal,
};
use plexus_core::plexus::{
plexus::{route_to_child, ChildRouter, PlexusError},
streaming::{done_stream, PlexusStream},
};
use serde_json::Value;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct EchoCallee {
namespace: String,
captured: Arc<Mutex<Option<AuthContext>>>,
}
impl EchoCallee {
fn new(namespace: impl Into<String>) -> Self {
Self {
namespace: namespace.into(),
captured: Arc::new(Mutex::new(None)),
}
}
fn captured(&self) -> Option<AuthContext> {
self.captured.lock().unwrap().clone()
}
}
#[async_trait]
impl ChildRouter for EchoCallee {
fn router_namespace(&self) -> &str {
&self.namespace
}
async fn router_call(
&self,
_method: &str,
_params: Value,
auth: Option<&AuthContext>,
_raw_ctx: Option<&plexus_core::request::RawRequestContext>,
) -> Result<PlexusStream, PlexusError> {
*self.captured.lock().unwrap() = auth.cloned();
Ok(done_stream(vec![]))
}
async fn get_child(&self, _name: &str) -> Option<Box<dyn ChildRouter>> {
None
}
}
#[derive(Clone)]
struct TestParent {
child: EchoCallee,
policy: Option<Arc<dyn ForwardPolicy>>,
stamped: Principal,
}
impl TestParent {
fn new(child: EchoCallee) -> Self {
Self {
child,
policy: None,
stamped: Principal::Anonymous,
}
}
fn with_policy(mut self, policy: Arc<dyn ForwardPolicy>) -> Self {
self.policy = Some(policy);
self
}
}
#[async_trait]
impl ChildRouter for TestParent {
fn router_namespace(&self) -> &str {
"parent"
}
async fn router_call(
&self,
method: &str,
params: Value,
auth: Option<&AuthContext>,
raw_ctx: Option<&plexus_core::request::RawRequestContext>,
) -> Result<PlexusStream, PlexusError> {
route_to_child(self, method, params, auth, raw_ctx).await
}
async fn get_child(&self, name: &str) -> Option<Box<dyn ChildRouter>> {
if name == self.child.namespace {
Some(Box::new(self.child.clone()) as Box<dyn ChildRouter>)
} else {
None
}
}
fn forward_policy_for(&self, _callee_ns: &str) -> Option<Arc<dyn ForwardPolicy>> {
self.policy.clone()
}
fn framework_stamped_principal(&self) -> Principal {
self.stamped.clone()
}
}
fn caller_ctx() -> AuthContext {
AuthContext::new(
"alice".to_string(),
"sess-7".to_string(),
vec!["admin".to_string(), "operator".to_string()],
serde_json::json!({"tenant_id": "acme"}),
)
}
#[tokio::test]
async fn default_identity_only_strips_roles_and_metadata() {
let echo = EchoCallee::new("solar");
let parent = TestParent::new(echo.clone());
let caller = caller_ctx();
let _ = parent
.router_call("solar.info", Value::Null, Some(&caller), None)
.await
.expect("dispatch succeeded");
let derived = echo.captured().expect("callee received some AuthContext");
assert_eq!(derived.user_id, "alice");
assert_eq!(derived.session_id, "sess-7");
assert!(
derived.roles.is_empty(),
"expected empty roles under IdentityOnly default, got {:?}",
derived.roles
);
assert_eq!(
derived.metadata,
Value::Null,
"expected null metadata under IdentityOnly default"
);
}
#[tokio::test]
async fn registered_pass_through_keeps_roles_and_metadata() {
let echo = EchoCallee::new("solar");
let parent = TestParent::new(echo.clone()).with_policy(Arc::new(PassThrough));
let caller = caller_ctx();
let _ = parent
.router_call("solar.info", Value::Null, Some(&caller), None)
.await
.expect("dispatch succeeded");
let derived = echo.captured().expect("callee received some AuthContext");
assert_eq!(derived.user_id, "alice");
assert_eq!(derived.session_id, "sess-7");
assert_eq!(derived.roles, vec!["admin".to_string(), "operator".to_string()]);
assert_eq!(derived.metadata, serde_json::json!({"tenant_id": "acme"}));
}
#[tokio::test]
async fn registered_anonymous_drops_everything() {
let echo = EchoCallee::new("solar");
let parent = TestParent::new(echo.clone()).with_policy(Arc::new(Anonymous));
let caller = caller_ctx();
let _ = parent
.router_call("solar.info", Value::Null, Some(&caller), None)
.await
.expect("dispatch succeeded");
let derived = echo.captured().expect("callee received some AuthContext");
assert_eq!(derived.user_id, "anonymous");
assert_eq!(derived.session_id, "");
assert!(derived.roles.is_empty());
assert_eq!(derived.metadata, Value::Null);
}
#[tokio::test]
async fn no_caller_context_no_callee_context() {
let echo = EchoCallee::new("solar");
let parent = TestParent::new(echo.clone()).with_policy(Arc::new(IdentityOnly));
let _ = parent
.router_call("solar.info", Value::Null, None, None)
.await
.expect("dispatch succeeded");
assert!(
echo.captured().is_none(),
"callee should receive None when caller had None"
);
}
#[tokio::test]
async fn unmodified_child_router_impl_still_compiles_and_dispatches() {
let echo = EchoCallee::new("solar");
assert!(
ChildRouter::forward_policy_for(&echo, "solar").is_none(),
"default forward_policy_for should return None"
);
assert!(
matches!(echo.framework_stamped_principal(), Principal::Anonymous),
"default framework_stamped_principal should be Anonymous"
);
let dyn_router: &dyn ChildRouter = &echo;
let _ = dyn_router
.router_call("info", Value::Null, None, None)
.await
.expect("direct dispatch succeeds");
}
#[allow(dead_code)]
mod compile_witness_pre_authlang_router {
use super::*;
struct LegacyRouter;
#[async_trait]
impl ChildRouter for LegacyRouter {
fn router_namespace(&self) -> &str {
"legacy"
}
async fn router_call(
&self,
_method: &str,
_params: Value,
_auth: Option<&AuthContext>,
_raw_ctx: Option<&plexus_core::request::RawRequestContext>,
) -> Result<PlexusStream, PlexusError> {
Ok(done_stream(vec![]))
}
async fn get_child(&self, _name: &str) -> Option<Box<dyn ChildRouter>> {
None
}
}
fn _trait_object(r: LegacyRouter) -> Box<dyn ChildRouter> {
Box::new(r)
}
fn _unused(_: BoxStream<'_, String>) {}
}
#[test]
fn dynamic_hub_forward_policy_registry_round_trip() {
use plexus_core::plexus::DynamicHub;
let hub = DynamicHub::new("test")
.with_forward_policy("solar", Arc::new(PassThrough));
assert_eq!(hub.forward_policies().len(), 1);
assert_eq!(
hub.forward_policies().get("solar").unwrap().name().as_str(),
"pass_through"
);
let trait_view: &dyn ChildRouter = &hub;
assert_eq!(
trait_view
.forward_policy_for("solar")
.unwrap()
.name()
.as_str(),
"pass_through"
);
assert!(trait_view.forward_policy_for("unregistered").is_none());
}