1use serde::{de::DeserializeOwned, Deserialize, Serialize};
7
8use crate::checkpoint_server::NodeJsHistoryEvent;
9use crate::error::TestError;
10use crate::types::{ExecutionStatus, Invocation, TestResultError};
11use durable_execution_sdk::{Operation, OperationStatus};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct TestResult<T> {
43 status: ExecutionStatus,
45 result: Option<T>,
47 error: Option<TestResultError>,
49 operations: Vec<Operation>,
51 invocations: Vec<Invocation>,
53 history_events: Vec<HistoryEvent>,
55 nodejs_history_events: Vec<NodeJsHistoryEvent>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct HistoryEvent {
67 pub event_type: String,
69 pub timestamp: Option<i64>,
71 pub operation_id: Option<String>,
73 pub data: Option<String>,
75}
76
77impl HistoryEvent {
78 pub fn new(event_type: impl Into<String>) -> Self {
80 Self {
81 event_type: event_type.into(),
82 timestamp: None,
83 operation_id: None,
84 data: None,
85 }
86 }
87
88 pub fn with_timestamp(mut self, timestamp: i64) -> Self {
90 self.timestamp = Some(timestamp);
91 self
92 }
93
94 pub fn with_operation_id(mut self, operation_id: impl Into<String>) -> Self {
96 self.operation_id = Some(operation_id.into());
97 self
98 }
99
100 pub fn with_data(mut self, data: impl Into<String>) -> Self {
102 self.data = Some(data.into());
103 self
104 }
105}
106
107impl<T> TestResult<T> {
108 pub fn success(result: T, operations: Vec<Operation>) -> Self {
110 Self {
111 status: ExecutionStatus::Succeeded,
112 result: Some(result),
113 error: None,
114 operations,
115 invocations: Vec::new(),
116 history_events: Vec::new(),
117 nodejs_history_events: Vec::new(),
118 }
119 }
120
121 pub fn failure(error: TestResultError, operations: Vec<Operation>) -> Self {
123 Self {
124 status: ExecutionStatus::Failed,
125 result: None,
126 error: Some(error),
127 operations,
128 invocations: Vec::new(),
129 history_events: Vec::new(),
130 nodejs_history_events: Vec::new(),
131 }
132 }
133
134 pub fn with_status(status: ExecutionStatus, operations: Vec<Operation>) -> Self {
136 Self {
137 status,
138 result: None,
139 error: None,
140 operations,
141 invocations: Vec::new(),
142 history_events: Vec::new(),
143 nodejs_history_events: Vec::new(),
144 }
145 }
146
147 pub fn set_result(&mut self, result: T) {
149 self.result = Some(result);
150 }
151
152 pub fn set_error(&mut self, error: TestResultError) {
154 self.error = Some(error);
155 }
156
157 pub fn set_invocations(&mut self, invocations: Vec<Invocation>) {
159 self.invocations = invocations;
160 }
161
162 pub fn add_invocation(&mut self, invocation: Invocation) {
164 self.invocations.push(invocation);
165 }
166
167 pub fn set_history_events(&mut self, events: Vec<HistoryEvent>) {
169 self.history_events = events;
170 }
171
172 pub fn add_history_event(&mut self, event: HistoryEvent) {
174 self.history_events.push(event);
175 }
176
177 pub fn set_nodejs_history_events(&mut self, events: Vec<NodeJsHistoryEvent>) {
182 self.nodejs_history_events = events;
183 }
184
185 pub fn add_nodejs_history_event(&mut self, event: NodeJsHistoryEvent) {
187 self.nodejs_history_events.push(event);
188 }
189
190 pub fn get_status(&self) -> ExecutionStatus {
196 self.status
197 }
198
199 pub fn get_result(&self) -> Result<&T, TestError> {
206 match self.status {
207 ExecutionStatus::Succeeded => self.result.as_ref().ok_or_else(|| {
208 TestError::result_not_available("Execution succeeded but result is not set")
209 }),
210 ExecutionStatus::Failed => Err(TestError::result_not_available(
211 "Cannot get result from failed execution",
212 )),
213 ExecutionStatus::Running => Err(TestError::result_not_available(
214 "Execution is still running",
215 )),
216 ExecutionStatus::Cancelled => {
217 Err(TestError::result_not_available("Execution was cancelled"))
218 }
219 ExecutionStatus::TimedOut => {
220 Err(TestError::result_not_available("Execution timed out"))
221 }
222 }
223 }
224
225 pub fn get_error(&self) -> Result<&TestResultError, &str> {
232 match self.status {
233 ExecutionStatus::Failed | ExecutionStatus::Cancelled | ExecutionStatus::TimedOut => {
234 self.error
235 .as_ref()
236 .ok_or("Execution failed but error details are not available")
237 }
238 ExecutionStatus::Succeeded => Err("Cannot get error from successful execution"),
239 ExecutionStatus::Running => Err("Execution is still running"),
240 }
241 }
242
243 pub fn get_operations(&self) -> &[Operation] {
249 &self.operations
250 }
251
252 pub fn get_operations_by_status(&self, status: OperationStatus) -> Vec<&Operation> {
262 self.operations
263 .iter()
264 .filter(|op| op.status == status)
265 .collect()
266 }
267
268 pub fn get_invocations(&self) -> &[Invocation] {
274 &self.invocations
275 }
276
277 pub fn get_history_events(&self) -> &[HistoryEvent] {
283 &self.history_events
284 }
285
286 pub fn get_nodejs_history_events(&self) -> &[NodeJsHistoryEvent] {
296 &self.nodejs_history_events
297 }
298
299 pub fn is_success(&self) -> bool {
301 self.status.is_success()
302 }
303
304 pub fn is_failure(&self) -> bool {
306 self.status.is_failure()
307 }
308
309 pub fn is_running(&self) -> bool {
311 matches!(self.status, ExecutionStatus::Running)
312 }
313
314 pub fn operation_count(&self) -> usize {
316 self.operations.len()
317 }
318
319 pub fn invocation_count(&self) -> usize {
321 self.invocations.len()
322 }
323}
324
325#[derive(Debug, Clone)]
329pub struct PrintConfig {
330 pub show_id: bool,
332 pub show_parent_id: bool,
334 pub show_name: bool,
336 pub show_type: bool,
338 pub show_status: bool,
340 pub show_start_time: bool,
342 pub show_end_time: bool,
344 pub show_duration: bool,
346 pub show_result: bool,
348}
349
350impl Default for PrintConfig {
351 fn default() -> Self {
352 Self {
353 show_id: false,
354 show_parent_id: false,
355 show_name: true,
356 show_type: true,
357 show_status: true,
358 show_start_time: true,
359 show_end_time: true,
360 show_duration: true,
361 show_result: false,
362 }
363 }
364}
365
366impl PrintConfig {
367 pub fn all() -> Self {
369 Self {
370 show_id: true,
371 show_parent_id: true,
372 show_name: true,
373 show_type: true,
374 show_status: true,
375 show_start_time: true,
376 show_end_time: true,
377 show_duration: true,
378 show_result: true,
379 }
380 }
381
382 pub fn minimal() -> Self {
384 Self {
385 show_id: false,
386 show_parent_id: false,
387 show_name: true,
388 show_type: true,
389 show_status: true,
390 show_start_time: false,
391 show_end_time: false,
392 show_duration: false,
393 show_result: false,
394 }
395 }
396
397 pub fn with_id(mut self, show: bool) -> Self {
399 self.show_id = show;
400 self
401 }
402
403 pub fn with_parent_id(mut self, show: bool) -> Self {
405 self.show_parent_id = show;
406 self
407 }
408
409 pub fn with_name(mut self, show: bool) -> Self {
411 self.show_name = show;
412 self
413 }
414
415 pub fn with_type(mut self, show: bool) -> Self {
417 self.show_type = show;
418 self
419 }
420
421 pub fn with_status(mut self, show: bool) -> Self {
423 self.show_status = show;
424 self
425 }
426
427 pub fn with_start_time(mut self, show: bool) -> Self {
429 self.show_start_time = show;
430 self
431 }
432
433 pub fn with_end_time(mut self, show: bool) -> Self {
435 self.show_end_time = show;
436 self
437 }
438
439 pub fn with_duration(mut self, show: bool) -> Self {
441 self.show_duration = show;
442 self
443 }
444
445 pub fn with_result(mut self, show: bool) -> Self {
447 self.show_result = show;
448 self
449 }
450}
451
452impl<T> TestResult<T> {
453 pub fn print(&self) {
457 self.print_with_config(PrintConfig::default());
458 }
459
460 pub fn print_with_config(&self, config: PrintConfig) {
466 let mut headers: Vec<&str> = Vec::new();
468 if config.show_id {
469 headers.push("ID");
470 }
471 if config.show_parent_id {
472 headers.push("Parent ID");
473 }
474 if config.show_name {
475 headers.push("Name");
476 }
477 if config.show_type {
478 headers.push("Type");
479 }
480 if config.show_status {
481 headers.push("Status");
482 }
483 if config.show_start_time {
484 headers.push("Start Time");
485 }
486 if config.show_end_time {
487 headers.push("End Time");
488 }
489 if config.show_duration {
490 headers.push("Duration");
491 }
492 if config.show_result {
493 headers.push("Result/Error");
494 }
495
496 let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
498
499 let rows: Vec<Vec<String>> = self
501 .operations
502 .iter()
503 .map(|op| {
504 let mut row: Vec<String> = Vec::new();
505 let mut col_idx = 0;
506
507 if config.show_id {
508 let val = op.operation_id.clone();
509 widths[col_idx] = widths[col_idx].max(val.len());
510 row.push(val);
511 col_idx += 1;
512 }
513 if config.show_parent_id {
514 let val = op.parent_id.clone().unwrap_or_else(|| "-".to_string());
515 widths[col_idx] = widths[col_idx].max(val.len());
516 row.push(val);
517 col_idx += 1;
518 }
519 if config.show_name {
520 let val = op.name.clone().unwrap_or_else(|| "-".to_string());
521 widths[col_idx] = widths[col_idx].max(val.len());
522 row.push(val);
523 col_idx += 1;
524 }
525 if config.show_type {
526 let val = format!("{}", op.operation_type);
527 widths[col_idx] = widths[col_idx].max(val.len());
528 row.push(val);
529 col_idx += 1;
530 }
531 if config.show_status {
532 let val = format!("{}", op.status);
533 widths[col_idx] = widths[col_idx].max(val.len());
534 row.push(val);
535 col_idx += 1;
536 }
537 if config.show_start_time {
538 let val = op
539 .start_timestamp
540 .map(format_timestamp)
541 .unwrap_or_else(|| "-".to_string());
542 widths[col_idx] = widths[col_idx].max(val.len());
543 row.push(val);
544 col_idx += 1;
545 }
546 if config.show_end_time {
547 let val = op
548 .end_timestamp
549 .map(format_timestamp)
550 .unwrap_or_else(|| "-".to_string());
551 widths[col_idx] = widths[col_idx].max(val.len());
552 row.push(val);
553 col_idx += 1;
554 }
555 if config.show_duration {
556 let val = match (op.start_timestamp, op.end_timestamp) {
557 (Some(start), Some(end)) => format_duration(end - start),
558 _ => "-".to_string(),
559 };
560 widths[col_idx] = widths[col_idx].max(val.len());
561 row.push(val);
562 col_idx += 1;
563 }
564 if config.show_result {
565 let val = if let Some(ref err) = op.error {
566 format!("Error: {}", err.error_message)
567 } else if let Some(result) = op.get_result() {
568 truncate_string(result, 50)
569 } else {
570 "-".to_string()
571 };
572 widths[col_idx] = widths[col_idx].max(val.len().min(50));
573 row.push(val);
574 }
575
576 row
577 })
578 .collect();
579
580 println!("\n=== Execution Result ===");
582 println!("Status: {}", self.status);
583 if let Some(ref err) = self.error {
584 println!("Error: {}", err);
585 }
586 println!("Operations: {}", self.operations.len());
587 println!("Invocations: {}", self.invocations.len());
588 println!();
589
590 print_row(
592 &headers.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
593 &widths,
594 );
595 print_separator(&widths);
596
597 for row in rows {
599 print_row(&row, &widths);
600 }
601
602 println!();
603 }
604}
605
606fn format_timestamp(millis: i64) -> String {
608 use chrono::{TimeZone, Utc};
609 match Utc.timestamp_millis_opt(millis) {
610 chrono::LocalResult::Single(dt) => dt.format("%Y-%m-%d %H:%M:%S%.3f").to_string(),
611 _ => format!("{}ms", millis),
612 }
613}
614
615fn format_duration(millis: i64) -> String {
617 if millis < 1000 {
618 format!("{}ms", millis)
619 } else if millis < 60_000 {
620 format!("{:.2}s", millis as f64 / 1000.0)
621 } else if millis < 3_600_000 {
622 let mins = millis / 60_000;
623 let secs = (millis % 60_000) / 1000;
624 format!("{}m {}s", mins, secs)
625 } else {
626 let hours = millis / 3_600_000;
627 let mins = (millis % 3_600_000) / 60_000;
628 format!("{}h {}m", hours, mins)
629 }
630}
631
632fn truncate_string(s: &str, max_len: usize) -> String {
634 if s.len() <= max_len {
635 s.to_string()
636 } else {
637 format!("{}...", &s[..max_len - 3])
638 }
639}
640
641fn print_row(row: &[String], widths: &[usize]) {
643 let formatted: Vec<String> = row
644 .iter()
645 .zip(widths.iter())
646 .map(|(val, width)| format!("{:<width$}", val, width = width))
647 .collect();
648 println!("| {} |", formatted.join(" | "));
649}
650
651fn print_separator(widths: &[usize]) {
653 let separators: Vec<String> = widths.iter().map(|w| "-".repeat(*w)).collect();
654 println!("+-{}-+", separators.join("-+-"));
655}
656
657impl<T: DeserializeOwned> TestResult<T> {
659 pub fn deserialize_result_from_json(json: &str) -> Result<T, TestError> {
664 serde_json::from_str(json).map_err(TestError::from)
665 }
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671 use durable_execution_sdk::{Operation, OperationStatus, OperationType};
672
673 fn create_test_operation(
674 name: &str,
675 op_type: OperationType,
676 status: OperationStatus,
677 ) -> Operation {
678 let mut op = Operation::new(format!("{}-001", name), op_type);
679 op.name = Some(name.to_string());
680 op.status = status;
681 op
682 }
683
684 #[test]
685 fn test_success_result() {
686 let ops = vec![create_test_operation(
687 "step1",
688 OperationType::Step,
689 OperationStatus::Succeeded,
690 )];
691 let result: TestResult<String> = TestResult::success("hello".to_string(), ops);
692
693 assert_eq!(result.get_status(), ExecutionStatus::Succeeded);
694 assert!(result.is_success());
695 assert!(!result.is_failure());
696 assert_eq!(result.get_result().unwrap(), "hello");
697 assert!(result.get_error().is_err());
698 }
699
700 #[test]
701 fn test_failure_result() {
702 let ops = vec![create_test_operation(
703 "step1",
704 OperationType::Step,
705 OperationStatus::Failed,
706 )];
707 let error = TestResultError::new("TestError", "Something went wrong");
708 let result: TestResult<String> = TestResult::failure(error, ops);
709
710 assert_eq!(result.get_status(), ExecutionStatus::Failed);
711 assert!(!result.is_success());
712 assert!(result.is_failure());
713 assert!(result.get_result().is_err());
714 assert!(result.get_error().is_ok());
715 assert_eq!(
716 result.get_error().unwrap().error_message,
717 Some("Something went wrong".to_string())
718 );
719 }
720
721 #[test]
722 fn test_get_operations() {
723 let ops = vec![
724 create_test_operation("step1", OperationType::Step, OperationStatus::Succeeded),
725 create_test_operation("wait1", OperationType::Wait, OperationStatus::Succeeded),
726 create_test_operation("step2", OperationType::Step, OperationStatus::Failed),
727 ];
728 let result: TestResult<String> = TestResult::success("done".to_string(), ops);
729
730 assert_eq!(result.get_operations().len(), 3);
731 assert_eq!(result.operation_count(), 3);
732 }
733
734 #[test]
735 fn test_get_operations_by_status() {
736 let ops = vec![
737 create_test_operation("step1", OperationType::Step, OperationStatus::Succeeded),
738 create_test_operation("wait1", OperationType::Wait, OperationStatus::Succeeded),
739 create_test_operation("step2", OperationType::Step, OperationStatus::Failed),
740 create_test_operation("step3", OperationType::Step, OperationStatus::Started),
741 ];
742 let result: TestResult<String> = TestResult::success("done".to_string(), ops);
743
744 let succeeded = result.get_operations_by_status(OperationStatus::Succeeded);
745 assert_eq!(succeeded.len(), 2);
746
747 let failed = result.get_operations_by_status(OperationStatus::Failed);
748 assert_eq!(failed.len(), 1);
749
750 let started = result.get_operations_by_status(OperationStatus::Started);
751 assert_eq!(started.len(), 1);
752
753 let pending = result.get_operations_by_status(OperationStatus::Pending);
754 assert_eq!(pending.len(), 0);
755 }
756
757 #[test]
758 fn test_invocations() {
759 let mut result: TestResult<String> = TestResult::success("done".to_string(), vec![]);
760
761 assert_eq!(result.get_invocations().len(), 0);
762 assert_eq!(result.invocation_count(), 0);
763
764 result.add_invocation(Invocation::new());
765 result.add_invocation(Invocation::new());
766
767 assert_eq!(result.get_invocations().len(), 2);
768 assert_eq!(result.invocation_count(), 2);
769 }
770
771 #[test]
772 fn test_history_events() {
773 let mut result: TestResult<String> = TestResult::success("done".to_string(), vec![]);
774
775 assert_eq!(result.get_history_events().len(), 0);
776
777 result.add_history_event(HistoryEvent::new("ExecutionStarted"));
778 result.add_history_event(HistoryEvent::new("StepCompleted").with_operation_id("step-001"));
779
780 assert_eq!(result.get_history_events().len(), 2);
781 assert_eq!(
782 result.get_history_events()[0].event_type,
783 "ExecutionStarted"
784 );
785 assert_eq!(
786 result.get_history_events()[1].operation_id,
787 Some("step-001".to_string())
788 );
789 }
790
791 #[test]
792 fn test_nodejs_history_events() {
793 use crate::checkpoint_server::{
794 ExecutionStartedDetails, ExecutionStartedDetailsWrapper, NodeJsEventDetails,
795 NodeJsEventType, NodeJsHistoryEvent, PayloadWrapper,
796 };
797
798 let mut result: TestResult<String> = TestResult::success("done".to_string(), vec![]);
799
800 assert_eq!(result.get_nodejs_history_events().len(), 0);
802
803 let event1 = NodeJsHistoryEvent {
805 event_type: NodeJsEventType::ExecutionStarted,
806 event_id: 1,
807 id: Some("exec-123".to_string()),
808 event_timestamp: "2025-12-03T22:58:35.094Z".to_string(),
809 sub_type: None,
810 name: None,
811 parent_id: None,
812 details: NodeJsEventDetails::ExecutionStarted(ExecutionStartedDetailsWrapper {
813 execution_started_details: ExecutionStartedDetails {
814 input: PayloadWrapper::new("{}"),
815 execution_timeout: None,
816 },
817 }),
818 };
819
820 let event2 = NodeJsHistoryEvent {
821 event_type: NodeJsEventType::StepStarted,
822 event_id: 2,
823 id: Some("step-456".to_string()),
824 event_timestamp: "2025-12-03T22:58:35.096Z".to_string(),
825 sub_type: Some("Step".to_string()),
826 name: Some("my-step".to_string()),
827 parent_id: Some("exec-123".to_string()),
828 details: NodeJsEventDetails::default(),
829 };
830
831 result.add_nodejs_history_event(event1.clone());
833 result.add_nodejs_history_event(event2.clone());
834
835 assert_eq!(result.get_nodejs_history_events().len(), 2);
836 assert_eq!(
837 result.get_nodejs_history_events()[0].event_type,
838 NodeJsEventType::ExecutionStarted
839 );
840 assert_eq!(result.get_nodejs_history_events()[0].event_id, 1);
841 assert_eq!(
842 result.get_nodejs_history_events()[1].event_type,
843 NodeJsEventType::StepStarted
844 );
845 assert_eq!(result.get_nodejs_history_events()[1].event_id, 2);
846 assert_eq!(
847 result.get_nodejs_history_events()[1].name,
848 Some("my-step".to_string())
849 );
850
851 let mut result2: TestResult<String> = TestResult::success("done".to_string(), vec![]);
853 result2.set_nodejs_history_events(vec![event1, event2]);
854 assert_eq!(result2.get_nodejs_history_events().len(), 2);
855 }
856
857 #[test]
858 fn test_print_config_default() {
859 let config = PrintConfig::default();
860 assert!(!config.show_id);
861 assert!(!config.show_parent_id);
862 assert!(config.show_name);
863 assert!(config.show_type);
864 assert!(config.show_status);
865 assert!(config.show_start_time);
866 assert!(config.show_end_time);
867 assert!(config.show_duration);
868 assert!(!config.show_result);
869 }
870
871 #[test]
872 fn test_print_config_all() {
873 let config = PrintConfig::all();
874 assert!(config.show_id);
875 assert!(config.show_parent_id);
876 assert!(config.show_name);
877 assert!(config.show_type);
878 assert!(config.show_status);
879 assert!(config.show_start_time);
880 assert!(config.show_end_time);
881 assert!(config.show_duration);
882 assert!(config.show_result);
883 }
884
885 #[test]
886 fn test_print_config_minimal() {
887 let config = PrintConfig::minimal();
888 assert!(!config.show_id);
889 assert!(!config.show_parent_id);
890 assert!(config.show_name);
891 assert!(config.show_type);
892 assert!(config.show_status);
893 assert!(!config.show_start_time);
894 assert!(!config.show_end_time);
895 assert!(!config.show_duration);
896 assert!(!config.show_result);
897 }
898
899 #[test]
900 fn test_format_duration() {
901 assert_eq!(format_duration(500), "500ms");
902 assert_eq!(format_duration(1500), "1.50s");
903 assert_eq!(format_duration(65000), "1m 5s");
904 assert_eq!(format_duration(3665000), "1h 1m");
905 }
906
907 #[test]
908 fn test_truncate_string() {
909 assert_eq!(truncate_string("short", 10), "short");
910 assert_eq!(truncate_string("this is a long string", 10), "this is...");
911 }
912
913 #[test]
914 fn test_history_event_builder() {
915 let event = HistoryEvent::new("TestEvent")
916 .with_timestamp(1234567890)
917 .with_operation_id("op-001")
918 .with_data("some data");
919
920 assert_eq!(event.event_type, "TestEvent");
921 assert_eq!(event.timestamp, Some(1234567890));
922 assert_eq!(event.operation_id, Some("op-001".to_string()));
923 assert_eq!(event.data, Some("some data".to_string()));
924 }
925
926 #[test]
927 fn test_running_status() {
928 let result: TestResult<String> = TestResult::with_status(ExecutionStatus::Running, vec![]);
929
930 assert!(result.is_running());
931 assert!(!result.is_success());
932 assert!(!result.is_failure());
933 assert!(result.get_result().is_err());
934 assert!(result.get_error().is_err());
935 }
936
937 #[test]
938 fn test_cancelled_status() {
939 let mut result: TestResult<String> =
940 TestResult::with_status(ExecutionStatus::Cancelled, vec![]);
941 result.set_error(TestResultError::new(
942 "CancelledError",
943 "Execution was cancelled",
944 ));
945
946 assert!(!result.is_running());
947 assert!(!result.is_success());
948 assert!(result.is_failure());
949 assert!(result.get_result().is_err());
950 assert!(result.get_error().is_ok());
951 }
952
953 #[test]
954 fn test_timed_out_status() {
955 let mut result: TestResult<String> =
956 TestResult::with_status(ExecutionStatus::TimedOut, vec![]);
957 result.set_error(TestResultError::new("TimeoutError", "Execution timed out"));
958
959 assert!(!result.is_running());
960 assert!(!result.is_success());
961 assert!(result.is_failure());
962 assert!(result.get_result().is_err());
963 assert!(result.get_error().is_ok());
964 }
965}
966
967#[cfg(test)]
971mod property_tests {
972 use super::*;
973 use durable_execution_sdk::{Operation, OperationStatus, OperationType};
974 use proptest::prelude::*;
975
976 fn operation_type_strategy() -> impl Strategy<Value = OperationType> {
978 prop_oneof![
979 Just(OperationType::Step),
980 Just(OperationType::Wait),
981 Just(OperationType::Callback),
982 Just(OperationType::Invoke),
983 Just(OperationType::Context),
984 ]
985 }
986
987 fn operation_status_strategy() -> impl Strategy<Value = OperationStatus> {
989 prop_oneof![
990 Just(OperationStatus::Started),
991 Just(OperationStatus::Pending),
992 Just(OperationStatus::Ready),
993 Just(OperationStatus::Succeeded),
994 Just(OperationStatus::Failed),
995 Just(OperationStatus::Cancelled),
996 Just(OperationStatus::TimedOut),
997 Just(OperationStatus::Stopped),
998 ]
999 }
1000
1001 fn operation_strategy() -> impl Strategy<Value = Operation> {
1003 (
1004 "[a-zA-Z0-9_-]{1,20}", operation_type_strategy(),
1006 operation_status_strategy(),
1007 proptest::option::of("[a-zA-Z0-9_-]{1,20}"), )
1009 .prop_map(|(id, op_type, status, name)| {
1010 let mut op = Operation::new(id, op_type);
1011 op.status = status;
1012 op.name = name;
1013 op
1014 })
1015 }
1016
1017 fn operations_strategy() -> impl Strategy<Value = Vec<Operation>> {
1019 prop::collection::vec(operation_strategy(), 0..=20)
1020 }
1021
1022 fn result_value_strategy() -> impl Strategy<Value = String> {
1024 "[a-zA-Z0-9 _-]{0,100}"
1025 }
1026
1027 fn error_strategy() -> impl Strategy<Value = TestResultError> {
1029 (
1030 proptest::option::of("[a-zA-Z0-9_]{1,30}"), proptest::option::of("[a-zA-Z0-9 _-]{1,100}"), )
1033 .prop_map(|(error_type, error_message)| TestResultError {
1034 error_type,
1035 error_message,
1036 error_data: None,
1037 stack_trace: None,
1038 })
1039 }
1040
1041 proptest! {
1042 #[test]
1050 fn prop_result_retrieval_consistency(
1051 result_value in result_value_strategy(),
1052 error in error_strategy(),
1053 operations in operations_strategy(),
1054 ) {
1055 let success_result: TestResult<String> = TestResult::success(result_value.clone(), operations.clone());
1057
1058 let retrieved = success_result.get_result();
1060 prop_assert!(retrieved.is_ok(), "get_result() should succeed for successful execution");
1061 prop_assert_eq!(retrieved.unwrap(), &result_value, "Retrieved value should match original");
1062
1063 let error_result = success_result.get_error();
1065 prop_assert!(error_result.is_err(), "get_error() should fail for successful execution");
1066
1067 let failure_result: TestResult<String> = TestResult::failure(error.clone(), operations.clone());
1069
1070 let retrieved = failure_result.get_result();
1072 prop_assert!(retrieved.is_err(), "get_result() should fail for failed execution");
1073
1074 let error_result = failure_result.get_error();
1076 prop_assert!(error_result.is_ok(), "get_error() should succeed for failed execution");
1077 let retrieved_error = error_result.unwrap();
1078 prop_assert_eq!(&retrieved_error.error_type, &error.error_type, "Error type should match");
1079 prop_assert_eq!(&retrieved_error.error_message, &error.error_message, "Error message should match");
1080 }
1081
1082 #[test]
1090 fn prop_operation_filtering_correctness(
1091 operations in operations_strategy(),
1092 filter_status in operation_status_strategy(),
1093 ) {
1094 let result: TestResult<String> = TestResult::success("test".to_string(), operations.clone());
1095
1096 let filtered = result.get_operations_by_status(filter_status);
1098
1099 let expected_count = operations.iter().filter(|op| op.status == filter_status).count();
1101
1102 prop_assert_eq!(
1104 filtered.len(),
1105 expected_count,
1106 "Filtered count should match expected count for status {:?}",
1107 filter_status
1108 );
1109
1110 for op in &filtered {
1112 prop_assert_eq!(
1113 op.status,
1114 filter_status,
1115 "All filtered operations should have status {:?}",
1116 filter_status
1117 );
1118 }
1119
1120 let all_ops = result.get_operations();
1122 let missed_count = all_ops
1123 .iter()
1124 .filter(|op| op.status == filter_status)
1125 .filter(|op| !filtered.iter().any(|f| f.operation_id == op.operation_id))
1126 .count();
1127
1128 prop_assert_eq!(
1129 missed_count,
1130 0,
1131 "No operations with status {:?} should be missed",
1132 filter_status
1133 );
1134 }
1135 }
1136}