From 573f84ecbb2d0a849e12d36be1f18e59c89bc6b5 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 7 Jun 2026 19:06:38 +0200 Subject: [PATCH 1/2] deps: updated tucana and code0-flow --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2bc7526..d1f6b2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,9 +295,9 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "code0-flow" -version = "0.0.34" +version = "0.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f853f9acd93a348d5c447bfbd404b72f50e49dfd9333eb0b769db8d268d31a7" +checksum = "b91a94cc5ff9392ea98ad89d2e5dbccca6129d664d0d80a55e0e73781b928401" dependencies = [ "async-nats", "async-trait", @@ -2186,9 +2186,9 @@ dependencies = [ [[package]] name = "tucana" -version = "0.0.73" +version = "0.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8c3882c50dad78a4ec128e9c0d80a681c0203e0ca55169361c49b31ad9161f" +checksum = "f015be0f6f1b5be9d5b650a432a1f731b70342506a452418bbc57441bd6cc7d1" dependencies = [ "pbjson", "pbjson-build", diff --git a/Cargo.toml b/Cargo.toml index 553f3eb..e98c592 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,8 @@ edition = "2024" [workspace.dependencies] async-trait = "0.1.89" -code0-flow = { version = "0.0.34" } -tucana = { version = "0.0.73" } +code0-flow = { version = "0.0.36" } +tucana = { version = "0.0.74" } tokio = { version = "1.44.1", features = ["rt-multi-thread", "signal"] } log = "0.4.27" futures-lite = "2.6.0" From e901e9dd5f4a8e608770920a506e15f52281680a Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 7 Jun 2026 19:54:07 +0200 Subject: [PATCH 2/2] feat: added subflow handling to execution results --- crates/taurus-core/src/runtime/engine.rs | 96 +++++++++++-- .../src/runtime/engine/executor.rs | 82 +++++++++++- .../src/runtime/execution/value_store.rs | 42 +++++- crates/taurus-manual/src/main.rs | 13 +- crates/taurus/src/app/mod.rs | 63 ++++++++- crates/taurus/src/app/worker.rs | 2 +- crates/taurus/src/client/runtime_status.rs | 126 ++++++++++++------ 7 files changed, 358 insertions(+), 66 deletions(-) diff --git a/crates/taurus-core/src/runtime/engine.rs b/crates/taurus-core/src/runtime/engine.rs index b173288..924c804 100644 --- a/crates/taurus-core/src/runtime/engine.rs +++ b/crates/taurus-core/src/runtime/engine.rs @@ -411,6 +411,20 @@ mod tests { } } + fn assert_node_result_id(result: &NodeExecutionResult, expected_id: i64) { + assert_eq!( + result.id, + Some(node_execution_result::Id::NodeId(expected_id)) + ); + } + + fn assert_function_result_id(result: &NodeExecutionResult, expected_id: i64) { + assert_eq!( + result.id, + Some(node_execution_result::Id::FunctionId(expected_id)) + ); + } + fn sleep_handler( _args: &[Argument], _ctx: &mut ValueStore, @@ -420,6 +434,21 @@ mod tests { Signal::Success(null_value()) } + fn echo_first_arg_handler( + args: &[Argument], + _ctx: &mut ValueStore, + _run: &mut ThunkRunner<'_>, + ) -> Signal { + match args.first() { + Some(Argument::Eval(value)) => Signal::Success(value.clone()), + _ => Signal::Failure(crate::types::errors::runtime_error::RuntimeError::new( + "T-TEST-000001", + "MissingEchoArgument", + "expected first eager argument", + )), + } + } + #[derive(Clone)] struct StubRemoteRuntime { result: NodeExecutionResult, @@ -848,6 +877,57 @@ mod tests { assert_eq!(expect_success(signal), int_value(42)); } + #[test] + fn execution_report_includes_function_identifier_subflow_results() { + let mut handlers = FunctionStore::default(); + handlers.populate(&[FunctionRegistration::eager("42", echo_first_arg_handler, 1)]); + let engine = ExecutionEngine { handlers }; + + let add_node = node( + 1, + "std::number::add", + vec![ + function_thunk_param( + 100, + "lhs", + "42", + vec![subflow_setting("value", Some(int_value(20)), false, true)], + ), + literal_param(101, "rhs", int_value(2)), + ], + None, + ); + + let report = engine.execute_graph_report(1, vec![add_node], None, None, None, false); + + assert_eq!(report.exit_reason, ExitReason::Success); + assert_eq!(expect_success(report.signal), int_value(22)); + assert_eq!(report.node_execution_results.len(), 2); + + let function_result = &report.node_execution_results[0]; + assert_function_result_id(function_result, 42); + assert_eq!(function_result.parameter_results.len(), 1); + assert_eq!( + function_result.parameter_results[0].value, + Some(int_value(20)) + ); + match function_result.result.as_ref() { + Some(node_execution_result::Result::Success(value)) => { + assert_eq!(value, &int_value(20)); + } + other => panic!("expected function success result, got {:?}", other), + } + + let node_result = &report.node_execution_results[1]; + assert_node_result_id(node_result, 1); + match node_result.result.as_ref() { + Some(node_execution_result::Result::Success(value)) => { + assert_eq!(value, &int_value(22)); + } + other => panic!("expected node success result, got {:?}", other), + } + } + #[test] fn execution_report_includes_literal_node_parameter_results() { let engine = ExecutionEngine::new(); @@ -867,7 +947,7 @@ mod tests { assert_eq!(report.node_execution_results.len(), 1); let node_result = &report.node_execution_results[0]; - assert_eq!(node_result.node_id, 1); + assert_node_result_id(node_result, 1); assert_eq!(node_result.parameter_results.len(), 2); assert_eq!(node_result.parameter_results[0].value, Some(int_value(1))); assert_eq!(node_result.parameter_results[1].value, Some(int_value(2))); @@ -906,7 +986,7 @@ mod tests { assert_eq!(report.node_execution_results.len(), 2); let node_result = &report.node_execution_results[1]; - assert_eq!(node_result.node_id, 2); + assert_node_result_id(node_result, 2); assert_eq!(node_result.parameter_results.len(), 2); assert_eq!(node_result.parameter_results[0].value, Some(int_value(7))); assert_eq!(node_result.parameter_results[1].value, Some(int_value(5))); @@ -939,7 +1019,7 @@ mod tests { assert_eq!(report.node_execution_results.len(), 1); let node_result = &report.node_execution_results[0]; - assert_eq!(node_result.node_id, 1); + assert_node_result_id(node_result, 1); assert_eq!(node_result.parameter_results.len(), 3); assert_eq!(node_result.parameter_results[0].value, Some(int_value(200))); assert_eq!( @@ -961,10 +1041,10 @@ mod tests { let engine = ExecutionEngine::new(); let remote = StubRemoteRuntime { result: NodeExecutionResult { - node_id: 99, started_at: 1, finished_at: 2, parameter_results: Vec::new(), + id: Some(node_execution_result::Id::NodeId(99)), result: None, }, }; @@ -987,7 +1067,7 @@ mod tests { assert_eq!(report.node_execution_results.len(), 1); let node_result = &report.node_execution_results[0]; - assert_eq!(node_result.node_id, 1); + assert_node_result_id(node_result, 1); assert_eq!(node_result.parameter_results.len(), 1); assert_eq!(node_result.parameter_results[0].value, Some(int_value(20))); match node_result.result.as_ref() { @@ -1012,7 +1092,7 @@ mod tests { assert_eq!(report.node_execution_results.len(), 1); let node_result = &report.node_execution_results[0]; - assert_eq!(node_result.node_id, 1); + assert_node_result_id(node_result, 1); assert!(node_result.started_at >= 1_000_000_000_000_000); assert!(node_result.finished_at > node_result.started_at); assert!(node_result.finished_at - node_result.started_at >= 1_000); @@ -1059,7 +1139,7 @@ mod tests { let callback_results: Vec<_> = report .node_execution_results .iter() - .filter(|result| result.node_id == 2) + .filter(|result| result.id == Some(node_execution_result::Id::NodeId(2))) .collect(); assert_eq!(callback_results.len(), 3); @@ -1093,7 +1173,7 @@ mod tests { vec![Some(int_value(3)), Some(int_value(2))], ] ); - assert_eq!(report.node_execution_results[3].node_id, 1); + assert_node_result_id(&report.node_execution_results[3], 1); } #[test] diff --git a/crates/taurus-core/src/runtime/engine/executor.rs b/crates/taurus-core/src/runtime/engine/executor.rs index 05aa1ec..013d9d1 100644 --- a/crates/taurus-core/src/runtime/engine/executor.rs +++ b/crates/taurus-core/src/runtime/engine/executor.rs @@ -183,15 +183,27 @@ impl<'a> EngineExecutor<'a> { function: &FunctionThunk, value_store: &mut ValueStore, ) -> ExecutionResult { + let started_at = now_unix_micros(); + let function_result_id = parse_function_result_id(function); let entry = match self.handlers.get(function.identifier.as_str()).copied() { Some(entry) => entry, None => { + let error = RuntimeError::new( + "T-CORE-000002", + "FunctionNotFound", + format!("Function {} not found", function.identifier), + ); + if let Some(function_id) = function_result_id { + value_store.insert_function_error_with_timing( + function_id, + error.clone(), + Vec::new(), + started_at, + now_unix_micros(), + ); + } return ExecutionResult { - signal: Signal::Failure(RuntimeError::new( - "T-CORE-000002", - "FunctionNotFound", - format!("Function {} not found", function.identifier), - )), + signal: Signal::Failure(error), root_frame: None, }; } @@ -208,12 +220,24 @@ impl<'a> EngineExecutor<'a> { Err(err) => { let signal = Signal::Failure(err); self.trace_exit(frame_id, &signal, value_store); + if let Some(function_id) = function_result_id { + let parameter_results = Vec::new(); + self.commit_function_result( + function_id, + signal.clone(), + parameter_results, + started_at, + now_unix_micros(), + value_store, + ); + } return ExecutionResult { signal, root_frame: frame_id, }; } }; + let parameter_results = parameter_results_from_args(&args); let signal = if let Some(signal) = self.force_eager_args(&entry, &mut args, value_store, frame_id) { @@ -233,6 +257,16 @@ impl<'a> EngineExecutor<'a> { }; self.trace_exit(frame_id, &signal, value_store); + if let Some(function_id) = function_result_id { + self.commit_function_result( + function_id, + signal.clone(), + parameter_results, + started_at, + now_unix_micros(), + value_store, + ); + } ExecutionResult { signal, @@ -747,6 +781,40 @@ impl<'a> EngineExecutor<'a> { } } + fn commit_function_result( + &self, + function_id: i64, + signal: Signal, + parameter_results: Vec, + started_at: i64, + finished_at: i64, + value_store: &mut ValueStore, + ) -> Signal { + match signal { + Signal::Success(value) => { + value_store.insert_function_success_with_timing( + function_id, + value.clone(), + parameter_results, + started_at, + finished_at, + ); + Signal::Success(value) + } + Signal::Failure(err) => { + value_store.insert_function_error_with_timing( + function_id, + err.clone(), + parameter_results, + started_at, + finished_at, + ); + Signal::Failure(err) + } + other => other, + } + } + fn commit_remote_result( &self, node_id: i64, @@ -901,6 +969,10 @@ fn compiled_thunk_to_argument(thunk: &CompiledThunk) -> Thunk { } } +fn parse_function_result_id(function: &FunctionThunk) -> Option { + function.identifier.parse::().ok() +} + fn resolve_function_setting( function: &FunctionThunk, setting: &SubFlowSetting, diff --git a/crates/taurus-core/src/runtime/execution/value_store.rs b/crates/taurus-core/src/runtime/execution/value_store.rs index f511517..65d5afa 100644 --- a/crates/taurus-core/src/runtime/execution/value_store.rs +++ b/crates/taurus-core/src/runtime/execution/value_store.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; -use tucana::shared::node_execution_result::Result as TucanaNodeResult; +use tucana::shared::node_execution_result::{Id as TucanaNodeResultId, Result as TucanaNodeResult}; use tucana::shared::{ InputType, NodeExecutionResult, NodeParameterNodeExecutionResult, ReferenceValue, Value, value::Kind, @@ -157,10 +157,10 @@ impl ValueStore { self.insert_node_result( id, NodeExecutionResult { - node_id: id, started_at, finished_at, parameter_results, + id: Some(TucanaNodeResultId::NodeId(id)), result: Some(TucanaNodeResult::Success(value)), }, ); @@ -177,21 +177,55 @@ impl ValueStore { self.insert_node_result( id, NodeExecutionResult { - node_id: id, started_at, finished_at, parameter_results, + id: Some(TucanaNodeResultId::NodeId(id)), result: Some(TucanaNodeResult::Error(runtime_error.as_tucana_error())), }, ); } pub fn insert_node_result(&mut self, id: i64, mut result: NodeExecutionResult) { - result.node_id = id; + result.id = Some(TucanaNodeResultId::NodeId(id)); self.latest_results.insert(id, result.clone()); self.result_history.push(result); } + pub fn insert_function_success_with_timing( + &mut self, + id: i64, + value: Value, + parameter_results: Vec, + started_at: i64, + finished_at: i64, + ) { + self.result_history.push(NodeExecutionResult { + started_at, + finished_at, + parameter_results, + id: Some(TucanaNodeResultId::FunctionId(id)), + result: Some(TucanaNodeResult::Success(value)), + }); + } + + pub fn insert_function_error_with_timing( + &mut self, + id: i64, + runtime_error: RuntimeError, + parameter_results: Vec, + started_at: i64, + finished_at: i64, + ) { + self.result_history.push(NodeExecutionResult { + started_at, + finished_at, + parameter_results, + id: Some(TucanaNodeResultId::FunctionId(id)), + result: Some(TucanaNodeResult::Error(runtime_error.as_tucana_error())), + }); + } + pub fn node_execution_results(&self) -> Vec { self.result_history.clone() } diff --git a/crates/taurus-manual/src/main.rs b/crates/taurus-manual/src/main.rs index 627151d..e2efc2e 100644 --- a/crates/taurus-manual/src/main.rs +++ b/crates/taurus-manual/src/main.rs @@ -16,6 +16,7 @@ use tucana::shared::NodeExecutionResult; use tucana::shared::ValidationFlow; use tucana::shared::helper::value::from_json_value; use tucana::shared::helper::value::to_json_value; +use tucana::shared::node_execution_result::Id as NodeExecutionResultId; #[derive(Clone, Deserialize)] pub struct Input { @@ -330,9 +331,9 @@ fn print_timing_debug( .join(", "); eprintln!( - "[manual timing] execution_index={} node_id={} started_at_unix_us={} finished_at_unix_us={} delta_us={} params=[{}]", + "[manual timing] execution_index={} {} started_at_unix_us={} finished_at_unix_us={} delta_us={} params=[{}]", execution_index, - result.node_id, + execution_result_id_label(result), result.started_at, result.finished_at, result.finished_at - result.started_at, @@ -340,3 +341,11 @@ fn print_timing_debug( ); } } + +fn execution_result_id_label(result: &NodeExecutionResult) -> String { + match result.id { + Some(NodeExecutionResultId::NodeId(id)) => format!("node_id={}", id), + Some(NodeExecutionResultId::FunctionId(id)) => format!("function_id={}", id), + None => "id=".to_string(), + } +} diff --git a/crates/taurus/src/app/mod.rs b/crates/taurus/src/app/mod.rs index 4fabc48..a9988e0 100644 --- a/crates/taurus/src/app/mod.rs +++ b/crates/taurus/src/app/mod.rs @@ -2,6 +2,7 @@ mod worker; use code0_flow::flow_config::load_env_file; use code0_flow::flow_config::mode::Mode::DYNAMIC; +use code0_flow::flow_definition::Reader; use code0_flow::flow_service::FlowUpdateService; use std::sync::Arc; use std::time::Duration; @@ -12,6 +13,7 @@ use tokio::signal; use tokio::task::JoinHandle; use tokio::time::sleep; use tonic_health::pb::health_server::HealthServer; +use tucana::shared::module_status::StatusVariant; use crate::client::runtime_execution::TaurusRuntimeExecutionService; use crate::client::runtime_status::TaurusRuntimeStatusService; @@ -136,14 +138,14 @@ async fn setup_dynamic_services_if_needed( TaurusRuntimeStatusService::from_url( config.aquila_url.clone(), config.aquila_token.clone(), - "taurus".into(), + read_module_status_identifiers(config.definitions.as_str()), ) .await, )); if let Some(status_service) = runtime_status_service.as_ref() { status_service - .update_runtime_status(tucana::shared::execution_runtime_status::Status::Running) + .update_runtime_status(StatusVariant::Running) .await; } @@ -164,9 +166,7 @@ async fn setup_dynamic_services_if_needed( loop { interval.tick().await; status_service - .update_runtime_status( - tucana::shared::execution_runtime_status::Status::Running, - ) + .update_runtime_status(StatusVariant::Running) .await; } }); @@ -189,6 +189,24 @@ async fn setup_dynamic_services_if_needed( ) } +fn read_module_status_identifiers(definition_path: &str) -> Vec { + let reader = Reader::configure(definition_path.to_string(), true, Vec::new(), None); + match reader.read_modules() { + Ok(modules) => modules + .into_iter() + .map(|module| module.identifier) + .filter(|identifier| !identifier.is_empty()) + .collect(), + Err(err) => { + log::error!( + "Failed to read module definitions for runtime status: {:?}", + err + ); + Vec::new() + } + } +} + async fn push_definitions_until_success(config: &Config) { let mut definition_service = FlowUpdateService::from_url( config.aquila_url.clone(), @@ -216,7 +234,7 @@ async fn push_definitions_until_success(config: &Config) { async fn update_stopped_status(runtime_status_service: Option<&Arc>) { if let Some(status_service) = runtime_status_service { status_service - .update_runtime_status(tucana::shared::execution_runtime_status::Status::Stopped) + .update_runtime_status(StatusVariant::Stopped) .await; } } @@ -273,3 +291,36 @@ async fn wait_for_shutdown( } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + + #[test] + fn reads_status_identifiers_for_each_readable_module() { + let root = + std::env::temp_dir().join(format!("taurus-module-status-test-{}", std::process::id())); + let module_a = root.join("module-a"); + let module_b = root.join("module-b"); + fs::create_dir_all(&module_a).expect("create module-a"); + fs::create_dir_all(&module_b).expect("create module-b"); + fs::write( + module_a.join("module.json"), + r#"{"identifier":"alpha","name":[],"description":[],"documentation":"","author":"","icon":""}"#, + ) + .expect("write module-a"); + fs::write( + module_b.join("module.json"), + r#"{"identifier":"beta","name":[],"description":[],"documentation":"","author":"","icon":""}"#, + ) + .expect("write module-b"); + + let mut identifiers = read_module_status_identifiers(root.to_str().expect("utf-8 path")); + identifiers.sort(); + + assert_eq!(identifiers, vec!["alpha".to_string(), "beta".to_string()]); + + fs::remove_dir_all(root).expect("cleanup module status test"); + } +} diff --git a/crates/taurus/src/app/worker.rs b/crates/taurus/src/app/worker.rs index d08ce13..e6b0c6a 100644 --- a/crates/taurus/src/app/worker.rs +++ b/crates/taurus/src/app/worker.rs @@ -368,12 +368,12 @@ mod tests { fn build_execution_result_preserves_node_execution_results() { let execution_id = ExecutionId::new_v4(); let node_result = NodeExecutionResult { - node_id: 42, started_at: 1, finished_at: 2, parameter_results: vec![tucana::shared::NodeParameterNodeExecutionResult { value: Some(from_json_value(serde_json::json!("parameter-value"))), }], + id: Some(tucana::shared::node_execution_result::Id::NodeId(42)), result: Some(tucana::shared::node_execution_result::Result::Success( from_json_value(serde_json::json!("node-output")), )), diff --git a/crates/taurus/src/client/runtime_status.rs b/crates/taurus/src/client/runtime_status.rs index f2f21bb..6c5391f 100644 --- a/crates/taurus/src/client/runtime_status.rs +++ b/crates/taurus/src/client/runtime_status.rs @@ -7,69 +7,115 @@ use tonic::{Extensions, Request, transport::Channel}; use tucana::{ aquila::{ RuntimeStatusUpdateRequest, runtime_status_service_client::RuntimeStatusServiceClient, - runtime_status_update_request::Status, }, - shared::ExecutionRuntimeStatus, + shared::{ModuleStatus, module_status::StatusVariant}, }; pub struct TaurusRuntimeStatusService { channel: Channel, - identifier: String, + identifiers: Vec, aquila_token: String, } impl TaurusRuntimeStatusService { - pub async fn from_url(aquila_url: String, aquila_token: String, identifier: String) -> Self { + pub async fn from_url( + aquila_url: String, + aquila_token: String, + identifiers: Vec, + ) -> Self { let channel = create_channel_with_retry("Aquila", aquila_url).await; - Self::new(channel, aquila_token, identifier) + Self::new(channel, aquila_token, identifiers) } - pub fn new(channel: Channel, aquila_token: String, identifier: String) -> Self { + pub fn new(channel: Channel, aquila_token: String, identifiers: Vec) -> Self { TaurusRuntimeStatusService { channel, - identifier, + identifiers, aquila_token, } } - pub async fn update_runtime_status( - &self, - status: tucana::shared::execution_runtime_status::Status, - ) { + pub async fn update_runtime_status(&self, status: StatusVariant) { log::info!("Updating the current Runtime Status!"); let mut client = RuntimeStatusServiceClient::new(self.channel.clone()); + let timestamp = now_unix_seconds(); - let now = SystemTime::now(); - let timestamp = match now.duration_since(UNIX_EPOCH) { - Ok(time) => time.as_secs(), - Err(err) => { - log::error!("cannot get current system time: {:?}", err); - 0 - } - }; - - let request = Request::from_parts( - get_authorization_metadata(&self.aquila_token), - Extensions::new(), - RuntimeStatusUpdateRequest { - status: Some(Status::ExecutionRuntimeStatus(ExecutionRuntimeStatus { - status: status.into(), - timestamp: timestamp as i64, - identifier: self.identifier.clone(), - })), - }, - ); + for request in build_runtime_status_requests(&self.identifiers, status, timestamp) { + let request = Request::from_parts( + get_authorization_metadata(&self.aquila_token), + Extensions::new(), + request, + ); - match client.update(request).await { - Ok(response) => { - log::info!( - "Was the update of the RuntimeStatus accepted by Sagittarius? {}", - response.into_inner().success - ); - } - Err(err) => { - log::error!("Failed to update RuntimeStatus: {:?}", err); + match client.update(request).await { + Ok(response) => { + log::info!( + "Was the update of the RuntimeStatus accepted by Sagittarius? {}", + response.into_inner().success + ); + } + Err(err) => { + log::error!("Failed to update RuntimeStatus: {:?}", err); + } } } } } + +fn now_unix_seconds() -> i64 { + let now = SystemTime::now(); + match now.duration_since(UNIX_EPOCH) { + Ok(time) => time.as_secs() as i64, + Err(err) => { + log::error!("cannot get current system time: {:?}", err); + 0 + } + } +} + +pub(crate) fn build_runtime_status_requests( + identifiers: &[String], + status: StatusVariant, + timestamp: i64, +) -> Vec { + identifiers + .iter() + .map(|identifier| RuntimeStatusUpdateRequest { + status: Some(ModuleStatus { + status: status.into(), + timestamp, + identifier: identifier.clone(), + }), + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn builds_one_runtime_status_request_per_module_identifier() { + let identifiers = vec!["taurus".to_string(), "http".to_string()]; + + let requests = build_runtime_status_requests(&identifiers, StatusVariant::Running, 123); + + assert_eq!(requests.len(), 2); + assert_eq!( + requests[0].status, + Some(ModuleStatus { + identifier: "taurus".to_string(), + timestamp: 123, + status: StatusVariant::Running.into(), + }) + ); + assert_eq!( + requests[1].status, + Some(ModuleStatus { + identifier: "http".to_string(), + timestamp: 123, + status: StatusVariant::Running.into(), + }) + ); + } +}