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 {
200 self.status
201 }
202
203 pub fn get_result(&self) -> Result<&T, TestError> {
215 match self.status {
216 ExecutionStatus::Succeeded => self.result.as_ref().ok_or_else(|| {
217 TestError::result_not_available("Execution succeeded but result is not set")
218 }),
219 ExecutionStatus::Failed => Err(TestError::result_not_available(
220 "Cannot get result from failed execution",
221 )),
222 ExecutionStatus::Running => Err(TestError::result_not_available(
223 "Execution is still running",
224 )),
225 ExecutionStatus::Cancelled => {
226 Err(TestError::result_not_available("Execution was cancelled"))
227 }
228 ExecutionStatus::TimedOut => {
229 Err(TestError::result_not_available("Execution timed out"))
230 }
231 }
232 }
233
234 pub fn get_error(&self) -> Result<&TestResultError, &str> {
245 match self.status {
246 ExecutionStatus::Failed | ExecutionStatus::Cancelled | ExecutionStatus::TimedOut => {
247 self.error
248 .as_ref()
249 .ok_or("Execution failed but error details are not available")
250 }
251 ExecutionStatus::Succeeded => Err("Cannot get error from successful execution"),
252 ExecutionStatus::Running => Err("Execution is still running"),
253 }
254 }
255
256 pub fn get_operations(&self) -> &[Operation] {
266 &self.operations
267 }
268
269 pub fn get_operations_by_status(&self, status: OperationStatus) -> Vec<&Operation> {
283 self.operations
284 .iter()
285 .filter(|op| op.status == status)
286 .collect()
287 }
288
289 pub fn get_invocations(&self) -> &[Invocation] {
299 &self.invocations
300 }
301
302 pub fn get_history_events(&self) -> &[HistoryEvent] {
308 &self.history_events
309 }
310
311 pub fn get_nodejs_history_events(&self) -> &[NodeJsHistoryEvent] {
325 &self.nodejs_history_events
326 }
327
328 pub fn is_success(&self) -> bool {
330 self.status.is_success()
331 }
332
333 pub fn is_failure(&self) -> bool {
335 self.status.is_failure()
336 }
337
338 pub fn is_running(&self) -> bool {
340 matches!(self.status, ExecutionStatus::Running)
341 }
342
343 pub fn operation_count(&self) -> usize {
345 self.operations.len()
346 }
347
348 pub fn invocation_count(&self) -> usize {
350 self.invocations.len()
351 }
352}
353
354#[derive(Debug, Clone)]
358pub struct PrintConfig {
359 pub show_id: bool,
361 pub show_parent_id: bool,
363 pub show_name: bool,
365 pub show_type: bool,
367 pub show_status: bool,
369 pub show_start_time: bool,
371 pub show_end_time: bool,
373 pub show_duration: bool,
375 pub show_result: bool,
377}
378
379impl Default for PrintConfig {
380 fn default() -> Self {
381 Self {
382 show_id: false,
383 show_parent_id: false,
384 show_name: true,
385 show_type: true,
386 show_status: true,
387 show_start_time: true,
388 show_end_time: true,
389 show_duration: true,
390 show_result: false,
391 }
392 }
393}
394
395impl PrintConfig {
396 pub fn all() -> Self {
398 Self {
399 show_id: true,
400 show_parent_id: true,
401 show_name: true,
402 show_type: true,
403 show_status: true,
404 show_start_time: true,
405 show_end_time: true,
406 show_duration: true,
407 show_result: true,
408 }
409 }
410
411 pub fn minimal() -> Self {
413 Self {
414 show_id: false,
415 show_parent_id: false,
416 show_name: true,
417 show_type: true,
418 show_status: true,
419 show_start_time: false,
420 show_end_time: false,
421 show_duration: false,
422 show_result: false,
423 }
424 }
425
426 pub fn with_id(mut self, show: bool) -> Self {
428 self.show_id = show;
429 self
430 }
431
432 pub fn with_parent_id(mut self, show: bool) -> Self {
434 self.show_parent_id = show;
435 self
436 }
437
438 pub fn with_name(mut self, show: bool) -> Self {
440 self.show_name = show;
441 self
442 }
443
444 pub fn with_type(mut self, show: bool) -> Self {
446 self.show_type = show;
447 self
448 }
449
450 pub fn with_status(mut self, show: bool) -> Self {
452 self.show_status = show;
453 self
454 }
455
456 pub fn with_start_time(mut self, show: bool) -> Self {
458 self.show_start_time = show;
459 self
460 }
461
462 pub fn with_end_time(mut self, show: bool) -> Self {
464 self.show_end_time = show;
465 self
466 }
467
468 pub fn with_duration(mut self, show: bool) -> Self {
470 self.show_duration = show;
471 self
472 }
473
474 pub fn with_result(mut self, show: bool) -> Self {
476 self.show_result = show;
477 self
478 }
479}
480
481impl<T> TestResult<T> {
482 pub fn print(&self) {
491 self.print_with_config(PrintConfig::default());
492 }
493
494 pub fn print_with_config(&self, config: PrintConfig) {
504 let mut headers: Vec<&str> = Vec::new();
506 if config.show_id {
507 headers.push("ID");
508 }
509 if config.show_parent_id {
510 headers.push("Parent ID");
511 }
512 if config.show_name {
513 headers.push("Name");
514 }
515 if config.show_type {
516 headers.push("Type");
517 }
518 if config.show_status {
519 headers.push("Status");
520 }
521 if config.show_start_time {
522 headers.push("Start Time");
523 }
524 if config.show_end_time {
525 headers.push("End Time");
526 }
527 if config.show_duration {
528 headers.push("Duration");
529 }
530 if config.show_result {
531 headers.push("Result/Error");
532 }
533
534 let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
536
537 let rows: Vec<Vec<String>> = self
539 .operations
540 .iter()
541 .map(|op| {
542 let mut row: Vec<String> = Vec::new();
543 let mut col_idx = 0;
544
545 if config.show_id {
546 let val = op.operation_id.clone();
547 widths[col_idx] = widths[col_idx].max(val.len());
548 row.push(val);
549 col_idx += 1;
550 }
551 if config.show_parent_id {
552 let val = op.parent_id.clone().unwrap_or_else(|| "-".to_string());
553 widths[col_idx] = widths[col_idx].max(val.len());
554 row.push(val);
555 col_idx += 1;
556 }
557 if config.show_name {
558 let val = op.name.clone().unwrap_or_else(|| "-".to_string());
559 widths[col_idx] = widths[col_idx].max(val.len());
560 row.push(val);
561 col_idx += 1;
562 }
563 if config.show_type {
564 let val = format!("{}", op.operation_type);
565 widths[col_idx] = widths[col_idx].max(val.len());
566 row.push(val);
567 col_idx += 1;
568 }
569 if config.show_status {
570 let val = format!("{}", op.status);
571 widths[col_idx] = widths[col_idx].max(val.len());
572 row.push(val);
573 col_idx += 1;
574 }
575 if config.show_start_time {
576 let val = op
577 .start_timestamp
578 .map(format_timestamp)
579 .unwrap_or_else(|| "-".to_string());
580 widths[col_idx] = widths[col_idx].max(val.len());
581 row.push(val);
582 col_idx += 1;
583 }
584 if config.show_end_time {
585 let val = op
586 .end_timestamp
587 .map(format_timestamp)
588 .unwrap_or_else(|| "-".to_string());
589 widths[col_idx] = widths[col_idx].max(val.len());
590 row.push(val);
591 col_idx += 1;
592 }
593 if config.show_duration {
594 let val = match (op.start_timestamp, op.end_timestamp) {
595 (Some(start), Some(end)) => format_duration(end - start),
596 _ => "-".to_string(),
597 };
598 widths[col_idx] = widths[col_idx].max(val.len());
599 row.push(val);
600 col_idx += 1;
601 }
602 if config.show_result {
603 let val = if let Some(ref err) = op.error {
604 format!("Error: {}", err.error_message)
605 } else if let Some(result) = op.get_result() {
606 truncate_string(result, 50)
607 } else {
608 "-".to_string()
609 };
610 widths[col_idx] = widths[col_idx].max(val.len().min(50));
611 row.push(val);
612 }
613
614 row
615 })
616 .collect();
617
618 println!("\n=== Execution Result ===");
620 println!("Status: {}", self.status);
621 if let Some(ref err) = self.error {
622 println!("Error: {}", err);
623 }
624 println!("Operations: {}", self.operations.len());
625 println!("Invocations: {}", self.invocations.len());
626 println!();
627
628 print_row(
630 &headers.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
631 &widths,
632 );
633 print_separator(&widths);
634
635 for row in rows {
637 print_row(&row, &widths);
638 }
639
640 println!();
641 }
642}
643
644fn format_timestamp(millis: i64) -> String {
646 use chrono::{TimeZone, Utc};
647 match Utc.timestamp_millis_opt(millis) {
648 chrono::LocalResult::Single(dt) => dt.format("%Y-%m-%d %H:%M:%S%.3f").to_string(),
649 _ => format!("{}ms", millis),
650 }
651}
652
653fn format_duration(millis: i64) -> String {
655 if millis < 1000 {
656 format!("{}ms", millis)
657 } else if millis < 60_000 {
658 format!("{:.2}s", millis as f64 / 1000.0)
659 } else if millis < 3_600_000 {
660 let mins = millis / 60_000;
661 let secs = (millis % 60_000) / 1000;
662 format!("{}m {}s", mins, secs)
663 } else {
664 let hours = millis / 3_600_000;
665 let mins = (millis % 3_600_000) / 60_000;
666 format!("{}h {}m", hours, mins)
667 }
668}
669
670fn truncate_string(s: &str, max_len: usize) -> String {
672 if s.len() <= max_len {
673 s.to_string()
674 } else {
675 format!("{}...", &s[..max_len - 3])
676 }
677}
678
679fn print_row(row: &[String], widths: &[usize]) {
681 let formatted: Vec<String> = row
682 .iter()
683 .zip(widths.iter())
684 .map(|(val, width)| format!("{:<width$}", val, width = width))
685 .collect();
686 println!("| {} |", formatted.join(" | "));
687}
688
689fn print_separator(widths: &[usize]) {
691 let separators: Vec<String> = widths.iter().map(|w| "-".repeat(*w)).collect();
692 println!("+-{}-+", separators.join("-+-"));
693}
694
695impl<T: DeserializeOwned> TestResult<T> {
697 pub fn deserialize_result_from_json(json: &str) -> Result<T, TestError> {
702 serde_json::from_str(json).map_err(TestError::from)
703 }
704}
705
706#[cfg(test)]
707mod tests {
708 use super::*;
709 use durable_execution_sdk::{Operation, OperationStatus, OperationType};
710
711 fn create_test_operation(
712 name: &str,
713 op_type: OperationType,
714 status: OperationStatus,
715 ) -> Operation {
716 let mut op = Operation::new(format!("{}-001", name), op_type);
717 op.name = Some(name.to_string());
718 op.status = status;
719 op
720 }
721
722 #[test]
723 fn test_success_result() {
724 let ops = vec![create_test_operation(
725 "step1",
726 OperationType::Step,
727 OperationStatus::Succeeded,
728 )];
729 let result: TestResult<String> = TestResult::success("hello".to_string(), ops);
730
731 assert_eq!(result.get_status(), ExecutionStatus::Succeeded);
732 assert!(result.is_success());
733 assert!(!result.is_failure());
734 assert_eq!(result.get_result().unwrap(), "hello");
735 assert!(result.get_error().is_err());
736 }
737
738 #[test]
739 fn test_failure_result() {
740 let ops = vec![create_test_operation(
741 "step1",
742 OperationType::Step,
743 OperationStatus::Failed,
744 )];
745 let error = TestResultError::new("TestError", "Something went wrong");
746 let result: TestResult<String> = TestResult::failure(error, ops);
747
748 assert_eq!(result.get_status(), ExecutionStatus::Failed);
749 assert!(!result.is_success());
750 assert!(result.is_failure());
751 assert!(result.get_result().is_err());
752 assert!(result.get_error().is_ok());
753 assert_eq!(
754 result.get_error().unwrap().error_message,
755 Some("Something went wrong".to_string())
756 );
757 }
758
759 #[test]
760 fn test_get_operations() {
761 let ops = vec![
762 create_test_operation("step1", OperationType::Step, OperationStatus::Succeeded),
763 create_test_operation("wait1", OperationType::Wait, OperationStatus::Succeeded),
764 create_test_operation("step2", OperationType::Step, OperationStatus::Failed),
765 ];
766 let result: TestResult<String> = TestResult::success("done".to_string(), ops);
767
768 assert_eq!(result.get_operations().len(), 3);
769 assert_eq!(result.operation_count(), 3);
770 }
771
772 #[test]
773 fn test_get_operations_by_status() {
774 let ops = vec![
775 create_test_operation("step1", OperationType::Step, OperationStatus::Succeeded),
776 create_test_operation("wait1", OperationType::Wait, OperationStatus::Succeeded),
777 create_test_operation("step2", OperationType::Step, OperationStatus::Failed),
778 create_test_operation("step3", OperationType::Step, OperationStatus::Started),
779 ];
780 let result: TestResult<String> = TestResult::success("done".to_string(), ops);
781
782 let succeeded = result.get_operations_by_status(OperationStatus::Succeeded);
783 assert_eq!(succeeded.len(), 2);
784
785 let failed = result.get_operations_by_status(OperationStatus::Failed);
786 assert_eq!(failed.len(), 1);
787
788 let started = result.get_operations_by_status(OperationStatus::Started);
789 assert_eq!(started.len(), 1);
790
791 let pending = result.get_operations_by_status(OperationStatus::Pending);
792 assert_eq!(pending.len(), 0);
793 }
794
795 #[test]
796 fn test_invocations() {
797 let mut result: TestResult<String> = TestResult::success("done".to_string(), vec![]);
798
799 assert_eq!(result.get_invocations().len(), 0);
800 assert_eq!(result.invocation_count(), 0);
801
802 result.add_invocation(Invocation::new());
803 result.add_invocation(Invocation::new());
804
805 assert_eq!(result.get_invocations().len(), 2);
806 assert_eq!(result.invocation_count(), 2);
807 }
808
809 #[test]
810 fn test_history_events() {
811 let mut result: TestResult<String> = TestResult::success("done".to_string(), vec![]);
812
813 assert_eq!(result.get_history_events().len(), 0);
814
815 result.add_history_event(HistoryEvent::new("ExecutionStarted"));
816 result.add_history_event(HistoryEvent::new("StepCompleted").with_operation_id("step-001"));
817
818 assert_eq!(result.get_history_events().len(), 2);
819 assert_eq!(
820 result.get_history_events()[0].event_type,
821 "ExecutionStarted"
822 );
823 assert_eq!(
824 result.get_history_events()[1].operation_id,
825 Some("step-001".to_string())
826 );
827 }
828
829 #[test]
830 fn test_nodejs_history_events() {
831 use crate::checkpoint_server::{
832 ExecutionStartedDetails, ExecutionStartedDetailsWrapper, NodeJsEventDetails,
833 NodeJsEventType, NodeJsHistoryEvent, PayloadWrapper,
834 };
835
836 let mut result: TestResult<String> = TestResult::success("done".to_string(), vec![]);
837
838 assert_eq!(result.get_nodejs_history_events().len(), 0);
840
841 let event1 = NodeJsHistoryEvent {
843 event_type: NodeJsEventType::ExecutionStarted,
844 event_id: 1,
845 id: Some("exec-123".to_string()),
846 event_timestamp: "2025-12-03T22:58:35.094Z".to_string(),
847 sub_type: None,
848 name: None,
849 parent_id: None,
850 details: NodeJsEventDetails::ExecutionStarted(ExecutionStartedDetailsWrapper {
851 execution_started_details: ExecutionStartedDetails {
852 input: PayloadWrapper::new("{}"),
853 execution_timeout: None,
854 },
855 }),
856 };
857
858 let event2 = NodeJsHistoryEvent {
859 event_type: NodeJsEventType::StepStarted,
860 event_id: 2,
861 id: Some("step-456".to_string()),
862 event_timestamp: "2025-12-03T22:58:35.096Z".to_string(),
863 sub_type: Some("Step".to_string()),
864 name: Some("my-step".to_string()),
865 parent_id: Some("exec-123".to_string()),
866 details: NodeJsEventDetails::default(),
867 };
868
869 result.add_nodejs_history_event(event1.clone());
871 result.add_nodejs_history_event(event2.clone());
872
873 assert_eq!(result.get_nodejs_history_events().len(), 2);
874 assert_eq!(
875 result.get_nodejs_history_events()[0].event_type,
876 NodeJsEventType::ExecutionStarted
877 );
878 assert_eq!(result.get_nodejs_history_events()[0].event_id, 1);
879 assert_eq!(
880 result.get_nodejs_history_events()[1].event_type,
881 NodeJsEventType::StepStarted
882 );
883 assert_eq!(result.get_nodejs_history_events()[1].event_id, 2);
884 assert_eq!(
885 result.get_nodejs_history_events()[1].name,
886 Some("my-step".to_string())
887 );
888
889 let mut result2: TestResult<String> = TestResult::success("done".to_string(), vec![]);
891 result2.set_nodejs_history_events(vec![event1, event2]);
892 assert_eq!(result2.get_nodejs_history_events().len(), 2);
893 }
894
895 #[test]
896 fn test_print_config_default() {
897 let config = PrintConfig::default();
898 assert!(!config.show_id);
899 assert!(!config.show_parent_id);
900 assert!(config.show_name);
901 assert!(config.show_type);
902 assert!(config.show_status);
903 assert!(config.show_start_time);
904 assert!(config.show_end_time);
905 assert!(config.show_duration);
906 assert!(!config.show_result);
907 }
908
909 #[test]
910 fn test_print_config_all() {
911 let config = PrintConfig::all();
912 assert!(config.show_id);
913 assert!(config.show_parent_id);
914 assert!(config.show_name);
915 assert!(config.show_type);
916 assert!(config.show_status);
917 assert!(config.show_start_time);
918 assert!(config.show_end_time);
919 assert!(config.show_duration);
920 assert!(config.show_result);
921 }
922
923 #[test]
924 fn test_print_config_minimal() {
925 let config = PrintConfig::minimal();
926 assert!(!config.show_id);
927 assert!(!config.show_parent_id);
928 assert!(config.show_name);
929 assert!(config.show_type);
930 assert!(config.show_status);
931 assert!(!config.show_start_time);
932 assert!(!config.show_end_time);
933 assert!(!config.show_duration);
934 assert!(!config.show_result);
935 }
936
937 #[test]
938 fn test_format_duration() {
939 assert_eq!(format_duration(500), "500ms");
940 assert_eq!(format_duration(1500), "1.50s");
941 assert_eq!(format_duration(65000), "1m 5s");
942 assert_eq!(format_duration(3665000), "1h 1m");
943 }
944
945 #[test]
946 fn test_truncate_string() {
947 assert_eq!(truncate_string("short", 10), "short");
948 assert_eq!(truncate_string("this is a long string", 10), "this is...");
949 }
950
951 #[test]
952 fn test_history_event_builder() {
953 let event = HistoryEvent::new("TestEvent")
954 .with_timestamp(1234567890)
955 .with_operation_id("op-001")
956 .with_data("some data");
957
958 assert_eq!(event.event_type, "TestEvent");
959 assert_eq!(event.timestamp, Some(1234567890));
960 assert_eq!(event.operation_id, Some("op-001".to_string()));
961 assert_eq!(event.data, Some("some data".to_string()));
962 }
963
964 #[test]
965 fn test_running_status() {
966 let result: TestResult<String> = TestResult::with_status(ExecutionStatus::Running, vec![]);
967
968 assert!(result.is_running());
969 assert!(!result.is_success());
970 assert!(!result.is_failure());
971 assert!(result.get_result().is_err());
972 assert!(result.get_error().is_err());
973 }
974
975 #[test]
976 fn test_cancelled_status() {
977 let mut result: TestResult<String> =
978 TestResult::with_status(ExecutionStatus::Cancelled, vec![]);
979 result.set_error(TestResultError::new(
980 "CancelledError",
981 "Execution was cancelled",
982 ));
983
984 assert!(!result.is_running());
985 assert!(!result.is_success());
986 assert!(result.is_failure());
987 assert!(result.get_result().is_err());
988 assert!(result.get_error().is_ok());
989 }
990
991 #[test]
992 fn test_timed_out_status() {
993 let mut result: TestResult<String> =
994 TestResult::with_status(ExecutionStatus::TimedOut, vec![]);
995 result.set_error(TestResultError::new("TimeoutError", "Execution timed out"));
996
997 assert!(!result.is_running());
998 assert!(!result.is_success());
999 assert!(result.is_failure());
1000 assert!(result.get_result().is_err());
1001 assert!(result.get_error().is_ok());
1002 }
1003}
1004
1005#[cfg(test)]
1009mod property_tests {
1010 use super::*;
1011 use durable_execution_sdk::{Operation, OperationStatus, OperationType};
1012 use proptest::prelude::*;
1013
1014 fn operation_type_strategy() -> impl Strategy<Value = OperationType> {
1016 prop_oneof![
1017 Just(OperationType::Step),
1018 Just(OperationType::Wait),
1019 Just(OperationType::Callback),
1020 Just(OperationType::Invoke),
1021 Just(OperationType::Context),
1022 ]
1023 }
1024
1025 fn operation_status_strategy() -> impl Strategy<Value = OperationStatus> {
1027 prop_oneof![
1028 Just(OperationStatus::Started),
1029 Just(OperationStatus::Pending),
1030 Just(OperationStatus::Ready),
1031 Just(OperationStatus::Succeeded),
1032 Just(OperationStatus::Failed),
1033 Just(OperationStatus::Cancelled),
1034 Just(OperationStatus::TimedOut),
1035 Just(OperationStatus::Stopped),
1036 ]
1037 }
1038
1039 fn operation_strategy() -> impl Strategy<Value = Operation> {
1041 (
1042 "[a-zA-Z0-9_-]{1,20}", operation_type_strategy(),
1044 operation_status_strategy(),
1045 proptest::option::of("[a-zA-Z0-9_-]{1,20}"), )
1047 .prop_map(|(id, op_type, status, name)| {
1048 let mut op = Operation::new(id, op_type);
1049 op.status = status;
1050 op.name = name;
1051 op
1052 })
1053 }
1054
1055 fn operations_strategy() -> impl Strategy<Value = Vec<Operation>> {
1057 prop::collection::vec(operation_strategy(), 0..=20)
1058 }
1059
1060 fn result_value_strategy() -> impl Strategy<Value = String> {
1062 "[a-zA-Z0-9 _-]{0,100}"
1063 }
1064
1065 fn error_strategy() -> impl Strategy<Value = TestResultError> {
1067 (
1068 proptest::option::of("[a-zA-Z0-9_]{1,30}"), proptest::option::of("[a-zA-Z0-9 _-]{1,100}"), )
1071 .prop_map(|(error_type, error_message)| TestResultError {
1072 error_type,
1073 error_message,
1074 error_data: None,
1075 stack_trace: None,
1076 })
1077 }
1078
1079 proptest! {
1080 #[test]
1088 fn prop_result_retrieval_consistency(
1089 result_value in result_value_strategy(),
1090 error in error_strategy(),
1091 operations in operations_strategy(),
1092 ) {
1093 let success_result: TestResult<String> = TestResult::success(result_value.clone(), operations.clone());
1095
1096 let retrieved = success_result.get_result();
1098 prop_assert!(retrieved.is_ok(), "get_result() should succeed for successful execution");
1099 prop_assert_eq!(retrieved.unwrap(), &result_value, "Retrieved value should match original");
1100
1101 let error_result = success_result.get_error();
1103 prop_assert!(error_result.is_err(), "get_error() should fail for successful execution");
1104
1105 let failure_result: TestResult<String> = TestResult::failure(error.clone(), operations.clone());
1107
1108 let retrieved = failure_result.get_result();
1110 prop_assert!(retrieved.is_err(), "get_result() should fail for failed execution");
1111
1112 let error_result = failure_result.get_error();
1114 prop_assert!(error_result.is_ok(), "get_error() should succeed for failed execution");
1115 let retrieved_error = error_result.unwrap();
1116 prop_assert_eq!(&retrieved_error.error_type, &error.error_type, "Error type should match");
1117 prop_assert_eq!(&retrieved_error.error_message, &error.error_message, "Error message should match");
1118 }
1119
1120 #[test]
1128 fn prop_operation_filtering_correctness(
1129 operations in operations_strategy(),
1130 filter_status in operation_status_strategy(),
1131 ) {
1132 let result: TestResult<String> = TestResult::success("test".to_string(), operations.clone());
1133
1134 let filtered = result.get_operations_by_status(filter_status);
1136
1137 let expected_count = operations.iter().filter(|op| op.status == filter_status).count();
1139
1140 prop_assert_eq!(
1142 filtered.len(),
1143 expected_count,
1144 "Filtered count should match expected count for status {:?}",
1145 filter_status
1146 );
1147
1148 for op in &filtered {
1150 prop_assert_eq!(
1151 op.status,
1152 filter_status,
1153 "All filtered operations should have status {:?}",
1154 filter_status
1155 );
1156 }
1157
1158 let all_ops = result.get_operations();
1160 let missed_count = all_ops
1161 .iter()
1162 .filter(|op| op.status == filter_status)
1163 .filter(|op| !filtered.iter().any(|f| f.operation_id == op.operation_id))
1164 .count();
1165
1166 prop_assert_eq!(
1167 missed_count,
1168 0,
1169 "No operations with status {:?} should be missed",
1170 filter_status
1171 );
1172 }
1173 }
1174}