Skip to main content

orcs_component/
testing.rs

1//! Testing harnesses for Component and Child implementations.
2//!
3//! Provides test harnesses for testing Component and Child implementations
4//! without requiring the full OrcsEngine infrastructure.
5//!
6//! # Features
7//!
8//! - Engine-independent component/child testing
9//! - Request/Signal/Lifecycle processing
10//! - Event logging for snapshot testing
11//! - Deterministic synchronous execution
12//! - Async child support with timeout
13//! - Automatic time measurement
14//!
15//! # Component Testing Example
16//!
17//! ```
18//! use orcs_component::testing::{ComponentTestHarness, RequestRecord};
19//! use orcs_component::{Component, ComponentError, Status, EventCategory};
20//! use orcs_event::{Request, Signal, SignalResponse};
21//! use orcs_types::ComponentId;
22//! use serde_json::{json, Value};
23//!
24//! struct EchoComponent {
25//!     id: ComponentId,
26//!     status: Status,
27//! }
28//!
29//! impl Component for EchoComponent {
30//!     fn id(&self) -> &ComponentId { &self.id }
31//!     fn status(&self) -> Status { self.status }
32//!     fn on_request(&mut self, req: &Request) -> Result<Value, ComponentError> {
33//!         Ok(req.payload.clone())
34//!     }
35//!     fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
36//!         if signal.is_veto() {
37//!             self.abort();
38//!             SignalResponse::Abort
39//!         } else {
40//!             SignalResponse::Handled
41//!         }
42//!     }
43//!     fn abort(&mut self) { self.status = Status::Aborted; }
44//! }
45//!
46//! let echo = EchoComponent {
47//!     id: ComponentId::builtin("echo"),
48//!     status: Status::Idle,
49//! };
50//! let mut harness = ComponentTestHarness::new(echo);
51//!
52//! // Test request handling
53//! let result = harness.request(EventCategory::Echo, "echo", json!({"msg": "hello"}));
54//! assert!(result.is_ok());
55//!
56//! // Test signal handling
57//! let response = harness.veto();
58//! assert_eq!(response, SignalResponse::Abort);
59//! ```
60//!
61//! # Child Testing Example
62//!
63//! ```
64//! use orcs_component::testing::SyncChildTestHarness;
65//! use orcs_component::{Child, RunnableChild, ChildResult, Identifiable, SignalReceiver, Statusable, Status};
66//! use orcs_event::{Signal, SignalResponse};
67//! use serde_json::{json, Value};
68//!
69//! struct Worker {
70//!     id: String,
71//!     status: Status,
72//! }
73//!
74//! impl Identifiable for Worker {
75//!     fn id(&self) -> &str { &self.id }
76//! }
77//!
78//! impl SignalReceiver for Worker {
79//!     fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
80//!         if signal.is_veto() {
81//!             self.abort();
82//!             SignalResponse::Abort
83//!         } else {
84//!             SignalResponse::Handled
85//!         }
86//!     }
87//!     fn abort(&mut self) { self.status = Status::Aborted; }
88//! }
89//!
90//! impl Statusable for Worker {
91//!     fn status(&self) -> Status { self.status }
92//! }
93//!
94//! impl Child for Worker {}
95//!
96//! impl RunnableChild for Worker {
97//!     fn run(&mut self, input: Value) -> ChildResult {
98//!         self.status = Status::Running;
99//!         let result = json!({"processed": input});
100//!         self.status = Status::Idle;
101//!         ChildResult::Ok(result)
102//!     }
103//! }
104//!
105//! let worker = Worker { id: "worker-1".into(), status: Status::Idle };
106//! let mut harness = SyncChildTestHarness::new(worker);
107//!
108//! // Test run
109//! let result = harness.run(json!({"task": "test"}));
110//! assert!(result.is_ok());
111//!
112//! // Test signal handling
113//! let response = harness.veto();
114//! assert_eq!(response, SignalResponse::Abort);
115//!
116//! // Check logs
117//! assert_eq!(harness.run_log().len(), 1);
118//! assert!(harness.run_log()[0].elapsed_ms.is_some());
119//! ```
120
121use crate::{
122    AsyncRunnableChild, Child, ChildResult, Component, ComponentError, EventCategory,
123    RunnableChild, Status,
124};
125use orcs_event::{Signal, SignalResponse};
126use orcs_types::{ChannelId, ComponentId, Principal};
127use serde::{Deserialize, Serialize};
128use serde_json::Value;
129use std::time::{Duration, Instant};
130use thiserror::Error;
131
132/// Record of a request sent to a component.
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct RequestRecord {
135    /// Operation name.
136    pub operation: String,
137    /// Event category.
138    pub category: String,
139    /// Result of the request.
140    pub result: RequestResult,
141}
142
143/// Result of a request.
144#[derive(Debug, Clone, Serialize, Deserialize)]
145#[serde(tag = "type", content = "value")]
146pub enum RequestResult {
147    /// Request succeeded with a value.
148    Ok(Value),
149    /// Request failed with an error message.
150    Err(String),
151}
152
153/// Record of a signal sent to a component.
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct SignalRecord {
156    /// Signal kind (e.g., "Veto", "Cancel", "Approve").
157    pub kind: String,
158    /// Response from the component.
159    pub response: String,
160}
161
162/// Test harness for Component implementations.
163///
164/// Provides a minimal testing environment for components
165/// without requiring OrcsEngine or EventBus.
166pub struct ComponentTestHarness<C: Component> {
167    /// The component under test.
168    component: C,
169    /// Log of requests sent to the component.
170    request_log: Vec<RequestRecord>,
171    /// Log of signals sent to the component.
172    signal_log: Vec<SignalRecord>,
173    /// Test channel ID for request context.
174    test_channel: ChannelId,
175}
176
177impl<C: Component> ComponentTestHarness<C> {
178    /// Creates a new test harness for the given component.
179    pub fn new(component: C) -> Self {
180        Self {
181            component,
182            request_log: Vec::new(),
183            signal_log: Vec::new(),
184            test_channel: ChannelId::new(),
185        }
186    }
187
188    /// Returns a reference to the component under test.
189    pub fn component(&self) -> &C {
190        &self.component
191    }
192
193    /// Returns a mutable reference to the component under test.
194    pub fn component_mut(&mut self) -> &mut C {
195        &mut self.component
196    }
197
198    /// Calls `init()` on the component with an empty config.
199    ///
200    /// # Errors
201    ///
202    /// Returns the component's initialization error if any.
203    pub fn init(&mut self) -> Result<(), ComponentError> {
204        self.component
205            .init(&serde_json::Value::Object(serde_json::Map::new()))
206    }
207
208    /// Calls `init()` on the component with the given config.
209    ///
210    /// # Errors
211    ///
212    /// Returns the component's initialization error if any.
213    pub fn init_with_config(&mut self, config: &serde_json::Value) -> Result<(), ComponentError> {
214        self.component.init(config)
215    }
216
217    /// Sends a request to the component and logs the result.
218    ///
219    /// # Arguments
220    ///
221    /// * `request` - The request to send
222    ///
223    /// # Returns
224    ///
225    /// The result of the request.
226    pub fn send_request(&mut self, request: &orcs_event::Request) -> Result<Value, ComponentError> {
227        let result = self.component.on_request(request);
228
229        self.request_log.push(RequestRecord {
230            operation: request.operation.clone(),
231            category: request.category.name(),
232            result: match &result {
233                Ok(v) => RequestResult::Ok(v.clone()),
234                Err(e) => RequestResult::Err(e.to_string()),
235            },
236        });
237
238        result
239    }
240
241    /// Sends a request with the given category, operation, and payload.
242    ///
243    /// Convenience method that builds the request internally.
244    ///
245    /// # Arguments
246    ///
247    /// * `category` - Event category
248    /// * `operation` - Operation name
249    /// * `payload` - Request payload
250    ///
251    /// # Returns
252    ///
253    /// The result of the request.
254    pub fn request(
255        &mut self,
256        category: EventCategory,
257        operation: &str,
258        payload: Value,
259    ) -> Result<Value, ComponentError> {
260        let req = orcs_event::Request::new(
261            category,
262            operation,
263            self.component.id().clone(),
264            self.test_channel,
265            payload,
266        );
267        self.send_request(&req)
268    }
269
270    /// Sends a signal to the component and logs the response.
271    ///
272    /// # Arguments
273    ///
274    /// * `signal` - The signal to send
275    ///
276    /// # Returns
277    ///
278    /// The component's response to the signal.
279    pub fn send_signal(&mut self, signal: Signal) -> SignalResponse {
280        let response = self.component.on_signal(&signal);
281
282        self.signal_log.push(SignalRecord {
283            kind: format!("{:?}", signal.kind),
284            response: format!("{:?}", response),
285        });
286
287        response
288    }
289
290    /// Sends a Veto signal to the component.
291    ///
292    /// # Returns
293    ///
294    /// The component's response (should be `Abort`).
295    pub fn veto(&mut self) -> SignalResponse {
296        self.send_signal(Signal::veto(Principal::System))
297    }
298
299    /// Sends a Cancel signal for the test channel.
300    ///
301    /// # Returns
302    ///
303    /// The component's response.
304    pub fn cancel(&mut self) -> SignalResponse {
305        self.send_signal(Signal::cancel(self.test_channel, Principal::System))
306    }
307
308    /// Sends a Cancel signal for a specific channel.
309    ///
310    /// # Arguments
311    ///
312    /// * `channel` - The channel to cancel
313    ///
314    /// # Returns
315    ///
316    /// The component's response.
317    pub fn cancel_channel(&mut self, channel: ChannelId) -> SignalResponse {
318        self.send_signal(Signal::cancel(channel, Principal::System))
319    }
320
321    /// Sends an Approve signal.
322    ///
323    /// # Arguments
324    ///
325    /// * `approval_id` - The approval request ID
326    ///
327    /// # Returns
328    ///
329    /// The component's response.
330    pub fn approve(&mut self, approval_id: &str) -> SignalResponse {
331        self.send_signal(Signal::approve(approval_id, Principal::System))
332    }
333
334    /// Sends a Reject signal.
335    ///
336    /// # Arguments
337    ///
338    /// * `approval_id` - The approval request ID
339    /// * `reason` - Optional rejection reason
340    ///
341    /// # Returns
342    ///
343    /// The component's response.
344    pub fn reject(&mut self, approval_id: &str, reason: Option<String>) -> SignalResponse {
345        self.send_signal(Signal::reject(approval_id, reason, Principal::System))
346    }
347
348    /// Calls `abort()` on the component.
349    pub fn abort(&mut self) {
350        self.component.abort();
351    }
352
353    /// Calls `shutdown()` on the component.
354    pub fn shutdown(&mut self) {
355        self.component.shutdown();
356    }
357
358    /// Returns the current status of the component.
359    pub fn status(&self) -> Status {
360        self.component.status()
361    }
362
363    /// Returns the component ID.
364    pub fn id(&self) -> &ComponentId {
365        self.component.id()
366    }
367
368    /// Returns the request log for snapshot testing.
369    pub fn request_log(&self) -> &[RequestRecord] {
370        &self.request_log
371    }
372
373    /// Returns the signal log for snapshot testing.
374    pub fn signal_log(&self) -> &[SignalRecord] {
375        &self.signal_log
376    }
377
378    /// Clears all logs.
379    pub fn clear_logs(&mut self) {
380        self.request_log.clear();
381        self.signal_log.clear();
382    }
383
384    /// Returns the test channel ID.
385    pub fn test_channel(&self) -> ChannelId {
386        self.test_channel
387    }
388
389    /// Sets a custom test channel ID.
390    pub fn with_channel(mut self, channel: ChannelId) -> Self {
391        self.test_channel = channel;
392        self
393    }
394}
395
396// =============================================================================
397// Child Test Harness Types
398// =============================================================================
399
400/// Record of a run() call on a child.
401#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct RunRecord {
403    /// Input value passed to run().
404    pub input: Value,
405    /// Result of the run() call.
406    pub result: RunResult,
407    /// Elapsed time in milliseconds.
408    pub elapsed_ms: Option<u64>,
409}
410
411/// Result of a run() call.
412#[derive(Debug, Clone, Serialize, Deserialize)]
413#[serde(tag = "type")]
414pub enum RunResult {
415    /// Run completed successfully.
416    Ok {
417        /// Output value.
418        value: Value,
419    },
420    /// Run failed with an error.
421    Err {
422        /// Error kind identifier.
423        kind: String,
424        /// Error message.
425        message: String,
426    },
427    /// Run was aborted.
428    Aborted,
429}
430
431impl From<&ChildResult> for RunResult {
432    fn from(result: &ChildResult) -> Self {
433        match result {
434            ChildResult::Ok(v) => Self::Ok { value: v.clone() },
435            ChildResult::Err(e) => Self::Err {
436                kind: e.kind().to_string(),
437                message: e.to_string(),
438            },
439            ChildResult::Aborted => Self::Aborted,
440        }
441    }
442}
443
444/// Error returned when an async operation times out.
445#[derive(Debug, Clone, Error)]
446#[error("operation timed out after {elapsed_ms}ms (limit: {timeout_ms}ms)")]
447pub struct TimeoutError {
448    /// Elapsed time before timeout.
449    pub elapsed_ms: u64,
450    /// Configured timeout.
451    pub timeout_ms: u64,
452}
453
454// =============================================================================
455// ChildTestHarness (Base - for Child trait)
456// =============================================================================
457
458/// Test harness for Child trait implementations.
459///
460/// Provides signal handling and status testing for any Child.
461/// For testing `run()`, use [`SyncChildTestHarness`] or [`AsyncChildTestHarness`].
462///
463/// # Example
464///
465/// ```
466/// use orcs_component::testing::ChildTestHarness;
467/// use orcs_component::{Child, Identifiable, SignalReceiver, Statusable, Status};
468/// use orcs_event::{Signal, SignalResponse};
469///
470/// struct PassiveChild {
471///     id: String,
472///     status: Status,
473/// }
474///
475/// impl Identifiable for PassiveChild {
476///     fn id(&self) -> &str { &self.id }
477/// }
478///
479/// impl SignalReceiver for PassiveChild {
480///     fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
481///         if signal.is_veto() {
482///             self.abort();
483///             SignalResponse::Abort
484///         } else {
485///             SignalResponse::Handled
486///         }
487///     }
488///     fn abort(&mut self) { self.status = Status::Aborted; }
489/// }
490///
491/// impl Statusable for PassiveChild {
492///     fn status(&self) -> Status { self.status }
493/// }
494///
495/// impl Child for PassiveChild {}
496///
497/// let child = PassiveChild { id: "test".into(), status: Status::Idle };
498/// let mut harness = ChildTestHarness::new(child);
499///
500/// assert_eq!(harness.status(), Status::Idle);
501/// harness.veto();
502/// assert_eq!(harness.status(), Status::Aborted);
503/// ```
504pub struct ChildTestHarness<C: Child> {
505    /// The child under test.
506    child: C,
507    /// Log of signals sent to the child.
508    signal_log: Vec<SignalRecord>,
509}
510
511impl<C: Child> ChildTestHarness<C> {
512    /// Creates a new test harness for the given child.
513    pub fn new(child: C) -> Self {
514        Self {
515            child,
516            signal_log: Vec::new(),
517        }
518    }
519
520    /// Returns a reference to the child under test.
521    pub fn child(&self) -> &C {
522        &self.child
523    }
524
525    /// Returns a mutable reference to the child under test.
526    pub fn child_mut(&mut self) -> &mut C {
527        &mut self.child
528    }
529
530    /// Returns the child's ID.
531    pub fn id(&self) -> &str {
532        self.child.id()
533    }
534
535    /// Returns the current status of the child.
536    pub fn status(&self) -> Status {
537        self.child.status()
538    }
539
540    /// Sends a signal to the child and logs the response.
541    pub fn send_signal(&mut self, signal: Signal) -> SignalResponse {
542        let response = self.child.on_signal(&signal);
543
544        self.signal_log.push(SignalRecord {
545            kind: format!("{:?}", signal.kind),
546            response: format!("{:?}", response),
547        });
548
549        response
550    }
551
552    /// Sends a Veto signal to the child.
553    pub fn veto(&mut self) -> SignalResponse {
554        self.send_signal(Signal::veto(Principal::System))
555    }
556
557    /// Sends a Cancel signal to the child.
558    pub fn cancel(&mut self) -> SignalResponse {
559        self.send_signal(Signal::cancel(ChannelId::new(), Principal::System))
560    }
561
562    /// Calls `abort()` on the child.
563    pub fn abort(&mut self) {
564        self.child.abort();
565    }
566
567    /// Returns the signal log for snapshot testing.
568    pub fn signal_log(&self) -> &[SignalRecord] {
569        &self.signal_log
570    }
571
572    /// Clears all logs.
573    pub fn clear_logs(&mut self) {
574        self.signal_log.clear();
575    }
576}
577
578// =============================================================================
579// SyncChildTestHarness (for RunnableChild)
580// =============================================================================
581
582/// Test harness for synchronous RunnableChild implementations.
583///
584/// Provides run() testing with automatic time measurement.
585///
586/// # Example
587///
588/// ```
589/// use orcs_component::testing::SyncChildTestHarness;
590/// use orcs_component::{Child, RunnableChild, ChildResult, Identifiable, SignalReceiver, Statusable, Status};
591/// use orcs_event::{Signal, SignalResponse};
592/// use serde_json::{json, Value};
593///
594/// struct Worker {
595///     id: String,
596///     status: Status,
597/// }
598///
599/// impl Identifiable for Worker {
600///     fn id(&self) -> &str { &self.id }
601/// }
602///
603/// impl SignalReceiver for Worker {
604///     fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
605///         if signal.is_veto() {
606///             self.abort();
607///             SignalResponse::Abort
608///         } else {
609///             SignalResponse::Handled
610///         }
611///     }
612///     fn abort(&mut self) { self.status = Status::Aborted; }
613/// }
614///
615/// impl Statusable for Worker {
616///     fn status(&self) -> Status { self.status }
617/// }
618///
619/// impl Child for Worker {}
620///
621/// impl RunnableChild for Worker {
622///     fn run(&mut self, input: Value) -> ChildResult {
623///         ChildResult::Ok(json!({"echo": input}))
624///     }
625/// }
626///
627/// let worker = Worker { id: "worker".into(), status: Status::Idle };
628/// let mut harness = SyncChildTestHarness::new(worker);
629///
630/// let result = harness.run(json!({"task": "test"}));
631/// assert!(result.is_ok());
632/// assert_eq!(harness.run_log().len(), 1);
633/// ```
634pub struct SyncChildTestHarness<C: RunnableChild> {
635    /// Inner base harness.
636    inner: ChildTestHarness<C>,
637    /// Log of run() calls.
638    run_log: Vec<RunRecord>,
639    /// Whether to measure execution time.
640    measure_time: bool,
641}
642
643impl<C: RunnableChild> SyncChildTestHarness<C> {
644    /// Creates a new test harness for the given runnable child.
645    ///
646    /// Time measurement is enabled by default.
647    pub fn new(child: C) -> Self {
648        Self {
649            inner: ChildTestHarness::new(child),
650            run_log: Vec::new(),
651            measure_time: true,
652        }
653    }
654
655    /// Disables time measurement.
656    pub fn without_time_measurement(mut self) -> Self {
657        self.measure_time = false;
658        self
659    }
660
661    /// Executes run() on the child with the given input.
662    pub fn run(&mut self, input: Value) -> ChildResult {
663        let start = if self.measure_time {
664            Some(Instant::now())
665        } else {
666            None
667        };
668
669        let result = self.inner.child.run(input.clone());
670
671        let elapsed_ms = start.map(|s| s.elapsed().as_millis() as u64);
672
673        self.run_log.push(RunRecord {
674            input,
675            result: RunResult::from(&result),
676            elapsed_ms,
677        });
678
679        result
680    }
681
682    /// Executes run() with a serializable input.
683    pub fn run_json<T: Serialize>(&mut self, input: T) -> ChildResult {
684        let value = serde_json::to_value(input).unwrap_or(Value::Null);
685        self.run(value)
686    }
687
688    /// Returns the run log for snapshot testing.
689    pub fn run_log(&self) -> &[RunRecord] {
690        &self.run_log
691    }
692
693    /// Clears all logs (both run and signal).
694    pub fn clear_all_logs(&mut self) {
695        self.run_log.clear();
696        self.inner.clear_logs();
697    }
698
699    // --- Delegate to inner ChildTestHarness ---
700
701    /// Returns a reference to the child under test.
702    pub fn child(&self) -> &C {
703        self.inner.child()
704    }
705
706    /// Returns a mutable reference to the child under test.
707    pub fn child_mut(&mut self) -> &mut C {
708        self.inner.child_mut()
709    }
710
711    /// Returns the child's ID.
712    pub fn id(&self) -> &str {
713        self.inner.id()
714    }
715
716    /// Returns the current status of the child.
717    pub fn status(&self) -> Status {
718        self.inner.status()
719    }
720
721    /// Sends a signal to the child.
722    pub fn send_signal(&mut self, signal: Signal) -> SignalResponse {
723        self.inner.send_signal(signal)
724    }
725
726    /// Sends a Veto signal to the child.
727    pub fn veto(&mut self) -> SignalResponse {
728        self.inner.veto()
729    }
730
731    /// Sends a Cancel signal to the child.
732    pub fn cancel(&mut self) -> SignalResponse {
733        self.inner.cancel()
734    }
735
736    /// Calls `abort()` on the child.
737    pub fn abort(&mut self) {
738        self.inner.abort();
739    }
740
741    /// Returns the signal log.
742    pub fn signal_log(&self) -> &[SignalRecord] {
743        self.inner.signal_log()
744    }
745}
746
747// =============================================================================
748// AsyncChildTestHarness (for AsyncRunnableChild)
749// =============================================================================
750
751/// Test harness for asynchronous AsyncRunnableChild implementations.
752///
753/// Provides async run() testing with automatic time measurement and timeout support.
754///
755/// # Example
756///
757/// ```ignore
758/// use orcs_component::testing::AsyncChildTestHarness;
759/// use orcs_component::{Child, AsyncRunnableChild, ChildResult, async_trait};
760/// use serde_json::{json, Value};
761/// use std::time::Duration;
762///
763/// struct AsyncWorker { /* ... */ }
764///
765/// #[async_trait]
766/// impl AsyncRunnableChild for AsyncWorker {
767///     async fn run(&mut self, input: Value) -> ChildResult {
768///         tokio::time::sleep(Duration::from_millis(10)).await;
769///         ChildResult::Ok(json!({"processed": input}))
770///     }
771/// }
772///
773/// #[tokio::test]
774/// async fn test_async_worker() {
775///     let worker = AsyncWorker::new();
776///     let mut harness = AsyncChildTestHarness::new(worker)
777///         .with_default_timeout(Duration::from_secs(5));
778///
779///     let result = harness.run(json!({"task": "test"})).await;
780///     assert!(result.is_ok());
781///
782///     // Test timeout
783///     let slow_result = harness
784///         .run_with_timeout(json!({}), Duration::from_millis(1))
785///         .await;
786///     assert!(slow_result.is_err());
787/// }
788/// ```
789pub struct AsyncChildTestHarness<C: AsyncRunnableChild> {
790    /// Inner base harness.
791    inner: ChildTestHarness<C>,
792    /// Log of run() calls.
793    run_log: Vec<RunRecord>,
794    /// Whether to measure execution time.
795    measure_time: bool,
796    /// Default timeout for run operations.
797    default_timeout: Option<Duration>,
798}
799
800impl<C: AsyncRunnableChild> AsyncChildTestHarness<C> {
801    /// Creates a new test harness for the given async runnable child.
802    ///
803    /// Time measurement is enabled by default.
804    pub fn new(child: C) -> Self {
805        Self {
806            inner: ChildTestHarness::new(child),
807            run_log: Vec::new(),
808            measure_time: true,
809            default_timeout: None,
810        }
811    }
812
813    /// Disables time measurement.
814    pub fn without_time_measurement(mut self) -> Self {
815        self.measure_time = false;
816        self
817    }
818
819    /// Sets the default timeout for run operations.
820    pub fn with_default_timeout(mut self, timeout: Duration) -> Self {
821        self.default_timeout = Some(timeout);
822        self
823    }
824
825    /// Executes async run() on the child with the given input.
826    ///
827    /// If a default timeout is set, it will be applied.
828    pub async fn run(&mut self, input: Value) -> ChildResult {
829        if let Some(timeout) = self.default_timeout {
830            match self.run_with_timeout(input, timeout).await {
831                Ok(result) => result,
832                Err(_) => ChildResult::Aborted,
833            }
834        } else {
835            self.run_inner(input).await
836        }
837    }
838
839    /// Executes async run() with a timeout.
840    ///
841    /// Returns `Err(TimeoutError)` if the operation times out.
842    pub async fn run_with_timeout(
843        &mut self,
844        input: Value,
845        timeout: Duration,
846    ) -> Result<ChildResult, TimeoutError> {
847        let start = Instant::now();
848
849        match tokio::time::timeout(timeout, self.run_inner(input)).await {
850            Ok(result) => Ok(result),
851            Err(_) => Err(TimeoutError {
852                elapsed_ms: start.elapsed().as_millis() as u64,
853                timeout_ms: timeout.as_millis() as u64,
854            }),
855        }
856    }
857
858    /// Internal run implementation.
859    async fn run_inner(&mut self, input: Value) -> ChildResult {
860        let start = if self.measure_time {
861            Some(Instant::now())
862        } else {
863            None
864        };
865
866        let result = self.inner.child.run(input.clone()).await;
867
868        let elapsed_ms = start.map(|s| s.elapsed().as_millis() as u64);
869
870        self.run_log.push(RunRecord {
871            input,
872            result: RunResult::from(&result),
873            elapsed_ms,
874        });
875
876        result
877    }
878
879    /// Executes async run() with a serializable input.
880    pub async fn run_json<T: Serialize>(&mut self, input: T) -> ChildResult {
881        let value = serde_json::to_value(input).unwrap_or(Value::Null);
882        self.run(value).await
883    }
884
885    /// Returns the run log for snapshot testing.
886    pub fn run_log(&self) -> &[RunRecord] {
887        &self.run_log
888    }
889
890    /// Clears all logs (both run and signal).
891    pub fn clear_all_logs(&mut self) {
892        self.run_log.clear();
893        self.inner.clear_logs();
894    }
895
896    // --- Delegate to inner ChildTestHarness ---
897
898    /// Returns a reference to the child under test.
899    pub fn child(&self) -> &C {
900        self.inner.child()
901    }
902
903    /// Returns a mutable reference to the child under test.
904    pub fn child_mut(&mut self) -> &mut C {
905        self.inner.child_mut()
906    }
907
908    /// Returns the child's ID.
909    pub fn id(&self) -> &str {
910        self.inner.id()
911    }
912
913    /// Returns the current status of the child.
914    pub fn status(&self) -> Status {
915        self.inner.status()
916    }
917
918    /// Sends a signal to the child.
919    pub fn send_signal(&mut self, signal: Signal) -> SignalResponse {
920        self.inner.send_signal(signal)
921    }
922
923    /// Sends a Veto signal to the child.
924    pub fn veto(&mut self) -> SignalResponse {
925        self.inner.veto()
926    }
927
928    /// Sends a Cancel signal to the child.
929    pub fn cancel(&mut self) -> SignalResponse {
930        self.inner.cancel()
931    }
932
933    /// Calls `abort()` on the child.
934    pub fn abort(&mut self) {
935        self.inner.abort();
936    }
937
938    /// Returns the signal log.
939    pub fn signal_log(&self) -> &[SignalRecord] {
940        self.inner.signal_log()
941    }
942}
943
944#[cfg(test)]
945mod tests {
946    use super::*;
947    use crate::{Identifiable, SignalReceiver, Status, Statusable};
948
949    struct TestComponent {
950        id: ComponentId,
951        status: Status,
952        request_count: usize,
953    }
954
955    impl TestComponent {
956        fn new(name: &str) -> Self {
957            Self {
958                id: ComponentId::builtin(name),
959                status: Status::Idle,
960                request_count: 0,
961            }
962        }
963    }
964
965    impl Component for TestComponent {
966        fn id(&self) -> &ComponentId {
967            &self.id
968        }
969
970        fn status(&self) -> Status {
971            self.status
972        }
973
974        fn on_request(&mut self, request: &orcs_event::Request) -> Result<Value, ComponentError> {
975            self.request_count += 1;
976            match request.operation.as_str() {
977                "echo" => Ok(request.payload.clone()),
978                "count" => Ok(Value::Number(self.request_count.into())),
979                _ => Err(ComponentError::NotSupported(request.operation.clone())),
980            }
981        }
982
983        fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
984            if signal.is_veto() {
985                self.abort();
986                SignalResponse::Abort
987            } else if matches!(signal.kind, orcs_event::SignalKind::Cancel) {
988                SignalResponse::Handled
989            } else {
990                SignalResponse::Ignored
991            }
992        }
993
994        fn abort(&mut self) {
995            self.status = Status::Aborted;
996        }
997    }
998
999    #[test]
1000    fn harness_request() {
1001        let comp = TestComponent::new("test");
1002        let mut harness = ComponentTestHarness::new(comp);
1003
1004        let result = harness.request(
1005            EventCategory::Echo,
1006            "echo",
1007            serde_json::json!({"msg": "hello"}),
1008        );
1009        assert!(result.is_ok());
1010        assert_eq!(
1011            result.expect("echo request should succeed"),
1012            serde_json::json!({"msg": "hello"})
1013        );
1014
1015        assert_eq!(harness.request_log().len(), 1);
1016        assert_eq!(harness.request_log()[0].operation, "echo");
1017    }
1018
1019    #[test]
1020    fn harness_request_error() {
1021        let comp = TestComponent::new("test");
1022        let mut harness = ComponentTestHarness::new(comp);
1023
1024        let result = harness.request(EventCategory::Echo, "unknown", Value::Null);
1025        assert!(result.is_err());
1026
1027        assert_eq!(harness.request_log().len(), 1);
1028        assert!(matches!(
1029            harness.request_log()[0].result,
1030            RequestResult::Err(_)
1031        ));
1032    }
1033
1034    #[test]
1035    fn harness_veto() {
1036        let comp = TestComponent::new("test");
1037        let mut harness = ComponentTestHarness::new(comp);
1038
1039        assert_eq!(harness.status(), Status::Idle);
1040
1041        let response = harness.veto();
1042        assert_eq!(response, SignalResponse::Abort);
1043        assert_eq!(harness.status(), Status::Aborted);
1044
1045        assert_eq!(harness.signal_log().len(), 1);
1046        assert!(harness.signal_log()[0].kind.contains("Veto"));
1047    }
1048
1049    #[test]
1050    fn harness_cancel() {
1051        let comp = TestComponent::new("test");
1052        let mut harness = ComponentTestHarness::new(comp);
1053
1054        let response = harness.cancel();
1055        assert_eq!(response, SignalResponse::Handled);
1056        assert_eq!(harness.status(), Status::Idle);
1057    }
1058
1059    #[test]
1060    fn harness_component_access() {
1061        let comp = TestComponent::new("test");
1062        let mut harness = ComponentTestHarness::new(comp);
1063
1064        assert_eq!(harness.component().request_count, 0);
1065
1066        harness
1067            .request(EventCategory::Echo, "count", Value::Null)
1068            .expect("count request should succeed");
1069        assert_eq!(harness.component().request_count, 1);
1070
1071        harness.component_mut().request_count = 100;
1072        assert_eq!(harness.component().request_count, 100);
1073    }
1074
1075    #[test]
1076    fn harness_clear_logs() {
1077        let comp = TestComponent::new("test");
1078        let mut harness = ComponentTestHarness::new(comp);
1079
1080        harness
1081            .request(EventCategory::Echo, "echo", Value::Null)
1082            .expect("echo request should succeed for log clearing test");
1083        harness.cancel();
1084
1085        assert_eq!(harness.request_log().len(), 1);
1086        assert_eq!(harness.signal_log().len(), 1);
1087
1088        harness.clear_logs();
1089
1090        assert_eq!(harness.request_log().len(), 0);
1091        assert_eq!(harness.signal_log().len(), 0);
1092    }
1093
1094    #[test]
1095    fn harness_init_shutdown() {
1096        let comp = TestComponent::new("test");
1097        let mut harness = ComponentTestHarness::new(comp);
1098
1099        assert!(harness.init().is_ok());
1100        harness.shutdown();
1101        // Component should still be accessible after shutdown
1102        assert_eq!(harness.id().name, "test");
1103    }
1104
1105    // =========================================================================
1106    // Child Test Harness Tests
1107    // =========================================================================
1108
1109    use crate::{Child, ChildError, RunnableChild};
1110
1111    struct TestChild {
1112        id: String,
1113        status: Status,
1114    }
1115
1116    impl TestChild {
1117        fn new(id: &str) -> Self {
1118            Self {
1119                id: id.into(),
1120                status: Status::Idle,
1121            }
1122        }
1123    }
1124
1125    impl Identifiable for TestChild {
1126        fn id(&self) -> &str {
1127            &self.id
1128        }
1129    }
1130
1131    impl SignalReceiver for TestChild {
1132        fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
1133            if signal.is_veto() {
1134                self.abort();
1135                SignalResponse::Abort
1136            } else {
1137                SignalResponse::Handled
1138            }
1139        }
1140
1141        fn abort(&mut self) {
1142            self.status = Status::Aborted;
1143        }
1144    }
1145
1146    impl Statusable for TestChild {
1147        fn status(&self) -> Status {
1148            self.status
1149        }
1150    }
1151
1152    impl Child for TestChild {}
1153
1154    // --- ChildTestHarness (base) tests ---
1155
1156    #[test]
1157    fn child_harness_new() {
1158        let child = TestChild::new("test-child");
1159        let harness = ChildTestHarness::new(child);
1160
1161        assert_eq!(harness.id(), "test-child");
1162        assert_eq!(harness.status(), Status::Idle);
1163        assert!(harness.signal_log().is_empty());
1164    }
1165
1166    #[test]
1167    fn child_harness_veto() {
1168        let child = TestChild::new("test");
1169        let mut harness = ChildTestHarness::new(child);
1170
1171        let response = harness.veto();
1172        assert_eq!(response, SignalResponse::Abort);
1173        assert_eq!(harness.status(), Status::Aborted);
1174
1175        assert_eq!(harness.signal_log().len(), 1);
1176        assert!(harness.signal_log()[0].kind.contains("Veto"));
1177    }
1178
1179    #[test]
1180    fn child_harness_cancel() {
1181        let child = TestChild::new("test");
1182        let mut harness = ChildTestHarness::new(child);
1183
1184        let response = harness.cancel();
1185        assert_eq!(response, SignalResponse::Handled);
1186        assert_eq!(harness.status(), Status::Idle);
1187    }
1188
1189    #[test]
1190    fn child_harness_clear_logs() {
1191        let child = TestChild::new("test");
1192        let mut harness = ChildTestHarness::new(child);
1193
1194        harness.veto();
1195        harness.cancel();
1196        assert_eq!(harness.signal_log().len(), 2);
1197
1198        harness.clear_logs();
1199        assert!(harness.signal_log().is_empty());
1200    }
1201
1202    #[test]
1203    fn child_harness_access() {
1204        let child = TestChild::new("test");
1205        let mut harness = ChildTestHarness::new(child);
1206
1207        assert_eq!(harness.child().id, "test");
1208        harness.child_mut().status = Status::Running;
1209        assert_eq!(harness.status(), Status::Running);
1210    }
1211
1212    // --- SyncChildTestHarness tests ---
1213
1214    struct TestRunnableChild {
1215        id: String,
1216        status: Status,
1217        run_count: usize,
1218    }
1219
1220    impl TestRunnableChild {
1221        fn new(id: &str) -> Self {
1222            Self {
1223                id: id.into(),
1224                status: Status::Idle,
1225                run_count: 0,
1226            }
1227        }
1228    }
1229
1230    impl Identifiable for TestRunnableChild {
1231        fn id(&self) -> &str {
1232            &self.id
1233        }
1234    }
1235
1236    impl SignalReceiver for TestRunnableChild {
1237        fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
1238            if signal.is_veto() {
1239                self.abort();
1240                SignalResponse::Abort
1241            } else {
1242                SignalResponse::Handled
1243            }
1244        }
1245
1246        fn abort(&mut self) {
1247            self.status = Status::Aborted;
1248        }
1249    }
1250
1251    impl Statusable for TestRunnableChild {
1252        fn status(&self) -> Status {
1253            self.status
1254        }
1255    }
1256
1257    impl Child for TestRunnableChild {}
1258
1259    impl RunnableChild for TestRunnableChild {
1260        fn run(&mut self, input: Value) -> ChildResult {
1261            self.status = Status::Running;
1262            self.run_count += 1;
1263
1264            if input.get("fail").is_some() {
1265                self.status = Status::Idle;
1266                return ChildResult::Err(ChildError::ExecutionFailed {
1267                    reason: "requested failure".into(),
1268                });
1269            }
1270
1271            self.status = Status::Idle;
1272            ChildResult::Ok(serde_json::json!({
1273                "echo": input,
1274                "count": self.run_count
1275            }))
1276        }
1277    }
1278
1279    #[test]
1280    fn sync_child_harness_run() {
1281        let child = TestRunnableChild::new("worker");
1282        let mut harness = SyncChildTestHarness::new(child);
1283
1284        let result = harness.run(serde_json::json!({"task": "test"}));
1285        assert!(result.is_ok());
1286
1287        assert_eq!(harness.run_log().len(), 1);
1288        assert!(harness.run_log()[0].elapsed_ms.is_some());
1289
1290        if let RunResult::Ok { value } = &harness.run_log()[0].result {
1291            assert_eq!(value["count"], 1);
1292        } else {
1293            panic!("expected Ok result");
1294        }
1295    }
1296
1297    #[test]
1298    fn sync_child_harness_run_error() {
1299        let child = TestRunnableChild::new("worker");
1300        let mut harness = SyncChildTestHarness::new(child);
1301
1302        let result = harness.run(serde_json::json!({"fail": true}));
1303        assert!(result.is_err());
1304
1305        assert_eq!(harness.run_log().len(), 1);
1306        assert!(matches!(harness.run_log()[0].result, RunResult::Err { .. }));
1307    }
1308
1309    #[test]
1310    fn sync_child_harness_run_json() {
1311        #[derive(Serialize)]
1312        struct Input {
1313            task: String,
1314        }
1315
1316        let child = TestRunnableChild::new("worker");
1317        let mut harness = SyncChildTestHarness::new(child);
1318
1319        let result = harness.run_json(Input {
1320            task: "test".into(),
1321        });
1322        assert!(result.is_ok());
1323
1324        assert_eq!(harness.run_log()[0].input["task"], "test");
1325    }
1326
1327    #[test]
1328    fn sync_child_harness_without_time_measurement() {
1329        let child = TestRunnableChild::new("worker");
1330        let mut harness = SyncChildTestHarness::new(child).without_time_measurement();
1331
1332        harness.run(Value::Null);
1333
1334        assert!(harness.run_log()[0].elapsed_ms.is_none());
1335    }
1336
1337    #[test]
1338    fn sync_child_harness_veto() {
1339        let child = TestRunnableChild::new("worker");
1340        let mut harness = SyncChildTestHarness::new(child);
1341
1342        let response = harness.veto();
1343        assert_eq!(response, SignalResponse::Abort);
1344        assert_eq!(harness.status(), Status::Aborted);
1345    }
1346
1347    #[test]
1348    fn sync_child_harness_clear_all_logs() {
1349        let child = TestRunnableChild::new("worker");
1350        let mut harness = SyncChildTestHarness::new(child);
1351
1352        harness.run(Value::Null);
1353        harness.veto();
1354
1355        assert_eq!(harness.run_log().len(), 1);
1356        assert_eq!(harness.signal_log().len(), 1);
1357
1358        harness.clear_all_logs();
1359
1360        assert!(harness.run_log().is_empty());
1361        assert!(harness.signal_log().is_empty());
1362    }
1363
1364    #[test]
1365    fn sync_child_harness_multiple_runs() {
1366        let child = TestRunnableChild::new("worker");
1367        let mut harness = SyncChildTestHarness::new(child);
1368
1369        for i in 0..5 {
1370            harness.run(serde_json::json!({"iteration": i}));
1371        }
1372
1373        assert_eq!(harness.run_log().len(), 5);
1374        assert_eq!(harness.child().run_count, 5);
1375    }
1376
1377    // --- AsyncChildTestHarness tests ---
1378
1379    use crate::AsyncRunnableChild;
1380    use async_trait::async_trait;
1381
1382    struct TestAsyncChild {
1383        id: String,
1384        status: Status,
1385        delay_ms: u64,
1386    }
1387
1388    impl TestAsyncChild {
1389        fn new(id: &str) -> Self {
1390            Self {
1391                id: id.into(),
1392                status: Status::Idle,
1393                delay_ms: 0,
1394            }
1395        }
1396
1397        fn with_delay(mut self, delay_ms: u64) -> Self {
1398            self.delay_ms = delay_ms;
1399            self
1400        }
1401    }
1402
1403    impl Identifiable for TestAsyncChild {
1404        fn id(&self) -> &str {
1405            &self.id
1406        }
1407    }
1408
1409    impl SignalReceiver for TestAsyncChild {
1410        fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
1411            if signal.is_veto() {
1412                self.abort();
1413                SignalResponse::Abort
1414            } else {
1415                SignalResponse::Handled
1416            }
1417        }
1418
1419        fn abort(&mut self) {
1420            self.status = Status::Aborted;
1421        }
1422    }
1423
1424    impl Statusable for TestAsyncChild {
1425        fn status(&self) -> Status {
1426            self.status
1427        }
1428    }
1429
1430    impl Child for TestAsyncChild {}
1431
1432    #[async_trait]
1433    impl AsyncRunnableChild for TestAsyncChild {
1434        async fn run(&mut self, input: Value) -> ChildResult {
1435            self.status = Status::Running;
1436
1437            if self.delay_ms > 0 {
1438                tokio::time::sleep(Duration::from_millis(self.delay_ms)).await;
1439            }
1440
1441            self.status = Status::Idle;
1442            ChildResult::Ok(serde_json::json!({
1443                "async": true,
1444                "input": input
1445            }))
1446        }
1447    }
1448
1449    #[tokio::test]
1450    async fn async_child_harness_run() {
1451        let child = TestAsyncChild::new("async-worker");
1452        let mut harness = AsyncChildTestHarness::new(child);
1453
1454        let result = harness.run(serde_json::json!({"task": "async_test"})).await;
1455        assert!(result.is_ok());
1456
1457        assert_eq!(harness.run_log().len(), 1);
1458        assert!(harness.run_log()[0].elapsed_ms.is_some());
1459
1460        if let RunResult::Ok { value } = &harness.run_log()[0].result {
1461            assert_eq!(value["async"], true);
1462        }
1463    }
1464
1465    #[tokio::test]
1466    async fn async_child_harness_with_timeout_success() {
1467        let child = TestAsyncChild::new("async-worker").with_delay(10);
1468        let mut harness = AsyncChildTestHarness::new(child);
1469
1470        let result = harness
1471            .run_with_timeout(Value::Null, Duration::from_millis(100))
1472            .await;
1473        assert!(result.is_ok());
1474    }
1475
1476    #[tokio::test]
1477    async fn async_child_harness_with_timeout_failure() {
1478        let child = TestAsyncChild::new("async-worker").with_delay(100);
1479        let mut harness = AsyncChildTestHarness::new(child);
1480
1481        let result = harness
1482            .run_with_timeout(Value::Null, Duration::from_millis(10))
1483            .await;
1484        assert!(result.is_err());
1485
1486        let err = result.expect_err("async child with long delay should timeout");
1487        assert!(err.timeout_ms == 10);
1488    }
1489
1490    #[tokio::test]
1491    async fn async_child_harness_with_default_timeout() {
1492        let child = TestAsyncChild::new("async-worker").with_delay(100);
1493        let mut harness =
1494            AsyncChildTestHarness::new(child).with_default_timeout(Duration::from_millis(10));
1495
1496        // Should timeout and return Aborted
1497        let result = harness.run(Value::Null).await;
1498        assert!(result.is_aborted());
1499    }
1500
1501    #[tokio::test]
1502    async fn async_child_harness_veto() {
1503        let child = TestAsyncChild::new("async-worker");
1504        let mut harness = AsyncChildTestHarness::new(child);
1505
1506        let response = harness.veto();
1507        assert_eq!(response, SignalResponse::Abort);
1508        assert_eq!(harness.status(), Status::Aborted);
1509    }
1510
1511    #[tokio::test]
1512    async fn async_child_harness_clear_all_logs() {
1513        let child = TestAsyncChild::new("async-worker");
1514        let mut harness = AsyncChildTestHarness::new(child);
1515
1516        harness.run(Value::Null).await;
1517        harness.veto();
1518
1519        assert_eq!(harness.run_log().len(), 1);
1520        assert_eq!(harness.signal_log().len(), 1);
1521
1522        harness.clear_all_logs();
1523
1524        assert!(harness.run_log().is_empty());
1525        assert!(harness.signal_log().is_empty());
1526    }
1527
1528    // --- RunResult serialization tests ---
1529
1530    #[test]
1531    fn run_result_serialization() {
1532        let ok_result = RunResult::Ok {
1533            value: serde_json::json!({"key": "value"}),
1534        };
1535        let json =
1536            serde_json::to_string(&ok_result).expect("RunResult::Ok should serialize to JSON");
1537        assert!(json.contains("\"type\":\"Ok\""));
1538
1539        let err_result = RunResult::Err {
1540            kind: "timeout".into(),
1541            message: "timed out".into(),
1542        };
1543        let json =
1544            serde_json::to_string(&err_result).expect("RunResult::Err should serialize to JSON");
1545        assert!(json.contains("\"type\":\"Err\""));
1546
1547        let aborted_result = RunResult::Aborted;
1548        let json = serde_json::to_string(&aborted_result)
1549            .expect("RunResult::Aborted should serialize to JSON");
1550        assert!(json.contains("\"type\":\"Aborted\""));
1551    }
1552
1553    #[test]
1554    fn run_record_serialization() {
1555        let record = RunRecord {
1556            input: serde_json::json!({"task": "test"}),
1557            result: RunResult::Ok {
1558                value: serde_json::json!({"done": true}),
1559            },
1560            elapsed_ms: Some(42),
1561        };
1562
1563        let json = serde_json::to_string(&record).expect("RunRecord should serialize to JSON");
1564        assert!(json.contains("\"elapsed_ms\":42"));
1565
1566        let restored: RunRecord =
1567            serde_json::from_str(&json).expect("RunRecord should deserialize from JSON");
1568        assert_eq!(restored.elapsed_ms, Some(42));
1569    }
1570}