use colonyos;
use colonyos::core::Attribute;
use colonyos::core::Colony;
use colonyos::core::Executor;
use colonyos::core::FunctionSpec;
use colonyos::crypto;
use random_string::generate;
const SERVER_PRVKEY: &str = "fcc79953d8a751bf41db661592dc34d30004b1a651ffa0725b03ac227641499d";
async fn create_test_colony() -> (Colony, Colony, String) {
let charset = "abcdefghijklmnopqrstuvwxyz";
let name = generate(64, charset);
let colonyprvkey = crypto::gen_prvkey();
let colonyid = crypto::gen_id(&colonyprvkey);
let colony = Colony {
colonyid: colonyid,
name: "test_colony_name_".to_owned() +&name,
};
let added_colony = colonyos::add_colony(&colony, &SERVER_PRVKEY.to_owned())
.await
.unwrap();
(colony, added_colony, colonyprvkey)
}
async fn create_test_executor(colonyname: &String, prvkey: &String) -> (Executor, Executor, String) {
let executorprvkey = crypto::gen_prvkey();
let executorid = crypto::gen_id(&executorprvkey);
let charset = "abcdefghijklmnopqrstuvwxyz";
let name = generate(64, charset);
let executor = Executor::new(&name, &executorid, "test_executor_type", &colonyname);
let added_executor = colonyos::add_executor(&executor, &prvkey).await.unwrap();
let _ = colonyos::approve_executor(&colonyname, &name, &prvkey).await;
(executor.clone(), added_executor, executorprvkey)
}
fn create_test_function_spec(colonyname: &str) -> FunctionSpec {
let mut spec = FunctionSpec::new("test_func", "test_executor_type", colonyname);
spec.args = vec!["test_args".to_owned()];
spec.label = "label".to_owned();
spec.env.insert("test_key".to_owned(), "test_value".to_owned());
spec.maxexectime = 120; spec.maxwaittime = 60; spec
}
#[tokio::test]
async fn test_add_colony() {
let t = create_test_colony().await;
let colony = t.0;
let added_colony = t.1;
assert_eq!(colony.colonyid, added_colony.colonyid);
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_add_executor() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executor = t.0;
let added_executor = t.1;
assert_eq!(executor.executorid, added_executor.executorid);
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_submit() {
println!("---------------------------- 1");
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
println!("---------------------------- 3");
let spec = create_test_function_spec(colony.name.as_str());
let process = colonyos::submit(&spec, &executorprvkey).await.unwrap();
assert_eq!(64, process.processid.len());
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_assign() {
println!("---------------------------- 1");
let t = create_test_colony().await;
println!("---------------------------- 2");
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
println!("---------------------------- 3");
let spec = create_test_function_spec(colony.name.as_str());
let submitted_process = colonyos::submit(&spec, &executorprvkey).await.unwrap();
println!("submitted_process: {:?}", submitted_process);
let assigned_process = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
assert_eq!(submitted_process.processid, assigned_process.processid);
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_close() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let spec = create_test_function_spec(colony.name.as_str());
let submitted_process = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let assigned_process = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
assert_eq!(submitted_process.processid, assigned_process.processid);
let _ = colonyos::close(&assigned_process.processid, &executorprvkey).await;
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_failed() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let spec = create_test_function_spec(colony.name.as_str());
let submitted_process = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let assigned_process = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
assert_eq!(submitted_process.processid, assigned_process.processid);
let _ = colonyos::fail(&assigned_process.processid, &executorprvkey).await;
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_add_attr() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let spec = create_test_function_spec(colony.name.as_str());
let submitted_process = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let assigned_process = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
assert_eq!(submitted_process.processid, assigned_process.processid);
let attr = Attribute::new(
&colony.name,
&assigned_process.processid,
"test_key",
"test_value",
);
let added_attr = colonyos::add_attr(&attr, &executorprvkey).await.unwrap();
assert_eq!(64, added_attr.attributeid.len());
assert_eq!(added_attr.targetcolonyname, attr.targetcolonyname);
assert_eq!(added_attr.key, attr.key);
assert_eq!(added_attr.value, attr.value);
let _ = colonyos::close(&assigned_process.processid, &executorprvkey).await;
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_get_process() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let spec = create_test_function_spec(colony.name.as_str());
let submitted_process = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let assigned_process = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
assert_eq!(submitted_process.processid, assigned_process.processid);
let _ = colonyos::close(&assigned_process.processid, &executorprvkey).await;
let process_from_server = colonyos::get_process(&assigned_process.processid, &executorprvkey)
.await
.unwrap();
assert_eq!(process_from_server.processid, assigned_process.processid);
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_get_processes() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let spec = create_test_function_spec(colony.name.as_str());
let submitted_process1 = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let submitted_process2 = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let processes = colonyos::get_processes(
&colony.name,
100,
colonyos::core::PENDING,
&executorprvkey,
)
.await
.unwrap();
let mut counter = 0;
let iter = processes.iter();
for p in iter {
if p.processid == submitted_process1.processid {
counter = counter + 1;
}
if p.processid == submitted_process2.processid {
counter = counter + 1;
}
}
assert_eq!(counter, 2);
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_get_processes_empty() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let _ = colonyos::get_processes(
&colony.name,
100,
colonyos::core::PENDING,
&executorprvkey,
)
.await
.unwrap();
}
#[tokio::test]
async fn test_channel_append_error_handling() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let spec = create_test_function_spec(colony.name.as_str());
let _submitted = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let assigned = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
let result = colonyos::channel_append(
&assigned.processid,
"non-existent-channel",
1,
"message",
"",
0,
&executorprvkey,
)
.await;
assert!(result.is_err());
let err = result.unwrap_err();
assert!(!err.conn_err());
let _ = colonyos::close(&assigned.processid, &executorprvkey).await;
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok());
}
#[tokio::test]
async fn test_channel_subscribe_and_stream() {
use std::sync::{Arc, Mutex};
use tokio::time::{sleep, Duration};
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2.clone();
let executorprvkey2 = t.2.clone();
let mut spec = create_test_function_spec(colony.name.as_str());
spec.channels = vec!["stream-channel".to_string()];
let _submitted = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let assigned = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
let processid = assigned.processid.clone();
let processid2 = assigned.processid.clone();
let colony_name = colony.name.clone();
let received_messages: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let received_clone = received_messages.clone();
let subscribe_handle = tokio::spawn(async move {
colonyos::subscribe_channel(
&processid,
"stream-channel",
0, 30, &executorprvkey,
move |entries| {
let mut msgs = received_clone.lock().unwrap();
for entry in &entries {
msgs.push(entry.payload_as_string());
}
msgs.len() < 3
},
)
.await
});
sleep(Duration::from_millis(500)).await;
for i in 1..=3 {
let result = colonyos::channel_append(
&processid2,
"stream-channel",
i,
&format!("Message {}", i),
"",
0,
&executorprvkey2,
)
.await;
if let Err(e) = &result {
if e.to_string().contains("Channel not found") || e.conn_err() {
subscribe_handle.abort();
let _ = colonyos::close(&processid2, &executorprvkey2).await;
let _ = colonyos::remove_colony(&colony_name, &SERVER_PRVKEY).await;
return;
}
}
result.unwrap();
sleep(Duration::from_millis(100)).await;
}
let subscribe_result = subscribe_handle.await;
match subscribe_result {
Ok(Ok(_entries)) => {
let msgs = received_messages.lock().unwrap();
assert_eq!(msgs.len(), 3, "Expected 3 messages, got {}", msgs.len());
assert_eq!(msgs[0], "Message 1");
assert_eq!(msgs[1], "Message 2");
assert_eq!(msgs[2], "Message 3");
}
Ok(Err(e)) => {
if e.conn_err() {
} else {
panic!("Subscribe failed: {}", e);
}
}
Err(e) => {
panic!("Subscriber task failed: {}", e);
}
}
let _ = colonyos::close(&processid2, &executorprvkey2).await;
let _ = colonyos::remove_colony(&colony_name, &SERVER_PRVKEY).await;
}
#[tokio::test]
async fn test_channel_append_and_read() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let mut spec = create_test_function_spec(colony.name.as_str());
spec.channels = vec!["test-channel".to_string()];
let _submitted = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let assigned = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
assert!(assigned.spec.channels.contains(&"test-channel".to_string()));
let append_result = colonyos::channel_append(
&assigned.processid,
"test-channel",
1, "message 1",
"", 0, &executorprvkey,
)
.await;
if let Err(e) = &append_result {
if e.to_string().contains("Channel not found") || e.to_string().contains("not supported") {
let _ = colonyos::close(&assigned.processid, &executorprvkey).await;
let _ = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
return;
}
}
append_result.unwrap();
colonyos::channel_append(
&assigned.processid,
"test-channel",
2, "message 2",
"", 0, &executorprvkey,
)
.await
.unwrap();
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let mut entries = Vec::new();
let mut retries = 3;
while retries > 0 {
match colonyos::channel_read(
&assigned.processid,
"test-channel",
0, 10, &executorprvkey,
)
.await
{
Ok(e) => {
entries = e;
break;
}
Err(_) if retries > 1 => {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
retries -= 1;
}
Err(e) => panic!("channel_read failed after retries: {}", e),
}
}
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].sequence, 1);
assert_eq!(entries[0].payload_as_string(), "message 1");
assert_eq!(entries[1].sequence, 2);
assert_eq!(entries[1].payload_as_string(), "message 2");
let _ = colonyos::close(&assigned.processid, &executorprvkey).await;
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_set_output() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let spec = create_test_function_spec(colony.name.as_str());
let _submitted = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let assigned = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
colonyos::set_output(
&assigned.processid,
vec!["output1".to_string(), "output2".to_string()],
&executorprvkey,
)
.await
.unwrap();
colonyos::close(&assigned.processid, &executorprvkey).await.unwrap();
let process = colonyos::get_process(&assigned.processid, &executorprvkey)
.await
.unwrap();
assert_eq!(process.output.len(), 2);
assert_eq!(process.output[0], "output1");
assert_eq!(process.output[1], "output2");
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_add_log() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executor = t.0;
let executorprvkey = t.2;
let spec = create_test_function_spec(colony.name.as_str());
let _submitted = colonyos::submit(&spec, &executorprvkey).await.unwrap();
let assigned = colonyos::assign(&colony.name, 10, &executorprvkey)
.await
.unwrap();
let log = colonyos::core::Log {
processid: assigned.processid.clone(),
colonyname: colony.name.clone(),
executorname: executor.executorname.clone(),
message: "Test log message".to_string(),
timestamp: 0,
};
colonyos::add_log(&log, &executorprvkey).await.unwrap();
let logs = colonyos::get_logs(
&colony.name,
&assigned.processid,
&executor.executorname,
10,
0,
&executorprvkey,
)
.await
.unwrap();
assert!(!logs.is_empty());
assert_eq!(logs[0].message, "Test log message");
colonyos::close(&assigned.processid, &executorprvkey).await.unwrap();
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_get_statistics() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let stats = colonyos::get_statistics(&colony.name, &executorprvkey)
.await
.unwrap();
assert!(stats.executors >= 1);
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_workflow() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = t.2;
let mut step1 = FunctionSpec::new("step1", "test_executor_type", &colony.name);
step1.nodename = "step1".to_string();
let mut step2 = FunctionSpec::new("step2", "test_executor_type", &colony.name);
step2.nodename = "step2".to_string();
step2.conditions.dependencies = vec!["step1".to_string()];
let workflow = colonyos::core::WorkflowSpec {
colonyname: colony.name.clone(),
functionspecs: vec![step1, step2],
};
let pg = colonyos::submit_workflow(&workflow, &executorprvkey)
.await
.unwrap();
assert!(!pg.processgraphid.is_empty());
assert_eq!(pg.processids.len(), 2);
let pg2 = colonyos::get_processgraph(&pg.processgraphid, &executorprvkey)
.await
.unwrap();
assert_eq!(pg.processgraphid, pg2.processgraphid);
let p1 = colonyos::assign(&colony.name, 10, &executorprvkey).await.unwrap();
colonyos::close(&p1.processid, &executorprvkey).await.unwrap();
let p2 = colonyos::assign(&colony.name, 10, &executorprvkey).await.unwrap();
colonyos::close(&p2.processid, &executorprvkey).await.unwrap();
colonyos::remove_processgraph(&pg.processgraphid, &executorprvkey)
.await
.unwrap();
let r = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
assert!(r.is_ok(), "Expected colonyos::remove_colony to succeed");
}
#[tokio::test]
async fn test_channel_chat() {
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executor_prvkey = t.2.clone();
let client_prvkey = t.2.clone();
let mut spec = create_test_function_spec(colony.name.as_str());
spec.channels = vec!["chat".to_string()];
let submitted = colonyos::submit(&spec, &client_prvkey).await.unwrap();
let processid = submitted.processid.clone();
let processid_for_client = processid.clone();
let processid_for_executor = processid.clone();
let colony_name = colony.name.clone();
let colony_name_for_executor = colony.name.clone();
let executor_received: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let executor_received_clone = executor_received.clone();
let executor_ready = Arc::new(AtomicBool::new(false));
let executor_ready_clone = executor_ready.clone();
let executor_prvkey_for_task = executor_prvkey.clone();
let executor_handle = tokio::spawn(async move {
let assigned = colonyos::assign(&colony_name_for_executor, 10, &executor_prvkey_for_task)
.await
.unwrap();
assert_eq!(assigned.state, colonyos::core::RUNNING);
let mut response_seq = 2i64;
let pid_for_callback = processid_for_executor.clone();
let key_for_callback = executor_prvkey_for_task.clone();
executor_ready_clone.store(true, Ordering::SeqCst);
colonyos::subscribe_channel(
&processid_for_executor,
"chat",
0,
30,
&executor_prvkey_for_task,
move |entries| {
for entry in &entries {
let msg = entry.payload_as_string();
{
let mut received = executor_received_clone.lock().unwrap();
received.push(msg.clone());
}
if entry.sequence % 2 == 1 {
let response = match msg.as_str() {
"Hello" => "Hi there!",
"How are you?" => "I'm doing great, thanks for asking!",
"Goodbye" => "See you later!",
_ => "I don't understand",
};
let rt = tokio::runtime::Handle::current();
let pid = pid_for_callback.clone();
let key = key_for_callback.clone();
let resp = response.to_string();
let seq = response_seq;
let reply_to = entry.sequence;
let is_goodbye = msg == "Goodbye";
let pid_for_end = pid_for_callback.clone();
let key_for_end = key_for_callback.clone();
let end_seq = response_seq + 2;
rt.spawn(async move {
let _ = colonyos::channel_append(
&pid, "chat", seq, &resp, "text", reply_to, &key,
).await;
if is_goodbye {
let _ = colonyos::channel_append(
&pid_for_end, "chat", end_seq, "", "end", 0, &key_for_end,
).await;
}
});
response_seq += 2;
}
if msg == "Goodbye" {
return false;
}
}
true
},
)
.await
});
let subscribe_result = colonyos::subscribe_process(
&submitted,
colonyos::core::RUNNING,
30,
&client_prvkey,
).await;
if let Err(e) = &subscribe_result {
if e.conn_err() {
executor_handle.abort();
let _ = colonyos::close(&processid, &executor_prvkey).await;
let _ = colonyos::remove_colony(&colony_name, &SERVER_PRVKEY).await;
println!("WebSocket not supported - test skipped");
return;
}
}
subscribe_result.unwrap();
while !executor_ready.load(Ordering::SeqCst) {
tokio::task::yield_now().await;
}
let processid_for_subscribe = processid.clone();
let client_prvkey_for_subscribe = client_prvkey.clone();
let subscribe_handle = tokio::spawn(async move {
colonyos::subscribe_channel(
&processid_for_subscribe,
"chat",
0, 30, &client_prvkey_for_subscribe,
|entries| {
for entry in &entries {
if entry.msgtype == "end" {
return false; }
}
true },
).await
});
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let messages = vec!["Hello", "How are you?", "Goodbye"];
for (i, msg) in messages.iter().enumerate() {
let seq = (i as i64 * 2) + 1;
let result = colonyos::channel_append(
&processid_for_client,
"chat",
seq,
msg,
"text",
0,
&client_prvkey,
).await;
if let Err(e) = &result {
if e.to_string().contains("Channel not found") {
executor_handle.abort();
subscribe_handle.abort();
let _ = colonyos::close(&processid, &executor_prvkey).await;
let _ = colonyos::remove_colony(&colony_name, &SERVER_PRVKEY).await;
println!("Channel not found - test skipped");
return;
}
}
result.unwrap();
}
let executor_result = executor_handle.await;
assert!(executor_result.is_ok(), "Executor task should complete successfully");
let subscribe_result = subscribe_handle.await;
assert!(subscribe_result.is_ok(), "Client subscription task should complete");
let all_entries = colonyos::channel_read(
&processid,
"chat",
0,
20,
&client_prvkey,
).await.unwrap();
let data_entries: Vec<_> = all_entries.iter().filter(|e| e.msgtype != "end").collect();
assert!(
data_entries.len() >= 6,
"Expected at least 6 data messages in chat, got {}",
data_entries.len()
);
let client_msgs: Vec<_> = all_entries.iter().filter(|e| e.sequence % 2 == 1).collect();
assert_eq!(client_msgs.len(), 3, "Expected 3 client messages");
assert_eq!(client_msgs[0].payload_as_string(), "Hello");
assert_eq!(client_msgs[1].payload_as_string(), "How are you?");
assert_eq!(client_msgs[2].payload_as_string(), "Goodbye");
let executor_msgs: Vec<_> = all_entries.iter().filter(|e| e.sequence % 2 == 0 && e.msgtype != "end").collect();
assert_eq!(executor_msgs.len(), 3, "Expected 3 executor responses");
assert_eq!(executor_msgs[0].payload_as_string(), "Hi there!");
assert_eq!(executor_msgs[1].payload_as_string(), "I'm doing great, thanks for asking!");
assert_eq!(executor_msgs[2].payload_as_string(), "See you later!");
assert_eq!(executor_msgs[0].inreplyto, 1);
assert_eq!(executor_msgs[1].inreplyto, 3);
assert_eq!(executor_msgs[2].inreplyto, 5);
let received = executor_received.lock().unwrap();
assert!(received.contains(&"Hello".to_string()));
assert!(received.contains(&"How are you?".to_string()));
assert!(received.contains(&"Goodbye".to_string()));
let _ = colonyos::close(&processid, &executor_prvkey).await;
let _ = colonyos::remove_colony(&colony_name, &SERVER_PRVKEY).await;
}
#[tokio::test]
async fn test_blueprint_definition_crud() {
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let _ = create_test_executor(&colony.name, &colonyprvkey).await;
let mut definition = colonyos::core::BlueprintDefinition::default();
definition.kind = "HomeDevice".to_string();
definition.metadata.name = "home-device-def".to_string();
definition.metadata.colonyname = colony.name.clone();
definition.spec.names.kind = "HomeDevice".to_string();
definition.spec.names.singular = "homedevice".to_string();
definition.spec.names.plural = "homedevices".to_string();
definition.spec.handler.executor_type = "home-reconciler".to_string();
let added = colonyos::add_blueprint_definition(&definition, &colonyprvkey).await;
if let Err(e) = &added {
if e.to_string().contains("not supported") || e.to_string().contains("not found") {
let _ = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
println!("Blueprint definitions not supported - test skipped");
return;
}
}
let added = added.unwrap();
assert_eq!(added.metadata.name, "home-device-def");
assert_eq!(added.kind, "HomeDevice");
let fetched = colonyos::get_blueprint_definition(&colony.name, "home-device-def", &colonyprvkey)
.await
.unwrap();
assert_eq!(fetched.metadata.name, "home-device-def");
assert_eq!(fetched.spec.handler.executor_type, "home-reconciler");
let all = colonyos::get_blueprint_definitions(&colony.name, &colonyprvkey)
.await
.unwrap();
assert!(!all.is_empty());
assert!(all.iter().any(|d| d.metadata.name == "home-device-def"));
let result = colonyos::remove_blueprint_definition(&colony.name, "home-device-def", &colonyprvkey).await;
assert!(result.is_ok());
let all_after = colonyos::get_blueprint_definitions(&colony.name, &colonyprvkey)
.await
.unwrap();
assert!(!all_after.iter().any(|d| d.metadata.name == "home-device-def"));
let _ = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
}
#[tokio::test]
async fn test_blueprint_crud() {
use serde_json::json;
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let executor_t = create_test_executor(&colony.name, &colonyprvkey).await;
let executorprvkey = executor_t.2;
let mut definition = colonyos::core::BlueprintDefinition::default();
definition.kind = "Light".to_string();
definition.metadata.name = "light-def".to_string();
definition.metadata.colonyname = colony.name.clone();
definition.spec.names.kind = "Light".to_string();
definition.spec.names.singular = "light".to_string();
definition.spec.names.plural = "lights".to_string();
definition.spec.handler.executor_type = "light-reconciler".to_string();
let def_result = colonyos::add_blueprint_definition(&definition, &colonyprvkey).await;
if let Err(e) = &def_result {
if e.to_string().contains("not supported") || e.to_string().contains("not found") {
let _ = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
println!("Blueprints not supported - test skipped");
return;
}
}
def_result.unwrap();
let mut blueprint = colonyos::core::Blueprint::default();
blueprint.kind = "Light".to_string();
blueprint.metadata.name = "living-room-light".to_string();
blueprint.metadata.colonyname = colony.name.clone();
blueprint.handler.executortype = "light-reconciler".to_string();
blueprint.spec.insert("power".to_string(), json!(true));
blueprint.spec.insert("brightness".to_string(), json!(80));
let added = colonyos::add_blueprint(&blueprint, &colonyprvkey).await.unwrap();
assert_eq!(added.metadata.name, "living-room-light");
assert_eq!(added.kind, "Light");
assert!(!added.blueprintid.is_empty());
let fetched = colonyos::get_blueprint(&colony.name, "living-room-light", &colonyprvkey)
.await
.unwrap();
assert_eq!(fetched.metadata.name, "living-room-light");
assert_eq!(fetched.spec.get("power"), Some(&json!(true)));
assert_eq!(fetched.spec.get("brightness"), Some(&json!(80)));
let mut updated_bp = fetched.clone();
updated_bp.spec.insert("brightness".to_string(), json!(50));
let updated = colonyos::update_blueprint(&updated_bp, false, &colonyprvkey)
.await
.unwrap();
assert_eq!(updated.spec.get("brightness"), Some(&json!(50)));
let all = colonyos::get_blueprints(&colony.name, "Light", "", &colonyprvkey)
.await
.unwrap();
assert!(!all.is_empty());
assert!(all.iter().any(|b| b.metadata.name == "living-room-light"));
let mut status = std::collections::HashMap::new();
status.insert("power".to_string(), json!(true));
status.insert("brightness".to_string(), json!(50));
status.insert("lastSeen".to_string(), json!("2025-01-01T12:00:00Z"));
let status_result = colonyos::update_blueprint_status(&colony.name, "living-room-light", status, &executorprvkey).await;
assert!(status_result.is_ok());
let with_status = colonyos::get_blueprint(&colony.name, "living-room-light", &colonyprvkey)
.await
.unwrap();
assert_eq!(with_status.status.get("brightness"), Some(&json!(50)));
let result = colonyos::remove_blueprint(&colony.name, "living-room-light", &colonyprvkey).await;
assert!(result.is_ok());
let _ = colonyos::remove_blueprint_definition(&colony.name, "light-def", &colonyprvkey).await;
let _ = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
}
#[tokio::test]
async fn test_blueprint_reconciler() {
use serde_json::json;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
let t = create_test_colony().await;
let colony = t.0;
let colonyprvkey = t.2;
let t = create_test_executor(&colony.name, &colonyprvkey).await;
let executor = t.0;
let executorprvkey = t.2.clone();
let mut definition = colonyos::core::BlueprintDefinition::default();
definition.kind = "Thermostat".to_string();
definition.metadata.name = "thermostat-def".to_string();
definition.metadata.colonyname = colony.name.clone();
definition.spec.names.kind = "Thermostat".to_string();
definition.spec.names.singular = "thermostat".to_string();
definition.spec.names.plural = "thermostats".to_string();
definition.spec.handler.executor_type = executor.executortype.clone();
let def_result = colonyos::add_blueprint_definition(&definition, &colonyprvkey).await;
if let Err(e) = &def_result {
if e.to_string().contains("not supported") || e.to_string().contains("not found") {
let _ = colonyos::remove_colony(&colony.name, &SERVER_PRVKEY).await;
println!("Blueprints not supported - test skipped");
return;
}
}
def_result.unwrap();
let mut blueprint = colonyos::core::Blueprint::default();
blueprint.kind = "Thermostat".to_string();
blueprint.metadata.name = "office-thermostat".to_string();
blueprint.metadata.colonyname = colony.name.clone();
blueprint.handler.executortype = executor.executortype.clone();
blueprint.spec.insert("targetTemp".to_string(), json!(22));
blueprint.spec.insert("mode".to_string(), json!("heat"));
let added_bp = colonyos::add_blueprint(&blueprint, &colonyprvkey).await.unwrap();
assert!(!added_bp.blueprintid.is_empty());
let colony_name = colony.name.clone();
let colony_name_for_reconciler = colony.name.clone();
let executorprvkey_for_reconciler = executorprvkey.clone();
let colonyprvkey_for_reconciler = colonyprvkey.clone();
let reconciled_blueprints: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let reconciled_clone = reconciled_blueprints.clone();
let reconciler_ready = Arc::new(AtomicBool::new(false));
let reconciler_ready_clone = reconciler_ready.clone();
let reconciler_stop = Arc::new(AtomicBool::new(false));
let reconciler_stop_clone = reconciler_stop.clone();
let reconciler_handle = tokio::spawn(async move {
reconciler_ready_clone.store(true, Ordering::SeqCst);
loop {
if reconciler_stop_clone.load(Ordering::SeqCst) {
break;
}
match colonyos::assign(&colony_name_for_reconciler, 2, &executorprvkey_for_reconciler).await {
Ok(process) => {
if process.spec.funcname == "reconcile" || process.spec.funcname == "cleanup" {
let bp_name = process.spec.kwargs.get("blueprintname")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
{
let mut reconciled = reconciled_clone.lock().unwrap();
reconciled.push(bp_name.clone());
}
if let Ok(bp) = colonyos::get_blueprint(
&colony_name_for_reconciler,
&bp_name,
&colonyprvkey_for_reconciler,
).await {
let mut status = std::collections::HashMap::new();
if let Some(target_temp) = bp.spec.get("targetTemp") {
status.insert("currentTemp".to_string(), target_temp.clone());
}
if let Some(mode) = bp.spec.get("mode") {
status.insert("mode".to_string(), mode.clone());
}
status.insert("reconciled".to_string(), json!(true));
status.insert("lastReconciled".to_string(), json!("2025-01-01T12:00:00Z"));
let _ = colonyos::update_blueprint_status(
&colony_name_for_reconciler,
&bp_name,
status,
&colonyprvkey_for_reconciler,
).await;
}
let _ = colonyos::close(&process.processid, &executorprvkey_for_reconciler).await;
} else {
let _ = colonyos::fail(&process.processid, &executorprvkey_for_reconciler).await;
}
}
Err(e) => {
if !e.conn_err() && !e.to_string().contains("timeout") {
break;
}
}
}
}
});
while !reconciler_ready.load(Ordering::SeqCst) {
tokio::task::yield_now().await;
}
let reconcile_result = colonyos::reconcile_blueprint(&colony_name, "office-thermostat", false, &colonyprvkey).await;
if let Err(e) = &reconcile_result {
if e.to_string().contains("not supported") {
reconciler_stop.store(true, Ordering::SeqCst);
let _ = reconciler_handle.await;
let _ = colonyos::remove_blueprint(&colony_name, "office-thermostat", &colonyprvkey).await;
let _ = colonyos::remove_blueprint_definition(&colony_name, "thermostat-def", &colonyprvkey).await;
let _ = colonyos::remove_colony(&colony_name, &SERVER_PRVKEY).await;
println!("Blueprint reconciliation not supported - test skipped");
return;
}
}
let start = std::time::Instant::now();
let timeout = std::time::Duration::from_secs(10);
loop {
let reconciled = reconciled_blueprints.lock().unwrap();
if reconciled.contains(&"office-thermostat".to_string()) {
break;
}
drop(reconciled);
if start.elapsed() > timeout {
break;
}
tokio::task::yield_now().await;
}
reconciler_stop.store(true, Ordering::SeqCst);
let _ = reconciler_handle.await;
let final_bp = colonyos::get_blueprint(&colony_name, "office-thermostat", &colonyprvkey)
.await
.unwrap();
let reconciled_list = reconciled_blueprints.lock().unwrap();
if reconciled_list.contains(&"office-thermostat".to_string()) {
assert_eq!(final_bp.status.get("currentTemp"), Some(&json!(22)));
assert_eq!(final_bp.status.get("mode"), Some(&json!("heat")));
assert_eq!(final_bp.status.get("reconciled"), Some(&json!(true)));
println!("Reconciler successfully processed blueprint");
} else {
println!("Reconciler did not receive a process (server may handle reconciliation differently)");
}
let _ = colonyos::remove_blueprint(&colony_name, "office-thermostat", &colonyprvkey).await;
let _ = colonyos::remove_blueprint_definition(&colony_name, "thermostat-def", &colonyprvkey).await;
let _ = colonyos::remove_colony(&colony_name, &SERVER_PRVKEY).await;
}