use std::sync::Arc;
use std::sync::RwLock as SyncRwLock;
use theater::actor::handle::ActorHandle;
use theater::actor::store::ActorStore;
use theater::chain::StateChain;
use theater::id::TheaterId;
use theater::messages::TheaterCommand;
use theater::pack_bridge::{ActorResult, AsyncRuntime, Ctx, PackInstance, Value};
use tokio::sync::mpsc;
use tracing::info;
#[tokio::test]
async fn test_task_manager() {
let _ = tracing_subscriber::fmt().with_env_filter("info").try_init();
let wasm_path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../../actors/task-manager/target/wasm32-unknown-unknown/release/task_manager.wasm"
);
let wasm_bytes = match std::fs::read(wasm_path) {
Ok(bytes) => bytes,
Err(e) => {
panic!(
"Failed to read task-manager WASM from {}: {}. \
Make sure to build it first with: \
cd ../actors/task-manager && cargo build --release",
wasm_path, e
);
}
};
info!("Loaded WASM bytes: {} bytes", wasm_bytes.len());
let runtime = AsyncRuntime::new();
let actor_id = TheaterId::generate();
let (theater_tx, _theater_rx) = mpsc::channel::<TheaterCommand>(100);
let (operation_tx, _operation_rx) = mpsc::channel(10);
let (info_tx, _info_rx) = mpsc::channel(10);
let (control_tx, _control_rx) = mpsc::channel(10);
let chain = Arc::new(SyncRwLock::new(StateChain::new(
actor_id.clone(),
theater_tx.clone(),
)));
let actor_handle = ActorHandle::new(operation_tx, info_tx, control_tx);
let actor_store = ActorStore::new(
actor_id.clone(),
theater_tx.clone(),
actor_handle,
chain.clone(),
None, );
let result = PackInstance::new(
"task-manager",
&wasm_bytes,
&runtime,
actor_store,
|builder| {
builder.interface("theater:simple/runtime")?.func_typed(
"log",
|_ctx: &mut Ctx<'_, ActorStore>, input: Value| {
let msg = match input {
Value::String(s) => s,
_ => format!("{:?}", input),
};
info!("[ACTOR LOG] {}", msg);
Value::Tuple(vec![])
},
)?;
Ok(())
},
)
.await;
let mut instance = match result {
Ok(inst) => inst,
Err(e) => {
panic!("Failed to create PackInstance: {}", e);
}
};
info!("PackInstance created successfully");
info!("=== Calling init ===");
let result: ActorResult<()> = instance
.call_typed("theater:simple/actor.init", None, Value::Tuple(vec![]))
.await
.expect("init should succeed");
info!("init succeeded");
assert!(result.state.is_some(), "init should return state");
let state = result.state;
info!("=== Adding task 'Buy groceries' ===");
let result: ActorResult<i32> = instance
.call_typed(
"theater:simple/task-manager.add-task",
state,
Value::Tuple(vec![Value::String("Buy groceries".to_string())]),
)
.await
.expect("add-task should succeed");
info!("add-task returned id = {}", result.value);
assert_eq!(result.value, 0, "First task should have id 0");
let state = result.state;
info!("=== Adding task 'Walk the dog' ===");
let result: ActorResult<i32> = instance
.call_typed(
"theater:simple/task-manager.add-task",
state,
Value::Tuple(vec![Value::String("Walk the dog".to_string())]),
)
.await
.expect("add-task should succeed");
info!("add-task returned id = {}", result.value);
assert_eq!(result.value, 1, "Second task should have id 1");
let state = result.state;
info!("=== Adding task 'Write code' ===");
let result: ActorResult<i32> = instance
.call_typed(
"theater:simple/task-manager.add-task",
state,
Value::Tuple(vec![Value::String("Write code".to_string())]),
)
.await
.expect("add-task should succeed");
info!("add-task returned id = {}", result.value);
assert_eq!(result.value, 2, "Third task should have id 2");
let state = result.state;
info!("=== Completing task 1 ===");
let result: ActorResult<bool> = instance
.call_typed(
"theater:simple/task-manager.complete-task",
state,
Value::Tuple(vec![Value::S32(1)]),
)
.await
.expect("complete-task should succeed");
info!("complete-task returned found = {}", result.value);
assert!(result.value, "Task 1 should be found and completed");
let state = result.state;
info!("=== Completing task 99 (should not exist) ===");
let result: ActorResult<bool> = instance
.call_typed(
"theater:simple/task-manager.complete-task",
state,
Value::Tuple(vec![Value::S32(99)]),
)
.await
.expect("complete-task should succeed");
info!("complete-task returned found = {}", result.value);
assert!(!result.value, "Task 99 should not be found");
let state = result.state;
info!("=== Listing tasks ===");
let result: ActorResult<Vec<u8>> = instance
.call_typed(
"theater:simple/task-manager.list-tasks",
state,
Value::Tuple(vec![]),
)
.await
.expect("list-tasks should succeed");
let tasks_json = String::from_utf8_lossy(&result.value);
info!("list-tasks returned: {}", tasks_json);
let tasks: serde_json::Value =
serde_json::from_slice(&result.value).expect("Should parse as JSON");
let tasks_array = tasks.as_array().expect("Should be an array");
assert_eq!(tasks_array.len(), 3, "Should have 3 tasks");
assert_eq!(tasks_array[0]["id"], 0);
assert_eq!(tasks_array[0]["title"], "Buy groceries");
assert_eq!(tasks_array[0]["completed"], false);
assert_eq!(tasks_array[1]["id"], 1);
assert_eq!(tasks_array[1]["title"], "Walk the dog");
assert_eq!(tasks_array[1]["completed"], true);
assert_eq!(tasks_array[2]["id"], 2);
assert_eq!(tasks_array[2]["title"], "Write code");
assert_eq!(tasks_array[2]["completed"], false);
info!("=== Task manager test passed! ===");
info!("Created 3 tasks, completed 1, verified state correctly persisted");
}