use std::sync::Arc;
use hiroz::{Builder, Result, context::ZContextBuilder, define_action};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestGoal {
pub order: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestResult {
pub value: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestFeedback {
pub progress: i32,
}
pub struct TestAction;
define_action! {
TestAction,
action_name: "test_action",
Goal: TestGoal,
Result: TestResult,
Feedback: TestFeedback,
}
#[allow(dead_code)]
async fn setup_test_base() -> Result<(hiroz::node::ZNode,)> {
let ctx = ZContextBuilder::default().build()?;
let node = ctx.create_node("test_action_remapping_node").build()?;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
Ok((node,))
}
async fn setup_test_with_client_server(
action_name: &str,
) -> Result<(
hiroz::node::ZNode,
hiroz::node::ZNode,
std::sync::Arc<hiroz::action::client::ZActionClient<TestAction>>,
hiroz::action::server::ZActionServer<TestAction>,
)> {
let ctx = ZContextBuilder::default().build()?;
let client_node = ctx
.create_node("test_action_remapping_client_node")
.build()?;
let server_node = ctx
.create_node("test_action_remapping_server_node")
.build()?;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let client = Arc::new(
client_node
.create_action_client::<TestAction>(action_name)
.build()?,
);
let server = server_node
.create_action_server::<TestAction>(action_name)
.build()?;
Ok((client_node, server_node, client, server))
}
async fn run_server_with_timeout(
server: hiroz::action::server::ZActionServer<TestAction>,
expected_result: i32,
timeout_ms: u64,
) -> Result<()> {
let timeout = tokio::time::timeout(std::time::Duration::from_millis(timeout_ms), async {
if let Ok(requested) = server.recv_goal().await {
let accepted = requested.accept();
let executing = accepted.execute();
let _ = executing.succeed(TestResult {
value: expected_result,
});
}
})
.await;
match timeout {
Ok(_) => Ok(()),
Err(_) => Err("Server timeout".into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_name_remapping_absolute() -> Result<()> {
let (_client_node, _server_node, client, server) =
setup_test_with_client_server("/absolute_action_name").await?;
let server_clone = server.clone();
let server_handle =
tokio::spawn(async move { run_server_with_timeout(server_clone, 100, 5000).await });
let goal = TestGoal { order: 5 };
let goal_handle = client.send_goal(goal).await?;
let result = goal_handle.result().await?;
assert_eq!(result.value, 100);
let _ = server_handle.await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_name_remapping_relative() -> Result<()> {
let (_client_node, _server_node, client, server) =
setup_test_with_client_server("relative_action_name").await?;
let server_clone = server.clone();
let server_handle =
tokio::spawn(async move { run_server_with_timeout(server_clone, 200, 5000).await });
let goal = TestGoal { order: 10 };
let goal_handle = client.send_goal(goal).await?;
let result = goal_handle.result().await?;
assert_eq!(result.value, 200);
let _ = server_handle.await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_name_remapping_with_rules() -> Result<()> {
let ctx = ZContextBuilder::default()
.with_remap_rule("original_action:=remapped_action")?
.build()?;
let client_node = ctx.create_node("test_client").build()?;
let server_node = ctx.create_node("test_server").build()?;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let client = Arc::new(
client_node
.create_action_client::<TestAction>("original_action")
.build()?,
);
let server = server_node
.create_action_server::<TestAction>("remapped_action")
.build()?;
let server_clone = server.clone();
tokio::spawn(async move {
if let Ok(requested) = server_clone.recv_goal().await {
let accepted = requested.accept();
let executing = accepted.execute();
let _ = executing.succeed(TestResult { value: 300 });
}
});
let goal = TestGoal { order: 15 };
let goal_handle = client.send_goal(goal).await?;
let result = goal_handle.result().await?;
assert_eq!(result.value, 300);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_name_remapping_multiple_rules() -> Result<()> {
let ctx = ZContextBuilder::default()
.with_remap_rule("action1:=remapped_action1")?
.with_remap_rule("action2:=remapped_action2")?
.build()?;
let client_node = ctx.create_node("test_client").build()?;
let server_node = ctx.create_node("test_server").build()?;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let client1 = Arc::new(
client_node
.create_action_client::<TestAction>("action1")
.build()?,
);
let server1 = server_node
.create_action_server::<TestAction>("remapped_action1")
.build()?;
let server_clone1 = server1.clone();
let server_handle1 =
tokio::spawn(async move { run_server_with_timeout(server_clone1, 400, 5000).await });
let goal = TestGoal { order: 20 };
let goal_handle = client1.send_goal(goal).await?;
let result = goal_handle.result().await?;
assert_eq!(result.value, 400);
let _ = server_handle1.await;
let client2 = Arc::new(
client_node
.create_action_client::<TestAction>("action2")
.build()?,
);
let server2 = server_node
.create_action_server::<TestAction>("remapped_action2")
.build()?;
let server_clone2 = server2.clone();
let server_handle2 =
tokio::spawn(async move { run_server_with_timeout(server_clone2, 500, 5000).await });
let goal = TestGoal { order: 25 };
let goal_handle = client2.send_goal(goal).await?;
let result = goal_handle.result().await?;
assert_eq!(result.value, 500);
let _ = server_handle2.await;
Ok(())
}
#[test]
fn test_remap_rules_apply() -> Result<()> {
let mut rules = hiroz::context::RemapRules::new();
rules.add_rule("original:=remapped")?;
assert_eq!(rules.apply("original"), "remapped");
assert_eq!(rules.apply("unchanged"), "unchanged");
Ok(())
}
#[test]
fn test_context_with_remap_rules() -> Result<()> {
let ctx = ZContextBuilder::default()
.with_remap_rule("test_action:=remapped_action")?
.build()?;
let node = ctx.create_node("test_node").build()?;
assert_eq!(node.apply_remap("test_action"), "remapped_action");
assert_eq!(node.apply_remap("other_action"), "other_action");
Ok(())
}
#[test]
fn test_remap_rules_complex_scenarios() -> Result<()> {
let mut rules = hiroz::context::RemapRules::new();
rules.add_rule("action1:=remapped_action1")?;
rules.add_rule("action2:=remapped_action2")?;
rules.add_rule("__node:=new_node")?;
rules.add_rule("__ns:=/new_namespace")?;
assert_eq!(rules.apply("action1"), "remapped_action1");
assert_eq!(rules.apply("action2"), "remapped_action2");
assert_eq!(rules.apply("__node"), "new_node");
assert_eq!(rules.apply("__ns"), "/new_namespace");
assert_eq!(rules.apply("unchanged_action"), "unchanged_action");
Ok(())
}
#[test]
fn test_remap_rules_edge_cases() -> Result<()> {
let mut rules = hiroz::context::RemapRules::new();
assert_eq!(rules.apply("any_name"), "any_name");
assert!(rules.is_empty());
assert!(rules.add_rule("source:=").is_err());
assert!(rules.add_rule(":=target").is_err());
assert!(rules.add_rule("invalid_format").is_err());
rules.add_rule("valid:=rule")?;
assert_eq!(rules.apply("valid"), "rule");
assert!(!rules.is_empty());
Ok(())
}
#[test]
fn test_remap_rules_namespace_resolution() -> Result<()> {
let mut rules = hiroz::context::RemapRules::new();
rules.add_rule("__ns:=/test_namespace")?;
rules.add_rule("local_action:=global_action")?;
assert_eq!(rules.apply("__ns"), "/test_namespace");
assert_eq!(rules.apply("local_action"), "global_action");
Ok(())
}
}