1#![allow(missing_docs)]
32
33use super::{Brick, BrickError};
34use std::collections::HashMap;
35use std::fmt::Debug;
36use std::time::{Duration, Instant};
37
38pub type PipelineResult<T> = Result<T, PipelineError>;
40
41#[derive(Debug, Clone)]
43pub struct PipelineContext {
44 pub data: HashMap<String, PipelineData>,
46 pub metadata: PipelineMetadata,
48 pub trace: Vec<StageTrace>,
50}
51
52impl PipelineContext {
53 #[must_use]
55 pub fn new() -> Self {
56 Self {
57 data: HashMap::new(),
58 metadata: PipelineMetadata::new(),
59 trace: Vec::new(),
60 }
61 }
62
63 #[must_use]
65 pub fn from_input(name: &str, data: PipelineData) -> Self {
66 let mut ctx = Self::new();
67 ctx.data.insert(name.to_string(), data);
68 ctx
69 }
70
71 pub fn get(&self, name: &str) -> Option<&PipelineData> {
73 self.data.get(name)
74 }
75
76 pub fn set(&mut self, name: impl Into<String>, data: PipelineData) {
78 self.data.insert(name.into(), data);
79 }
80
81 pub fn add_trace(&mut self, trace: StageTrace) {
83 self.trace.push(trace);
84 }
85}
86
87impl Default for PipelineContext {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93#[derive(Debug, Clone)]
95pub enum PipelineData {
96 Bytes(Vec<u8>),
98 FloatTensor { data: Vec<f32>, shape: Vec<usize> },
100 Text(String),
102 Json(serde_json::Value),
104 Int(i64),
106 Bool(bool),
108}
109
110impl PipelineData {
111 #[must_use]
113 pub fn tensor(data: Vec<f32>, shape: Vec<usize>) -> Self {
114 Self::FloatTensor { data, shape }
115 }
116
117 pub fn as_tensor(&self) -> Option<(&[f32], &[usize])> {
119 match self {
120 Self::FloatTensor { data, shape } => Some((data, shape)),
121 _ => None,
122 }
123 }
124
125 pub fn as_text(&self) -> Option<&str> {
127 match self {
128 Self::Text(s) => Some(s),
129 _ => None,
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
136pub struct PipelineMetadata {
137 pub run_id: String,
139 pub started_at: Option<Instant>,
141 pub tags: HashMap<String, String>,
143}
144
145impl PipelineMetadata {
146 #[must_use]
148 pub fn new() -> Self {
149 Self {
150 run_id: format!("run-{}", uuid_v4()),
151 started_at: None,
152 tags: HashMap::new(),
153 }
154 }
155
156 pub fn tag(&mut self, key: impl Into<String>, value: impl Into<String>) {
158 self.tags.insert(key.into(), value.into());
159 }
160}
161
162impl Default for PipelineMetadata {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct StageTrace {
171 pub stage_name: String,
173 pub duration: Duration,
175 pub success: bool,
177 pub error: Option<String>,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum PrivacyTier {
184 Sovereign,
186 Private,
188 Standard,
190}
191
192impl Default for PrivacyTier {
193 fn default() -> Self {
194 Self::Standard
195 }
196}
197
198#[derive(Debug, Clone)]
200pub struct ValidationResult {
201 pub valid: bool,
203 pub messages: Vec<ValidationMessage>,
205}
206
207impl ValidationResult {
208 #[must_use]
210 pub fn ok() -> Self {
211 Self {
212 valid: true,
213 messages: Vec::new(),
214 }
215 }
216
217 #[must_use]
219 pub fn fail(reason: impl Into<String>) -> Self {
220 Self {
221 valid: false,
222 messages: vec![ValidationMessage {
223 level: ValidationLevel::Error,
224 message: reason.into(),
225 }],
226 }
227 }
228
229 pub fn warn(&mut self, message: impl Into<String>) {
231 self.messages.push(ValidationMessage {
232 level: ValidationLevel::Warning,
233 message: message.into(),
234 });
235 }
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub enum ValidationLevel {
241 Info,
243 Warning,
245 Error,
247}
248
249#[derive(Debug, Clone)]
251pub struct ValidationMessage {
252 pub level: ValidationLevel,
254 pub message: String,
256}
257
258#[derive(Debug, Clone)]
260pub enum PipelineError {
261 ValidationFailed { stage: String, reason: String },
263 ExecutionFailed { stage: String, reason: String },
265 MissingInput { stage: String, input: String },
267 PrivacyViolation { tier: PrivacyTier, reason: String },
269 CheckpointFailed { reason: String },
271 BrickError(String),
273}
274
275impl std::fmt::Display for PipelineError {
276 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277 match self {
278 Self::ValidationFailed { stage, reason } => {
279 write!(f, "Validation failed at stage '{}': {}", stage, reason)
280 }
281 Self::ExecutionFailed { stage, reason } => {
282 write!(f, "Execution failed at stage '{}': {}", stage, reason)
283 }
284 Self::MissingInput { stage, input } => {
285 write!(f, "Missing input '{}' for stage '{}'", input, stage)
286 }
287 Self::PrivacyViolation { tier, reason } => {
288 write!(f, "Privacy tier {:?} violated: {}", tier, reason)
289 }
290 Self::CheckpointFailed { reason } => {
291 write!(f, "Checkpoint failed: {}", reason)
292 }
293 Self::BrickError(msg) => write!(f, "Brick error: {}", msg),
294 }
295 }
296}
297
298impl std::error::Error for PipelineError {}
299
300impl From<BrickError> for PipelineError {
301 fn from(e: BrickError) -> Self {
302 Self::BrickError(e.to_string())
303 }
304}
305
306pub trait BrickStage: Brick + Send + Sync {
308 fn execute(&self, ctx: PipelineContext) -> PipelineResult<PipelineContext>;
310
311 fn validate(&self, ctx: &PipelineContext) -> ValidationResult;
313
314 fn required_inputs(&self) -> &[&str] {
316 &[]
317 }
318
319 fn output_names(&self) -> &[&str] {
321 &[]
322 }
323}
324
325#[derive(Debug, Clone)]
327pub struct AuditEntry {
328 pub stage: String,
330 pub timestamp: Instant,
332 pub duration: Duration,
334 pub success: bool,
336 pub inputs: Vec<String>,
338 pub outputs: Vec<String>,
340}
341
342#[derive(Debug, Default)]
344pub struct PipelineAuditCollector {
345 entries: Vec<AuditEntry>,
346}
347
348impl PipelineAuditCollector {
349 #[must_use]
351 pub fn new() -> Self {
352 Self {
353 entries: Vec::new(),
354 }
355 }
356
357 pub fn record(&mut self, stage: &str, duration: Duration, success: bool) {
359 self.entries.push(AuditEntry {
360 stage: stage.to_string(),
361 timestamp: Instant::now(),
362 duration,
363 success,
364 inputs: Vec::new(),
365 outputs: Vec::new(),
366 });
367 }
368
369 pub fn entries(&self) -> &[AuditEntry] {
371 &self.entries
372 }
373
374 pub fn total_duration(&self) -> Duration {
376 self.entries.iter().map(|e| e.duration).sum()
377 }
378}
379
380#[derive(Debug, Clone)]
382pub struct Checkpoint {
383 pub stage_index: usize,
385 pub context: PipelineContext,
387 pub created_at: Instant,
389}
390
391pub struct BrickPipeline {
393 name: String,
395 stages: Vec<Box<dyn BrickStage>>,
397 privacy_tier: PrivacyTier,
399 checkpoint_interval: Option<Duration>,
401 audit_collector: PipelineAuditCollector,
403 last_checkpoint: Option<Checkpoint>,
405}
406
407impl BrickPipeline {
408 #[must_use]
410 pub fn new(name: impl Into<String>) -> Self {
411 Self {
412 name: name.into(),
413 stages: Vec::new(),
414 privacy_tier: PrivacyTier::Standard,
415 checkpoint_interval: None,
416 audit_collector: PipelineAuditCollector::new(),
417 last_checkpoint: None,
418 }
419 }
420
421 #[must_use]
423 pub fn stage<S: BrickStage + 'static>(mut self, stage: S) -> Self {
424 self.stages.push(Box::new(stage));
425 self
426 }
427
428 #[must_use]
430 pub fn with_privacy(mut self, tier: PrivacyTier) -> Self {
431 self.privacy_tier = tier;
432 self
433 }
434
435 #[must_use]
437 pub fn with_checkpointing(mut self, interval: Duration) -> Self {
438 self.checkpoint_interval = Some(interval);
439 self
440 }
441
442 pub fn run(&mut self, input: PipelineContext) -> PipelineResult<PipelineContext> {
444 let mut ctx = input;
445 ctx.metadata.started_at = Some(Instant::now());
446
447 let start_index = self
448 .last_checkpoint
449 .as_ref()
450 .map(|c| c.stage_index)
451 .unwrap_or(0);
452
453 if let Some(checkpoint) = &self.last_checkpoint {
455 ctx = checkpoint.context.clone();
456 }
457
458 let mut last_checkpoint_time = Instant::now();
459
460 for (i, stage) in self.stages.iter().enumerate().skip(start_index) {
461 let stage_name = stage.brick_name();
462
463 let validation = stage.validate(&ctx);
465 if !validation.valid {
466 let reason = validation
467 .messages
468 .iter()
469 .filter(|m| m.level == ValidationLevel::Error)
470 .map(|m| m.message.as_str())
471 .collect::<Vec<_>>()
472 .join("; ");
473
474 return Err(PipelineError::ValidationFailed {
475 stage: stage_name.to_string(),
476 reason,
477 });
478 }
479
480 let start = Instant::now();
482 let ctx_for_error = ctx.clone();
483 let result = stage.execute(ctx);
484 let duration = start.elapsed();
485
486 match result {
487 Ok(mut new_ctx) => {
488 new_ctx.add_trace(StageTrace {
489 stage_name: stage_name.to_string(),
490 duration,
491 success: true,
492 error: None,
493 });
494
495 self.audit_collector.record(stage_name, duration, true);
496
497 if let Some(interval) = self.checkpoint_interval {
499 if last_checkpoint_time.elapsed() >= interval {
500 self.last_checkpoint = Some(Checkpoint {
501 stage_index: i + 1,
502 context: new_ctx.clone(),
503 created_at: Instant::now(),
504 });
505 last_checkpoint_time = Instant::now();
506 }
507 }
508
509 ctx = new_ctx;
510 }
511 Err(e) => {
512 self.audit_collector.record(stage_name, duration, false);
513
514 let mut error_ctx = ctx_for_error;
516 error_ctx.add_trace(StageTrace {
517 stage_name: stage_name.to_string(),
518 duration,
519 success: false,
520 error: Some(e.to_string()),
521 });
522
523 return Err(PipelineError::ExecutionFailed {
524 stage: stage_name.to_string(),
525 reason: e.to_string(),
526 });
527 }
528 }
529 }
530
531 self.last_checkpoint = None;
533
534 Ok(ctx)
535 }
536
537 #[must_use]
539 pub fn name(&self) -> &str {
540 &self.name
541 }
542
543 #[must_use]
545 pub fn stage_count(&self) -> usize {
546 self.stages.len()
547 }
548
549 pub fn audit_trail(&self) -> &[AuditEntry] {
551 self.audit_collector.entries()
552 }
553
554 #[must_use]
556 pub fn privacy_tier(&self) -> PrivacyTier {
557 self.privacy_tier
558 }
559}
560
561impl Debug for BrickPipeline {
562 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
563 f.debug_struct("BrickPipeline")
564 .field("name", &self.name)
565 .field("stage_count", &self.stages.len())
566 .field("privacy_tier", &self.privacy_tier)
567 .finish()
568 }
569}
570
571fn uuid_v4() -> String {
573 use std::time::{SystemTime, UNIX_EPOCH};
574 let now = SystemTime::now()
575 .duration_since(UNIX_EPOCH)
576 .unwrap_or_default();
577 format!("{:x}{:x}", now.as_nanos(), std::process::id())
578}
579
580#[cfg(test)]
581#[allow(clippy::unwrap_used, clippy::expect_used)]
582mod tests {
583 use super::*;
584 use crate::brick::{BrickAssertion, BrickBudget, BrickVerification};
585
586 struct TestStage {
591 name: &'static str,
592 should_fail: bool,
593 }
594
595 impl Brick for TestStage {
596 fn brick_name(&self) -> &'static str {
597 self.name
598 }
599
600 fn assertions(&self) -> &[BrickAssertion] {
601 &[]
602 }
603
604 fn budget(&self) -> BrickBudget {
605 BrickBudget::uniform(100)
606 }
607
608 fn verify(&self) -> BrickVerification {
609 BrickVerification {
610 passed: vec![],
611 failed: vec![],
612 verification_time: Duration::from_micros(10),
613 }
614 }
615
616 fn to_html(&self) -> String {
617 String::new()
618 }
619
620 fn to_css(&self) -> String {
621 String::new()
622 }
623 }
624
625 impl BrickStage for TestStage {
626 fn execute(&self, mut ctx: PipelineContext) -> PipelineResult<PipelineContext> {
627 if self.should_fail {
628 return Err(PipelineError::ExecutionFailed {
629 stage: self.name.to_string(),
630 reason: "Test failure".into(),
631 });
632 }
633 ctx.set(
634 format!("{}_output", self.name),
635 PipelineData::Text("done".into()),
636 );
637 Ok(ctx)
638 }
639
640 fn validate(&self, _ctx: &PipelineContext) -> ValidationResult {
641 ValidationResult::ok()
642 }
643 }
644
645 struct FailingValidationStage {
647 name: &'static str,
648 }
649
650 impl Brick for FailingValidationStage {
651 fn brick_name(&self) -> &'static str {
652 self.name
653 }
654
655 fn assertions(&self) -> &[BrickAssertion] {
656 &[]
657 }
658
659 fn budget(&self) -> BrickBudget {
660 BrickBudget::uniform(100)
661 }
662
663 fn verify(&self) -> BrickVerification {
664 BrickVerification {
665 passed: vec![],
666 failed: vec![],
667 verification_time: Duration::from_micros(10),
668 }
669 }
670
671 fn to_html(&self) -> String {
672 String::new()
673 }
674
675 fn to_css(&self) -> String {
676 String::new()
677 }
678 }
679
680 impl BrickStage for FailingValidationStage {
681 fn execute(&self, ctx: PipelineContext) -> PipelineResult<PipelineContext> {
682 Ok(ctx)
683 }
684
685 fn validate(&self, _ctx: &PipelineContext) -> ValidationResult {
686 ValidationResult::fail("Validation error")
687 }
688 }
689
690 #[test]
695 fn test_pipeline_context_new() {
696 let ctx = PipelineContext::new();
697 assert!(ctx.data.is_empty());
698 assert!(ctx.trace.is_empty());
699 }
700
701 #[test]
702 fn test_pipeline_context_default() {
703 let ctx = PipelineContext::default();
704 assert!(ctx.data.is_empty());
705 }
706
707 #[test]
708 fn test_pipeline_context_from_input() {
709 let ctx = PipelineContext::from_input("input", PipelineData::Text("hello".into()));
710 assert!(ctx.get("input").is_some());
711 }
712
713 #[test]
714 fn test_pipeline_context() {
715 let mut ctx = PipelineContext::new();
716 ctx.set("test", PipelineData::Text("hello".into()));
717
718 assert!(ctx.get("test").is_some());
719 assert!(ctx.get("missing").is_none());
720 }
721
722 #[test]
723 fn test_pipeline_context_add_trace() {
724 let mut ctx = PipelineContext::new();
725 ctx.add_trace(StageTrace {
726 stage_name: "test".to_string(),
727 duration: Duration::from_millis(10),
728 success: true,
729 error: None,
730 });
731
732 assert_eq!(ctx.trace.len(), 1);
733 assert_eq!(ctx.trace[0].stage_name, "test");
734 }
735
736 #[test]
737 fn test_pipeline_context_clone() {
738 let mut ctx = PipelineContext::new();
739 ctx.set("key", PipelineData::Int(42));
740
741 let cloned = ctx.clone();
742 assert!(cloned.get("key").is_some());
743 }
744
745 #[test]
750 fn test_pipeline_data_tensor() {
751 let data = PipelineData::tensor(vec![1.0, 2.0, 3.0], vec![3]);
752
753 let (values, shape) = data.as_tensor().unwrap();
754 assert_eq!(values, &[1.0, 2.0, 3.0]);
755 assert_eq!(shape, &[3]);
756 }
757
758 #[test]
759 fn test_pipeline_data_as_tensor_none() {
760 let data = PipelineData::Text("hello".into());
761 assert!(data.as_tensor().is_none());
762 }
763
764 #[test]
765 fn test_pipeline_data_as_text() {
766 let data = PipelineData::Text("hello".into());
767 assert_eq!(data.as_text(), Some("hello"));
768 }
769
770 #[test]
771 fn test_pipeline_data_as_text_none() {
772 let data = PipelineData::Int(42);
773 assert!(data.as_text().is_none());
774 }
775
776 #[test]
777 fn test_pipeline_data_bytes() {
778 let data = PipelineData::Bytes(vec![1, 2, 3, 4]);
779 if let PipelineData::Bytes(bytes) = data {
780 assert_eq!(bytes, vec![1, 2, 3, 4]);
781 } else {
782 panic!("Expected Bytes variant");
783 }
784 }
785
786 #[test]
787 fn test_pipeline_data_json() {
788 let json = serde_json::json!({"key": "value"});
789 let data = PipelineData::Json(json.clone());
790 if let PipelineData::Json(value) = data {
791 assert_eq!(value, json);
792 } else {
793 panic!("Expected Json variant");
794 }
795 }
796
797 #[test]
798 fn test_pipeline_data_int() {
799 let data = PipelineData::Int(-42);
800 if let PipelineData::Int(val) = data {
801 assert_eq!(val, -42);
802 } else {
803 panic!("Expected Int variant");
804 }
805 }
806
807 #[test]
808 fn test_pipeline_data_bool() {
809 let data = PipelineData::Bool(true);
810 if let PipelineData::Bool(val) = data {
811 assert!(val);
812 } else {
813 panic!("Expected Bool variant");
814 }
815 }
816
817 #[test]
818 fn test_pipeline_data_clone_and_debug() {
819 let data = PipelineData::Text("test".into());
820 let cloned = data;
821 assert!(format!("{:?}", cloned).contains("Text"));
822 }
823
824 #[test]
829 fn test_pipeline_metadata_new() {
830 let meta = PipelineMetadata::new();
831 assert!(meta.run_id.starts_with("run-"));
832 assert!(meta.started_at.is_none());
833 assert!(meta.tags.is_empty());
834 }
835
836 #[test]
837 fn test_pipeline_metadata_default() {
838 let meta = PipelineMetadata::default();
839 assert!(meta.run_id.starts_with("run-"));
840 }
841
842 #[test]
843 fn test_pipeline_metadata_tag() {
844 let mut meta = PipelineMetadata::new();
845 meta.tag("env", "test");
846 meta.tag("version", "1.0");
847
848 assert_eq!(meta.tags.get("env"), Some(&"test".to_string()));
849 assert_eq!(meta.tags.get("version"), Some(&"1.0".to_string()));
850 }
851
852 #[test]
853 fn test_pipeline_metadata_clone_and_debug() {
854 let meta = PipelineMetadata::new();
855 let cloned = meta;
856 assert!(format!("{:?}", cloned).contains("PipelineMetadata"));
857 }
858
859 #[test]
864 fn test_stage_trace_clone_and_debug() {
865 let trace = StageTrace {
866 stage_name: "test".to_string(),
867 duration: Duration::from_millis(100),
868 success: true,
869 error: None,
870 };
871
872 let cloned = trace;
873 assert_eq!(cloned.stage_name, "test");
874 assert!(cloned.success);
875 assert!(format!("{:?}", cloned).contains("StageTrace"));
876 }
877
878 #[test]
879 fn test_stage_trace_with_error() {
880 let trace = StageTrace {
881 stage_name: "failed".to_string(),
882 duration: Duration::from_millis(50),
883 success: false,
884 error: Some("Something went wrong".to_string()),
885 };
886
887 assert!(!trace.success);
888 assert_eq!(trace.error, Some("Something went wrong".to_string()));
889 }
890
891 #[test]
896 fn test_privacy_tier_default() {
897 let tier = PrivacyTier::default();
898 assert_eq!(tier, PrivacyTier::Standard);
899 }
900
901 #[test]
902 fn test_privacy_tier_equality() {
903 assert_eq!(PrivacyTier::Sovereign, PrivacyTier::Sovereign);
904 assert_ne!(PrivacyTier::Sovereign, PrivacyTier::Private);
905 assert_ne!(PrivacyTier::Private, PrivacyTier::Standard);
906 }
907
908 #[test]
909 fn test_privacy_tier_debug_and_clone() {
910 let tier = PrivacyTier::Private;
911 let cloned = tier;
912 assert!(format!("{:?}", cloned).contains("Private"));
913 }
914
915 #[test]
920 fn test_validation_result_ok() {
921 let ok = ValidationResult::ok();
922 assert!(ok.valid);
923 assert!(ok.messages.is_empty());
924 }
925
926 #[test]
927 fn test_validation_result_fail() {
928 let fail = ValidationResult::fail("test error");
929 assert!(!fail.valid);
930 assert_eq!(fail.messages.len(), 1);
931 assert_eq!(fail.messages[0].level, ValidationLevel::Error);
932 assert_eq!(fail.messages[0].message, "test error");
933 }
934
935 #[test]
936 fn test_validation_result_warn() {
937 let mut result = ValidationResult::ok();
938 result.warn("warning message");
939
940 assert!(result.valid);
941 assert_eq!(result.messages.len(), 1);
942 assert_eq!(result.messages[0].level, ValidationLevel::Warning);
943 }
944
945 #[test]
946 fn test_validation_result_clone_and_debug() {
947 let result = ValidationResult::fail("error");
948 let cloned = result;
949 assert!(format!("{:?}", cloned).contains("ValidationResult"));
950 }
951
952 #[test]
957 fn test_validation_level_equality() {
958 assert_eq!(ValidationLevel::Info, ValidationLevel::Info);
959 assert_eq!(ValidationLevel::Warning, ValidationLevel::Warning);
960 assert_eq!(ValidationLevel::Error, ValidationLevel::Error);
961 assert_ne!(ValidationLevel::Info, ValidationLevel::Error);
962 }
963
964 #[test]
965 fn test_validation_level_debug_and_clone() {
966 let level = ValidationLevel::Warning;
967 let cloned = level;
968 assert!(format!("{:?}", cloned).contains("Warning"));
969 }
970
971 #[test]
976 fn test_validation_message_clone_and_debug() {
977 let msg = ValidationMessage {
978 level: ValidationLevel::Error,
979 message: "test".to_string(),
980 };
981
982 let cloned = msg;
983 assert_eq!(cloned.message, "test");
984 assert!(format!("{:?}", cloned).contains("ValidationMessage"));
985 }
986
987 #[test]
992 fn test_pipeline_error_validation_failed() {
993 let err = PipelineError::ValidationFailed {
994 stage: "test".to_string(),
995 reason: "bad input".to_string(),
996 };
997
998 let display = format!("{}", err);
999 assert!(display.contains("Validation failed"));
1000 assert!(display.contains("test"));
1001 assert!(display.contains("bad input"));
1002 }
1003
1004 #[test]
1005 fn test_pipeline_error_execution_failed() {
1006 let err = PipelineError::ExecutionFailed {
1007 stage: "compute".to_string(),
1008 reason: "timeout".to_string(),
1009 };
1010
1011 let display = format!("{}", err);
1012 assert!(display.contains("Execution failed"));
1013 assert!(display.contains("compute"));
1014 }
1015
1016 #[test]
1017 fn test_pipeline_error_missing_input() {
1018 let err = PipelineError::MissingInput {
1019 stage: "transform".to_string(),
1020 input: "data".to_string(),
1021 };
1022
1023 let display = format!("{}", err);
1024 assert!(display.contains("Missing input"));
1025 assert!(display.contains("data"));
1026 assert!(display.contains("transform"));
1027 }
1028
1029 #[test]
1030 fn test_pipeline_error_privacy_violation() {
1031 let err = PipelineError::PrivacyViolation {
1032 tier: PrivacyTier::Sovereign,
1033 reason: "external API call".to_string(),
1034 };
1035
1036 let display = format!("{}", err);
1037 assert!(display.contains("Privacy tier"));
1038 assert!(display.contains("Sovereign"));
1039 }
1040
1041 #[test]
1042 fn test_pipeline_error_checkpoint_failed() {
1043 let err = PipelineError::CheckpointFailed {
1044 reason: "disk full".to_string(),
1045 };
1046
1047 let display = format!("{}", err);
1048 assert!(display.contains("Checkpoint failed"));
1049 assert!(display.contains("disk full"));
1050 }
1051
1052 #[test]
1053 fn test_pipeline_error_brick_error() {
1054 let err = PipelineError::BrickError("brick error".to_string());
1055
1056 let display = format!("{}", err);
1057 assert!(display.contains("Brick error"));
1058 }
1059
1060 #[test]
1061 fn test_pipeline_error_from_brick_error() {
1062 use crate::brick::{BrickAssertion, BrickError};
1063 let brick_err = BrickError::AssertionFailed {
1064 assertion: BrickAssertion::ElementPresent("test".to_string()),
1065 reason: "failed".to_string(),
1066 };
1067
1068 let pipeline_err: PipelineError = brick_err.into();
1069 if let PipelineError::BrickError(msg) = pipeline_err {
1070 assert!(msg.contains("test"));
1071 } else {
1072 panic!("Expected BrickError variant");
1073 }
1074 }
1075
1076 #[test]
1077 fn test_pipeline_error_is_error_trait() {
1078 let err: Box<dyn std::error::Error> = Box::new(PipelineError::CheckpointFailed {
1079 reason: "test".to_string(),
1080 });
1081
1082 assert!(err.to_string().contains("Checkpoint"));
1083 }
1084
1085 #[test]
1090 fn test_audit_collector_new() {
1091 let collector = PipelineAuditCollector::new();
1092 assert!(collector.entries().is_empty());
1093 }
1094
1095 #[test]
1096 fn test_audit_collector_default() {
1097 let collector = PipelineAuditCollector::default();
1098 assert!(collector.entries().is_empty());
1099 }
1100
1101 #[test]
1102 fn test_audit_collector() {
1103 let mut collector = PipelineAuditCollector::new();
1104 collector.record("stage1", Duration::from_millis(100), true);
1105 collector.record("stage2", Duration::from_millis(50), true);
1106
1107 assert_eq!(collector.entries().len(), 2);
1108 assert_eq!(collector.total_duration(), Duration::from_millis(150));
1109 }
1110
1111 #[test]
1112 fn test_audit_collector_record_failure() {
1113 let mut collector = PipelineAuditCollector::new();
1114 collector.record("failed", Duration::from_millis(25), false);
1115
1116 assert_eq!(collector.entries().len(), 1);
1117 assert!(!collector.entries()[0].success);
1118 }
1119
1120 #[test]
1121 fn test_audit_collector_debug() {
1122 let collector = PipelineAuditCollector::new();
1123 assert!(format!("{:?}", collector).contains("PipelineAuditCollector"));
1124 }
1125
1126 #[test]
1131 fn test_audit_entry_clone_and_debug() {
1132 let entry = AuditEntry {
1133 stage: "test".to_string(),
1134 timestamp: Instant::now(),
1135 duration: Duration::from_millis(100),
1136 success: true,
1137 inputs: vec!["input1".to_string()],
1138 outputs: vec!["output1".to_string()],
1139 };
1140
1141 let cloned = entry;
1142 assert_eq!(cloned.stage, "test");
1143 assert!(format!("{:?}", cloned).contains("AuditEntry"));
1144 }
1145
1146 #[test]
1151 fn test_checkpoint_clone_and_debug() {
1152 let checkpoint = Checkpoint {
1153 stage_index: 2,
1154 context: PipelineContext::new(),
1155 created_at: Instant::now(),
1156 };
1157
1158 let cloned = checkpoint;
1159 assert_eq!(cloned.stage_index, 2);
1160 assert!(format!("{:?}", cloned).contains("Checkpoint"));
1161 }
1162
1163 #[test]
1168 fn test_pipeline_basic() {
1169 let mut pipeline = BrickPipeline::new("test")
1170 .stage(TestStage {
1171 name: "stage1",
1172 should_fail: false,
1173 })
1174 .stage(TestStage {
1175 name: "stage2",
1176 should_fail: false,
1177 });
1178
1179 let ctx = PipelineContext::new();
1180 let result = pipeline.run(ctx);
1181
1182 assert!(result.is_ok());
1183 let output = result.unwrap();
1184 assert!(output.get("stage1_output").is_some());
1185 assert!(output.get("stage2_output").is_some());
1186 }
1187
1188 #[test]
1189 fn test_pipeline_failure() {
1190 let mut pipeline = BrickPipeline::new("test")
1191 .stage(TestStage {
1192 name: "stage1",
1193 should_fail: false,
1194 })
1195 .stage(TestStage {
1196 name: "stage2",
1197 should_fail: true,
1198 });
1199
1200 let ctx = PipelineContext::new();
1201 let result = pipeline.run(ctx);
1202
1203 assert!(result.is_err());
1204 match result {
1205 Err(PipelineError::ExecutionFailed { stage, .. }) => {
1206 assert_eq!(stage, "stage2");
1207 }
1208 _ => panic!("Expected ExecutionFailed"),
1209 }
1210 }
1211
1212 #[test]
1213 fn test_pipeline_validation_failure() {
1214 let mut pipeline =
1215 BrickPipeline::new("test").stage(FailingValidationStage { name: "validator" });
1216
1217 let ctx = PipelineContext::new();
1218 let result = pipeline.run(ctx);
1219
1220 assert!(result.is_err());
1221 match result {
1222 Err(PipelineError::ValidationFailed { stage, reason }) => {
1223 assert_eq!(stage, "validator");
1224 assert!(reason.contains("Validation error"));
1225 }
1226 _ => panic!("Expected ValidationFailed"),
1227 }
1228 }
1229
1230 #[test]
1231 fn test_pipeline_privacy_tier() {
1232 let pipeline = BrickPipeline::new("test").with_privacy(PrivacyTier::Sovereign);
1233
1234 assert_eq!(pipeline.privacy_tier(), PrivacyTier::Sovereign);
1235 }
1236
1237 #[test]
1238 fn test_pipeline_name() {
1239 let pipeline = BrickPipeline::new("my-pipeline");
1240 assert_eq!(pipeline.name(), "my-pipeline");
1241 }
1242
1243 #[test]
1244 fn test_pipeline_stage_count() {
1245 let pipeline = BrickPipeline::new("test")
1246 .stage(TestStage {
1247 name: "s1",
1248 should_fail: false,
1249 })
1250 .stage(TestStage {
1251 name: "s2",
1252 should_fail: false,
1253 })
1254 .stage(TestStage {
1255 name: "s3",
1256 should_fail: false,
1257 });
1258
1259 assert_eq!(pipeline.stage_count(), 3);
1260 }
1261
1262 #[test]
1263 fn test_pipeline_empty() {
1264 let mut pipeline = BrickPipeline::new("empty");
1265
1266 let ctx = PipelineContext::new();
1267 let result = pipeline.run(ctx);
1268
1269 assert!(result.is_ok());
1270 }
1271
1272 #[test]
1273 fn test_pipeline_with_checkpointing() {
1274 let pipeline =
1275 BrickPipeline::new("checkpointed").with_checkpointing(Duration::from_secs(5));
1276
1277 assert_eq!(pipeline.name(), "checkpointed");
1279 }
1280
1281 #[test]
1282 fn test_pipeline_audit_trail() {
1283 let mut pipeline = BrickPipeline::new("audited")
1284 .stage(TestStage {
1285 name: "step1",
1286 should_fail: false,
1287 })
1288 .stage(TestStage {
1289 name: "step2",
1290 should_fail: false,
1291 });
1292
1293 let ctx = PipelineContext::new();
1294 let _ = pipeline.run(ctx);
1295
1296 let trail = pipeline.audit_trail();
1297 assert_eq!(trail.len(), 2);
1298 assert!(trail[0].success);
1299 assert!(trail[1].success);
1300 }
1301
1302 #[test]
1303 fn test_pipeline_audit_trail_with_failure() {
1304 let mut pipeline = BrickPipeline::new("audited")
1305 .stage(TestStage {
1306 name: "success",
1307 should_fail: false,
1308 })
1309 .stage(TestStage {
1310 name: "failure",
1311 should_fail: true,
1312 });
1313
1314 let ctx = PipelineContext::new();
1315 let _ = pipeline.run(ctx);
1316
1317 let trail = pipeline.audit_trail();
1318 assert_eq!(trail.len(), 2);
1319 assert!(trail[0].success);
1320 assert!(!trail[1].success);
1321 }
1322
1323 #[test]
1324 fn test_pipeline_debug() {
1325 let pipeline = BrickPipeline::new("debug-test")
1326 .with_privacy(PrivacyTier::Private)
1327 .stage(TestStage {
1328 name: "s1",
1329 should_fail: false,
1330 });
1331
1332 let debug_str = format!("{:?}", pipeline);
1333 assert!(debug_str.contains("BrickPipeline"));
1334 assert!(debug_str.contains("debug-test"));
1335 assert!(debug_str.contains("Private"));
1336 }
1337
1338 #[test]
1339 fn test_pipeline_context_metadata_started_at() {
1340 let mut pipeline = BrickPipeline::new("test").stage(TestStage {
1341 name: "s1",
1342 should_fail: false,
1343 });
1344
1345 let ctx = PipelineContext::new();
1346 let result = pipeline.run(ctx).unwrap();
1347
1348 assert!(result.metadata.started_at.is_some());
1349 }
1350
1351 #[test]
1352 fn test_pipeline_traces_recorded() {
1353 let mut pipeline = BrickPipeline::new("traced").stage(TestStage {
1354 name: "traced_stage",
1355 should_fail: false,
1356 });
1357
1358 let ctx = PipelineContext::new();
1359 let result = pipeline.run(ctx).unwrap();
1360
1361 assert_eq!(result.trace.len(), 1);
1362 assert_eq!(result.trace[0].stage_name, "traced_stage");
1363 assert!(result.trace[0].success);
1364 assert!(result.trace[0].error.is_none());
1365 }
1366
1367 #[test]
1372 fn test_brick_stage_default_required_inputs() {
1373 let stage = TestStage {
1374 name: "test",
1375 should_fail: false,
1376 };
1377
1378 assert!(stage.required_inputs().is_empty());
1379 }
1380
1381 #[test]
1382 fn test_brick_stage_default_output_names() {
1383 let stage = TestStage {
1384 name: "test",
1385 should_fail: false,
1386 };
1387
1388 assert!(stage.output_names().is_empty());
1389 }
1390
1391 #[test]
1396 fn test_uuid_generation() {
1397 let meta1 = PipelineMetadata::new();
1399 let meta2 = PipelineMetadata::new();
1400
1401 assert!(meta1.run_id.starts_with("run-"));
1403 assert!(meta2.run_id.starts_with("run-"));
1404 }
1405
1406 #[test]
1411 fn test_full_pipeline_workflow() {
1412 let mut pipeline = BrickPipeline::new("full-workflow")
1413 .with_privacy(PrivacyTier::Private)
1414 .stage(TestStage {
1415 name: "input",
1416 should_fail: false,
1417 })
1418 .stage(TestStage {
1419 name: "transform",
1420 should_fail: false,
1421 })
1422 .stage(TestStage {
1423 name: "output",
1424 should_fail: false,
1425 });
1426
1427 let ctx = PipelineContext::from_input("initial", PipelineData::Text("start".into()));
1428 let result = pipeline.run(ctx).unwrap();
1429
1430 assert!(result.get("input_output").is_some());
1432 assert!(result.get("transform_output").is_some());
1433 assert!(result.get("output_output").is_some());
1434
1435 assert_eq!(result.trace.len(), 3);
1437
1438 assert_eq!(pipeline.audit_trail().len(), 3);
1440 }
1441
1442 #[test]
1443 fn test_pipeline_with_tensor_data() {
1444 let mut pipeline = BrickPipeline::new("tensor-pipeline").stage(TestStage {
1445 name: "process",
1446 should_fail: false,
1447 });
1448
1449 let ctx = PipelineContext::from_input(
1450 "tensor",
1451 PipelineData::tensor(vec![1.0, 2.0, 3.0, 4.0], vec![2, 2]),
1452 );
1453
1454 let result = pipeline.run(ctx).unwrap();
1455
1456 let tensor = result.get("tensor").unwrap();
1458 let (data, shape) = tensor.as_tensor().unwrap();
1459 assert_eq!(data.len(), 4);
1460 assert_eq!(shape, &[2, 2]);
1461 }
1462
1463 struct SlowStage {
1469 name: &'static str,
1470 delay_ms: u64,
1471 }
1472
1473 impl Brick for SlowStage {
1474 fn brick_name(&self) -> &'static str {
1475 self.name
1476 }
1477
1478 fn assertions(&self) -> &[BrickAssertion] {
1479 &[]
1480 }
1481
1482 fn budget(&self) -> BrickBudget {
1483 BrickBudget::uniform(100)
1484 }
1485
1486 fn verify(&self) -> BrickVerification {
1487 BrickVerification {
1488 passed: vec![],
1489 failed: vec![],
1490 verification_time: Duration::from_micros(10),
1491 }
1492 }
1493
1494 fn to_html(&self) -> String {
1495 String::new()
1496 }
1497
1498 fn to_css(&self) -> String {
1499 String::new()
1500 }
1501 }
1502
1503 impl BrickStage for SlowStage {
1504 fn execute(&self, mut ctx: PipelineContext) -> PipelineResult<PipelineContext> {
1505 std::thread::sleep(Duration::from_millis(self.delay_ms));
1507 ctx.set(
1508 format!("{}_output", self.name),
1509 PipelineData::Text("slow done".into()),
1510 );
1511 Ok(ctx)
1512 }
1513
1514 fn validate(&self, _ctx: &PipelineContext) -> ValidationResult {
1515 ValidationResult::ok()
1516 }
1517 }
1518
1519 struct MultiErrorValidationStage {
1521 name: &'static str,
1522 }
1523
1524 impl Brick for MultiErrorValidationStage {
1525 fn brick_name(&self) -> &'static str {
1526 self.name
1527 }
1528
1529 fn assertions(&self) -> &[BrickAssertion] {
1530 &[]
1531 }
1532
1533 fn budget(&self) -> BrickBudget {
1534 BrickBudget::uniform(100)
1535 }
1536
1537 fn verify(&self) -> BrickVerification {
1538 BrickVerification {
1539 passed: vec![],
1540 failed: vec![],
1541 verification_time: Duration::from_micros(10),
1542 }
1543 }
1544
1545 fn to_html(&self) -> String {
1546 String::new()
1547 }
1548
1549 fn to_css(&self) -> String {
1550 String::new()
1551 }
1552 }
1553
1554 impl BrickStage for MultiErrorValidationStage {
1555 fn execute(&self, ctx: PipelineContext) -> PipelineResult<PipelineContext> {
1556 Ok(ctx)
1557 }
1558
1559 fn validate(&self, _ctx: &PipelineContext) -> ValidationResult {
1560 let mut result = ValidationResult {
1561 valid: false,
1562 messages: vec![
1563 ValidationMessage {
1564 level: ValidationLevel::Error,
1565 message: "First error".to_string(),
1566 },
1567 ValidationMessage {
1568 level: ValidationLevel::Error,
1569 message: "Second error".to_string(),
1570 },
1571 ValidationMessage {
1572 level: ValidationLevel::Warning,
1573 message: "A warning".to_string(),
1574 },
1575 ValidationMessage {
1576 level: ValidationLevel::Info,
1577 message: "Some info".to_string(),
1578 },
1579 ],
1580 };
1581 result.warn("Another warning");
1583 result
1584 }
1585 }
1586
1587 struct CustomIOStage {
1589 name: &'static str,
1590 inputs: &'static [&'static str],
1591 outputs: &'static [&'static str],
1592 }
1593
1594 impl Brick for CustomIOStage {
1595 fn brick_name(&self) -> &'static str {
1596 self.name
1597 }
1598
1599 fn assertions(&self) -> &[BrickAssertion] {
1600 &[]
1601 }
1602
1603 fn budget(&self) -> BrickBudget {
1604 BrickBudget::uniform(100)
1605 }
1606
1607 fn verify(&self) -> BrickVerification {
1608 BrickVerification {
1609 passed: vec![],
1610 failed: vec![],
1611 verification_time: Duration::from_micros(10),
1612 }
1613 }
1614
1615 fn to_html(&self) -> String {
1616 String::new()
1617 }
1618
1619 fn to_css(&self) -> String {
1620 String::new()
1621 }
1622 }
1623
1624 impl BrickStage for CustomIOStage {
1625 fn execute(&self, mut ctx: PipelineContext) -> PipelineResult<PipelineContext> {
1626 for output in self.outputs {
1627 ctx.set((*output).to_string(), PipelineData::Text("output".into()));
1628 }
1629 Ok(ctx)
1630 }
1631
1632 fn validate(&self, _ctx: &PipelineContext) -> ValidationResult {
1633 ValidationResult::ok()
1634 }
1635
1636 fn required_inputs(&self) -> &[&str] {
1637 self.inputs
1638 }
1639
1640 fn output_names(&self) -> &[&str] {
1641 self.outputs
1642 }
1643 }
1644
1645 #[test]
1646 fn test_pipeline_checkpointing_triggers() {
1647 let mut pipeline = BrickPipeline::new("checkpoint-test")
1649 .with_checkpointing(Duration::from_millis(1))
1650 .stage(SlowStage {
1651 name: "slow1",
1652 delay_ms: 5,
1653 })
1654 .stage(SlowStage {
1655 name: "slow2",
1656 delay_ms: 5,
1657 })
1658 .stage(SlowStage {
1659 name: "slow3",
1660 delay_ms: 5,
1661 });
1662
1663 let ctx = PipelineContext::new();
1664 let result = pipeline.run(ctx);
1665
1666 assert!(result.is_ok());
1667 let output = result.unwrap();
1668 assert!(output.get("slow1_output").is_some());
1669 assert!(output.get("slow2_output").is_some());
1670 assert!(output.get("slow3_output").is_some());
1671 }
1672
1673 #[test]
1674 fn test_pipeline_multi_error_validation() {
1675 let mut pipeline =
1676 BrickPipeline::new("multi-error").stage(MultiErrorValidationStage { name: "multi" });
1677
1678 let ctx = PipelineContext::new();
1679 let result = pipeline.run(ctx);
1680
1681 assert!(result.is_err());
1682 match result {
1683 Err(PipelineError::ValidationFailed { stage, reason }) => {
1684 assert_eq!(stage, "multi");
1685 assert!(reason.contains("First error"));
1687 assert!(reason.contains("Second error"));
1688 assert!(!reason.contains("warning"));
1690 assert!(!reason.contains("info"));
1691 }
1692 _ => panic!("Expected ValidationFailed"),
1693 }
1694 }
1695
1696 #[test]
1697 fn test_custom_io_stage_inputs_outputs() {
1698 let stage = CustomIOStage {
1699 name: "custom",
1700 inputs: &["input1", "input2"],
1701 outputs: &["output1", "output2"],
1702 };
1703
1704 assert_eq!(stage.required_inputs(), &["input1", "input2"]);
1705 assert_eq!(stage.output_names(), &["output1", "output2"]);
1706 }
1707
1708 #[test]
1709 fn test_pipeline_with_custom_io_stage() {
1710 let mut pipeline = BrickPipeline::new("custom-io").stage(CustomIOStage {
1711 name: "custom",
1712 inputs: &["in"],
1713 outputs: &["out1", "out2"],
1714 });
1715
1716 let ctx = PipelineContext::from_input("in", PipelineData::Text("input".into()));
1717 let result = pipeline.run(ctx).unwrap();
1718
1719 assert!(result.get("out1").is_some());
1720 assert!(result.get("out2").is_some());
1721 }
1722
1723 #[test]
1724 fn test_validation_level_info() {
1725 let msg = ValidationMessage {
1727 level: ValidationLevel::Info,
1728 message: "Informational message".to_string(),
1729 };
1730
1731 assert_eq!(msg.level, ValidationLevel::Info);
1732 assert!(format!("{:?}", msg.level).contains("Info"));
1733 }
1734
1735 #[test]
1736 fn test_pipeline_error_clone() {
1737 let err1 = PipelineError::ValidationFailed {
1739 stage: "s".to_string(),
1740 reason: "r".to_string(),
1741 };
1742 let cloned1 = err1;
1743 assert!(matches!(cloned1, PipelineError::ValidationFailed { .. }));
1744
1745 let err2 = PipelineError::ExecutionFailed {
1746 stage: "s".to_string(),
1747 reason: "r".to_string(),
1748 };
1749 let cloned2 = err2;
1750 assert!(matches!(cloned2, PipelineError::ExecutionFailed { .. }));
1751
1752 let err3 = PipelineError::MissingInput {
1753 stage: "s".to_string(),
1754 input: "i".to_string(),
1755 };
1756 let cloned3 = err3;
1757 assert!(matches!(cloned3, PipelineError::MissingInput { .. }));
1758
1759 let err4 = PipelineError::PrivacyViolation {
1760 tier: PrivacyTier::Sovereign,
1761 reason: "r".to_string(),
1762 };
1763 let cloned4 = err4;
1764 assert!(matches!(cloned4, PipelineError::PrivacyViolation { .. }));
1765
1766 let err5 = PipelineError::CheckpointFailed {
1767 reason: "r".to_string(),
1768 };
1769 let cloned5 = err5;
1770 assert!(matches!(cloned5, PipelineError::CheckpointFailed { .. }));
1771
1772 let err6 = PipelineError::BrickError("e".to_string());
1773 let cloned6 = err6;
1774 assert!(matches!(cloned6, PipelineError::BrickError(_)));
1775 }
1776
1777 #[test]
1778 fn test_pipeline_error_debug() {
1779 let err = PipelineError::ValidationFailed {
1780 stage: "test".to_string(),
1781 reason: "debug test".to_string(),
1782 };
1783 let debug_str = format!("{:?}", err);
1784 assert!(debug_str.contains("ValidationFailed"));
1785 }
1786
1787 #[test]
1788 fn test_validation_result_multiple_warnings() {
1789 let mut result = ValidationResult::ok();
1790 result.warn("warning 1");
1791 result.warn("warning 2");
1792 result.warn("warning 3");
1793
1794 assert!(result.valid);
1795 assert_eq!(result.messages.len(), 3);
1796 for msg in &result.messages {
1797 assert_eq!(msg.level, ValidationLevel::Warning);
1798 }
1799 }
1800
1801 #[test]
1802 fn test_pipeline_data_debug_variants() {
1803 let bytes = PipelineData::Bytes(vec![1, 2, 3]);
1805 assert!(format!("{:?}", bytes).contains("Bytes"));
1806
1807 let tensor = PipelineData::FloatTensor {
1808 data: vec![1.0],
1809 shape: vec![1],
1810 };
1811 assert!(format!("{:?}", tensor).contains("FloatTensor"));
1812
1813 let text = PipelineData::Text("hello".into());
1814 assert!(format!("{:?}", text).contains("Text"));
1815
1816 let json = PipelineData::Json(serde_json::json!({}));
1817 assert!(format!("{:?}", json).contains("Json"));
1818
1819 let int = PipelineData::Int(42);
1820 assert!(format!("{:?}", int).contains("Int"));
1821
1822 let boolean = PipelineData::Bool(false);
1823 assert!(format!("{:?}", boolean).contains("Bool"));
1824 }
1825
1826 #[test]
1827 fn test_pipeline_context_set_with_string() {
1828 let mut ctx = PipelineContext::new();
1829 ctx.set(String::from("key"), PipelineData::Int(123));
1831
1832 assert!(ctx.get("key").is_some());
1833 }
1834
1835 #[test]
1836 fn test_pipeline_metadata_tag_with_string() {
1837 let mut meta = PipelineMetadata::new();
1838 meta.tag(String::from("key"), String::from("value"));
1840
1841 assert_eq!(meta.tags.get("key"), Some(&"value".to_string()));
1842 }
1843
1844 #[test]
1845 fn test_audit_collector_total_duration_empty() {
1846 let collector = PipelineAuditCollector::new();
1847 assert_eq!(collector.total_duration(), Duration::ZERO);
1848 }
1849
1850 #[test]
1851 fn test_privacy_tier_copy() {
1852 let tier = PrivacyTier::Sovereign;
1853 let copied = tier;
1854 assert_eq!(tier, copied);
1855 assert_eq!(tier, PrivacyTier::Sovereign);
1856 }
1857
1858 #[test]
1859 fn test_stage_trace_error_field() {
1860 let trace = StageTrace {
1861 stage_name: "error_stage".to_string(),
1862 duration: Duration::from_secs(1),
1863 success: false,
1864 error: Some("error message".to_string()),
1865 };
1866
1867 assert_eq!(trace.error.as_deref(), Some("error message"));
1868 }
1869
1870 #[test]
1871 fn test_pipeline_run_clears_checkpoint_on_success() {
1872 let mut pipeline = BrickPipeline::new("clear-checkpoint")
1873 .with_checkpointing(Duration::from_millis(1))
1874 .stage(SlowStage {
1875 name: "slow",
1876 delay_ms: 5,
1877 });
1878
1879 let ctx = PipelineContext::new();
1880 let result = pipeline.run(ctx);
1881
1882 assert!(result.is_ok());
1883 let ctx2 = PipelineContext::new();
1886 let result2 = pipeline.run(ctx2);
1887 assert!(result2.is_ok());
1888 }
1889
1890 #[test]
1891 fn test_validation_message_levels() {
1892 let info = ValidationMessage {
1893 level: ValidationLevel::Info,
1894 message: "info".to_string(),
1895 };
1896 let warning = ValidationMessage {
1897 level: ValidationLevel::Warning,
1898 message: "warning".to_string(),
1899 };
1900 let error = ValidationMessage {
1901 level: ValidationLevel::Error,
1902 message: "error".to_string(),
1903 };
1904
1905 assert_ne!(info.level, warning.level);
1906 assert_ne!(warning.level, error.level);
1907 assert_ne!(info.level, error.level);
1908 }
1909
1910 #[test]
1911 fn test_pipeline_data_clone_all_variants() {
1912 let bytes = PipelineData::Bytes(vec![1, 2, 3]);
1913 let _ = bytes;
1914
1915 let tensor = PipelineData::FloatTensor {
1916 data: vec![1.0, 2.0],
1917 shape: vec![2],
1918 };
1919 let _ = tensor;
1920
1921 let text = PipelineData::Text("test".into());
1922 let _ = text;
1923
1924 let json = PipelineData::Json(serde_json::json!({"key": "value"}));
1925 let _ = json;
1926
1927 let int = PipelineData::Int(-100);
1928 let _ = int;
1929
1930 let boolean = PipelineData::Bool(true);
1931 let _ = boolean;
1932 }
1933
1934 #[test]
1935 fn test_pipeline_with_input_context() {
1936 let mut pipeline = BrickPipeline::new("with-input").stage(TestStage {
1937 name: "process",
1938 should_fail: false,
1939 });
1940
1941 let mut ctx = PipelineContext::new();
1943 ctx.set("input1", PipelineData::Text("value1".into()));
1944 ctx.set("input2", PipelineData::Int(42));
1945 ctx.metadata.tag("env", "test");
1946
1947 let result = pipeline.run(ctx).unwrap();
1948
1949 assert!(result.get("input1").is_some());
1951 assert!(result.get("input2").is_some());
1952 assert!(result.get("process_output").is_some());
1954 }
1955
1956 #[test]
1957 fn test_uuid_v4_generates_unique_ids() {
1958 let mut ids = std::collections::HashSet::new();
1960 for _ in 0..100 {
1961 let meta = PipelineMetadata::new();
1962 ids.insert(meta.run_id);
1963 }
1964 assert!(ids.len() >= 90);
1966 }
1967
1968 #[test]
1969 fn test_pipeline_debug_format_complete() {
1970 let pipeline = BrickPipeline::new("debug-complete")
1971 .with_privacy(PrivacyTier::Sovereign)
1972 .stage(TestStage {
1973 name: "s1",
1974 should_fail: false,
1975 })
1976 .stage(TestStage {
1977 name: "s2",
1978 should_fail: false,
1979 });
1980
1981 let debug_str = format!("{:?}", pipeline);
1982 assert!(debug_str.contains("BrickPipeline"));
1983 assert!(debug_str.contains("debug-complete"));
1984 assert!(debug_str.contains("stage_count"));
1985 assert!(debug_str.contains('2'));
1986 assert!(debug_str.contains("Sovereign"));
1987 }
1988
1989 #[test]
1990 fn test_checkpoint_fields() {
1991 let ctx = PipelineContext::from_input("test", PipelineData::Text("data".into()));
1992 let checkpoint = Checkpoint {
1993 stage_index: 5,
1994 context: ctx,
1995 created_at: Instant::now(),
1996 };
1997
1998 assert_eq!(checkpoint.stage_index, 5);
1999 assert!(checkpoint.context.get("test").is_some());
2000 }
2001
2002 #[test]
2003 fn test_audit_entry_fields() {
2004 let entry = AuditEntry {
2005 stage: "my_stage".to_string(),
2006 timestamp: Instant::now(),
2007 duration: Duration::from_millis(250),
2008 success: false,
2009 inputs: vec!["a".to_string(), "b".to_string()],
2010 outputs: vec!["c".to_string()],
2011 };
2012
2013 assert_eq!(entry.stage, "my_stage");
2014 assert_eq!(entry.duration, Duration::from_millis(250));
2015 assert!(!entry.success);
2016 assert_eq!(entry.inputs.len(), 2);
2017 assert_eq!(entry.outputs.len(), 1);
2018 }
2019
2020 #[test]
2021 fn test_pipeline_error_display_all_variants() {
2022 let errors = vec![
2024 PipelineError::ValidationFailed {
2025 stage: "stg".to_string(),
2026 reason: "rsn".to_string(),
2027 },
2028 PipelineError::ExecutionFailed {
2029 stage: "stg".to_string(),
2030 reason: "rsn".to_string(),
2031 },
2032 PipelineError::MissingInput {
2033 stage: "stg".to_string(),
2034 input: "inp".to_string(),
2035 },
2036 PipelineError::PrivacyViolation {
2037 tier: PrivacyTier::Private,
2038 reason: "rsn".to_string(),
2039 },
2040 PipelineError::CheckpointFailed {
2041 reason: "rsn".to_string(),
2042 },
2043 PipelineError::BrickError("err".to_string()),
2044 ];
2045
2046 for err in errors {
2047 let display = format!("{}", err);
2048 assert!(!display.is_empty());
2049 }
2050 }
2051
2052 #[test]
2053 fn test_pipeline_context_trace_with_error() {
2054 let mut ctx = PipelineContext::new();
2055 ctx.add_trace(StageTrace {
2056 stage_name: "failing".to_string(),
2057 duration: Duration::from_millis(50),
2058 success: false,
2059 error: Some("Detailed error message".to_string()),
2060 });
2061
2062 assert_eq!(ctx.trace.len(), 1);
2063 assert!(!ctx.trace[0].success);
2064 assert!(ctx.trace[0].error.is_some());
2065 assert!(ctx.trace[0]
2066 .error
2067 .as_ref()
2068 .unwrap()
2069 .contains("Detailed error"));
2070 }
2071
2072 struct CheckpointMarkerStage {
2074 name: &'static str,
2075 marker_value: &'static str,
2076 }
2077
2078 impl Brick for CheckpointMarkerStage {
2079 fn brick_name(&self) -> &'static str {
2080 self.name
2081 }
2082
2083 fn assertions(&self) -> &[BrickAssertion] {
2084 &[]
2085 }
2086
2087 fn budget(&self) -> BrickBudget {
2088 BrickBudget::uniform(100)
2089 }
2090
2091 fn verify(&self) -> BrickVerification {
2092 BrickVerification {
2093 passed: vec![],
2094 failed: vec![],
2095 verification_time: Duration::from_micros(10),
2096 }
2097 }
2098
2099 fn to_html(&self) -> String {
2100 String::new()
2101 }
2102
2103 fn to_css(&self) -> String {
2104 String::new()
2105 }
2106 }
2107
2108 impl BrickStage for CheckpointMarkerStage {
2109 fn execute(&self, mut ctx: PipelineContext) -> PipelineResult<PipelineContext> {
2110 ctx.set(
2111 format!("{}_marker", self.name),
2112 PipelineData::Text(self.marker_value.to_string()),
2113 );
2114 Ok(ctx)
2115 }
2116
2117 fn validate(&self, _ctx: &PipelineContext) -> ValidationResult {
2118 ValidationResult::ok()
2119 }
2120 }
2121
2122 #[test]
2123 fn test_pipeline_checkpoint_restoration() {
2124 let mut pipeline = BrickPipeline::new("checkpoint-restore-test")
2126 .with_checkpointing(Duration::from_nanos(1))
2127 .stage(SlowStage {
2128 name: "stage1",
2129 delay_ms: 2,
2130 })
2131 .stage(SlowStage {
2132 name: "stage2",
2133 delay_ms: 2,
2134 });
2135
2136 let ctx = PipelineContext::new();
2138 let result1 = pipeline.run(ctx);
2139 assert!(result1.is_ok());
2140
2141 let ctx2 = PipelineContext::new();
2145 let result2 = pipeline.run(ctx2);
2146 assert!(result2.is_ok());
2147 }
2148
2149 #[test]
2150 fn test_pipeline_start_index_from_checkpoint() {
2151 let mut pipeline = BrickPipeline::new("start-index-test")
2153 .stage(TestStage {
2154 name: "stage1",
2155 should_fail: false,
2156 })
2157 .stage(TestStage {
2158 name: "stage2",
2159 should_fail: false,
2160 })
2161 .stage(TestStage {
2162 name: "stage3",
2163 should_fail: false,
2164 });
2165
2166 let checkpoint_ctx = PipelineContext::from_input("checkpoint_data", PipelineData::Int(42));
2168 pipeline.last_checkpoint = Some(Checkpoint {
2169 stage_index: 1,
2170 context: checkpoint_ctx,
2171 created_at: Instant::now(),
2172 });
2173
2174 let fresh_ctx = PipelineContext::new();
2176 let result = pipeline.run(fresh_ctx).unwrap();
2177
2178 assert!(result.get("stage2_output").is_some());
2180 assert!(result.get("stage3_output").is_some());
2181 assert!(result.get("stage1_output").is_none());
2183 assert!(result.get("checkpoint_data").is_some());
2185 }
2186
2187 #[test]
2188 fn test_pipeline_checkpoint_context_restored() {
2189 let mut pipeline = BrickPipeline::new("context-restore-test")
2191 .stage(TestStage {
2192 name: "stage1",
2193 should_fail: false,
2194 })
2195 .stage(TestStage {
2196 name: "stage2",
2197 should_fail: false,
2198 });
2199
2200 let mut checkpoint_ctx = PipelineContext::new();
2202 checkpoint_ctx.set("restored_key", PipelineData::Text("restored_value".into()));
2203
2204 pipeline.last_checkpoint = Some(Checkpoint {
2205 stage_index: 0,
2206 context: checkpoint_ctx,
2207 created_at: Instant::now(),
2208 });
2209
2210 let input_ctx =
2212 PipelineContext::from_input("input_key", PipelineData::Text("input_value".into()));
2213 let result = pipeline.run(input_ctx).unwrap();
2214
2215 assert!(result.get("restored_key").is_some());
2217 assert!(result.get("input_key").is_none());
2219 }
2220
2221 #[test]
2222 fn test_multiple_checkpoints_during_run() {
2223 let mut pipeline = BrickPipeline::new("multi-checkpoint")
2225 .with_checkpointing(Duration::from_millis(1))
2226 .stage(SlowStage {
2227 name: "s1",
2228 delay_ms: 3,
2229 })
2230 .stage(SlowStage {
2231 name: "s2",
2232 delay_ms: 3,
2233 })
2234 .stage(SlowStage {
2235 name: "s3",
2236 delay_ms: 3,
2237 })
2238 .stage(SlowStage {
2239 name: "s4",
2240 delay_ms: 3,
2241 });
2242
2243 let ctx = PipelineContext::new();
2244 let result = pipeline.run(ctx);
2245
2246 assert!(result.is_ok());
2247 let output = result.unwrap();
2248 assert!(output.get("s1_output").is_some());
2249 assert!(output.get("s2_output").is_some());
2250 assert!(output.get("s3_output").is_some());
2251 assert!(output.get("s4_output").is_some());
2252 }
2253
2254 #[test]
2255 fn test_checkpoint_not_created_when_interval_not_exceeded() {
2256 let mut pipeline = BrickPipeline::new("no-checkpoint")
2258 .with_checkpointing(Duration::from_secs(3600)) .stage(TestStage {
2260 name: "fast1",
2261 should_fail: false,
2262 })
2263 .stage(TestStage {
2264 name: "fast2",
2265 should_fail: false,
2266 });
2267
2268 let ctx = PipelineContext::new();
2269 let result = pipeline.run(ctx);
2270
2271 assert!(result.is_ok());
2272 assert!(pipeline.last_checkpoint.is_none());
2274 }
2275
2276 #[test]
2277 fn test_all_privacy_tier_variants_in_debug() {
2278 let sovereign = PrivacyTier::Sovereign;
2280 let private = PrivacyTier::Private;
2281 let standard = PrivacyTier::Standard;
2282
2283 assert!(format!("{:?}", sovereign).contains("Sovereign"));
2284 assert!(format!("{:?}", private).contains("Private"));
2285 assert!(format!("{:?}", standard).contains("Standard"));
2286 }
2287
2288 #[test]
2289 fn test_pipeline_error_debug_all_variants() {
2290 let errors: Vec<PipelineError> = vec![
2292 PipelineError::ValidationFailed {
2293 stage: "s".to_string(),
2294 reason: "r".to_string(),
2295 },
2296 PipelineError::ExecutionFailed {
2297 stage: "s".to_string(),
2298 reason: "r".to_string(),
2299 },
2300 PipelineError::MissingInput {
2301 stage: "s".to_string(),
2302 input: "i".to_string(),
2303 },
2304 PipelineError::PrivacyViolation {
2305 tier: PrivacyTier::Sovereign,
2306 reason: "r".to_string(),
2307 },
2308 PipelineError::CheckpointFailed {
2309 reason: "r".to_string(),
2310 },
2311 PipelineError::BrickError("e".to_string()),
2312 ];
2313
2314 for err in errors {
2315 let debug_str = format!("{:?}", err);
2316 assert!(!debug_str.is_empty());
2317 }
2318 }
2319
2320 #[test]
2321 fn test_pipeline_run_with_zero_stages() {
2322 let mut pipeline = BrickPipeline::new("zero-stages");
2323
2324 let ctx = PipelineContext::from_input("data", PipelineData::Bool(true));
2325 let result = pipeline.run(ctx).unwrap();
2326
2327 assert!(result.get("data").is_some());
2329 assert!(result.metadata.started_at.is_some());
2331 }
2332
2333 #[test]
2334 fn test_validation_result_fail_with_different_messages() {
2335 let fail1 = ValidationResult::fail("error message");
2336 assert!(!fail1.valid);
2337 assert_eq!(fail1.messages.len(), 1);
2338
2339 let fail2 = ValidationResult::fail(String::from("string message"));
2340 assert!(!fail2.valid);
2341 assert_eq!(fail2.messages.len(), 1);
2342 }
2343
2344 #[test]
2345 fn test_pipeline_data_tensor_multidimensional() {
2346 let data =
2347 PipelineData::tensor(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], vec![2, 2, 2]);
2348
2349 let (values, shape) = data.as_tensor().unwrap();
2350 assert_eq!(values.len(), 8);
2351 assert_eq!(shape, &[2, 2, 2]);
2352 }
2353
2354 #[test]
2355 fn test_audit_collector_records_multiple() {
2356 let mut collector = PipelineAuditCollector::new();
2357
2358 collector.record("stage1", Duration::from_millis(10), true);
2359 collector.record("stage2", Duration::from_millis(20), true);
2360 collector.record("stage3", Duration::from_millis(30), false);
2361 collector.record("stage4", Duration::from_millis(40), true);
2362
2363 assert_eq!(collector.entries().len(), 4);
2364 assert_eq!(collector.total_duration(), Duration::from_millis(100));
2365
2366 assert_eq!(collector.entries()[0].stage, "stage1");
2368 assert!(collector.entries()[0].success);
2369 assert!(!collector.entries()[2].success);
2370 }
2371
2372 #[test]
2377 fn test_checkpoint_marker_stage_execute() {
2378 let stage = CheckpointMarkerStage {
2380 name: "marker",
2381 marker_value: "test_marker",
2382 };
2383
2384 let ctx = PipelineContext::new();
2385 let result = stage.execute(ctx).unwrap();
2386
2387 assert!(result.get("marker_marker").is_some());
2388 if let Some(PipelineData::Text(value)) = result.get("marker_marker") {
2389 assert_eq!(value, "test_marker");
2390 } else {
2391 panic!("Expected Text variant");
2392 }
2393 }
2394
2395 #[test]
2396 fn test_checkpoint_marker_stage_validate() {
2397 let stage = CheckpointMarkerStage {
2398 name: "marker",
2399 marker_value: "val",
2400 };
2401
2402 let ctx = PipelineContext::new();
2403 let validation = stage.validate(&ctx);
2404
2405 assert!(validation.valid);
2406 }
2407
2408 #[test]
2409 fn test_checkpoint_marker_stage_brick_impl() {
2410 let stage = CheckpointMarkerStage {
2411 name: "test_marker",
2412 marker_value: "v",
2413 };
2414
2415 assert_eq!(stage.brick_name(), "test_marker");
2416 assert!(stage.assertions().is_empty());
2417 assert!(stage.to_html().is_empty());
2418 assert!(stage.to_css().is_empty());
2419
2420 let budget = stage.budget();
2421 assert_eq!(budget.total_ms, 100);
2422
2423 let verify = stage.verify();
2424 assert!(verify.passed.is_empty());
2425 assert!(verify.failed.is_empty());
2426 }
2427
2428 #[test]
2429 fn test_slow_stage_brick_impl() {
2430 let stage = SlowStage {
2431 name: "slow_test",
2432 delay_ms: 1,
2433 };
2434
2435 assert_eq!(stage.brick_name(), "slow_test");
2436 assert!(stage.assertions().is_empty());
2437 assert!(stage.to_html().is_empty());
2438 assert!(stage.to_css().is_empty());
2439
2440 let budget = stage.budget();
2441 assert_eq!(budget.total_ms, 100);
2442
2443 let verify = stage.verify();
2444 assert!(verify.passed.is_empty());
2445 }
2446
2447 #[test]
2448 fn test_slow_stage_validate() {
2449 let stage = SlowStage {
2450 name: "slow",
2451 delay_ms: 1,
2452 };
2453
2454 let ctx = PipelineContext::new();
2455 let validation = stage.validate(&ctx);
2456
2457 assert!(validation.valid);
2458 }
2459
2460 #[test]
2461 fn test_multi_error_validation_stage_brick_impl() {
2462 let stage = MultiErrorValidationStage { name: "multi_err" };
2463
2464 assert_eq!(stage.brick_name(), "multi_err");
2465 assert!(stage.assertions().is_empty());
2466 assert!(stage.to_html().is_empty());
2467 assert!(stage.to_css().is_empty());
2468
2469 let budget = stage.budget();
2470 assert_eq!(budget.total_ms, 100);
2471
2472 let verify = stage.verify();
2473 assert!(verify.passed.is_empty());
2474 }
2475
2476 #[test]
2477 fn test_multi_error_validation_stage_execute() {
2478 let stage = MultiErrorValidationStage { name: "multi" };
2479
2480 let ctx = PipelineContext::new();
2481 let result = stage.execute(ctx);
2482
2483 assert!(result.is_ok());
2485 }
2486
2487 #[test]
2488 fn test_custom_io_stage_brick_impl() {
2489 let stage = CustomIOStage {
2490 name: "custom_io",
2491 inputs: &["a"],
2492 outputs: &["b"],
2493 };
2494
2495 assert_eq!(stage.brick_name(), "custom_io");
2496 assert!(stage.assertions().is_empty());
2497 assert!(stage.to_html().is_empty());
2498 assert!(stage.to_css().is_empty());
2499
2500 let budget = stage.budget();
2501 assert_eq!(budget.total_ms, 100);
2502
2503 let verify = stage.verify();
2504 assert!(verify.passed.is_empty());
2505 }
2506
2507 #[test]
2508 fn test_custom_io_stage_validate() {
2509 let stage = CustomIOStage {
2510 name: "custom",
2511 inputs: &[],
2512 outputs: &[],
2513 };
2514
2515 let ctx = PipelineContext::new();
2516 let validation = stage.validate(&ctx);
2517
2518 assert!(validation.valid);
2519 }
2520
2521 #[test]
2522 fn test_failing_validation_stage_brick_impl() {
2523 let stage = FailingValidationStage { name: "fail_val" };
2524
2525 assert_eq!(stage.brick_name(), "fail_val");
2526 assert!(stage.assertions().is_empty());
2527 assert!(stage.to_html().is_empty());
2528 assert!(stage.to_css().is_empty());
2529
2530 let budget = stage.budget();
2531 assert_eq!(budget.total_ms, 100);
2532
2533 let verify = stage.verify();
2534 assert!(verify.passed.is_empty());
2535 }
2536
2537 #[test]
2538 fn test_test_stage_brick_impl_full() {
2539 let stage = TestStage {
2540 name: "test_brick",
2541 should_fail: false,
2542 };
2543
2544 assert_eq!(stage.brick_name(), "test_brick");
2545 assert!(stage.assertions().is_empty());
2546 assert!(stage.to_html().is_empty());
2547 assert!(stage.to_css().is_empty());
2548
2549 let budget = stage.budget();
2550 assert_eq!(budget.total_ms, 100);
2551
2552 let verify = stage.verify();
2553 assert!(verify.passed.is_empty());
2554 assert!(verify.failed.is_empty());
2555 }
2556
2557 #[test]
2558 fn test_pipeline_failure_records_trace() {
2559 let mut pipeline = BrickPipeline::new("failure-trace")
2560 .stage(TestStage {
2561 name: "success_stage",
2562 should_fail: false,
2563 })
2564 .stage(TestStage {
2565 name: "fail_stage",
2566 should_fail: true,
2567 });
2568
2569 let ctx = PipelineContext::new();
2570 let result = pipeline.run(ctx);
2571
2572 assert!(result.is_err());
2573
2574 let trail = pipeline.audit_trail();
2576 assert_eq!(trail.len(), 2);
2577 assert!(trail[0].success);
2578 assert!(!trail[1].success);
2579 }
2580
2581 #[test]
2582 fn test_pipeline_data_all_variants_as_methods() {
2583 let bytes = PipelineData::Bytes(vec![1, 2]);
2585 assert!(bytes.as_tensor().is_none());
2586 assert!(bytes.as_text().is_none());
2587
2588 let json = PipelineData::Json(serde_json::json!({}));
2589 assert!(json.as_tensor().is_none());
2590 assert!(json.as_text().is_none());
2591
2592 let int = PipelineData::Int(42);
2593 assert!(int.as_tensor().is_none());
2594 assert!(int.as_text().is_none());
2595
2596 let boolean = PipelineData::Bool(true);
2597 assert!(boolean.as_tensor().is_none());
2598 assert!(boolean.as_text().is_none());
2599 }
2600
2601 #[test]
2602 fn test_pipeline_with_checkpoint_marker_stage() {
2603 let mut pipeline = BrickPipeline::new("marker-pipeline")
2604 .with_checkpointing(Duration::from_millis(1))
2605 .stage(CheckpointMarkerStage {
2606 name: "mark1",
2607 marker_value: "first",
2608 })
2609 .stage(SlowStage {
2610 name: "slow",
2611 delay_ms: 5,
2612 })
2613 .stage(CheckpointMarkerStage {
2614 name: "mark2",
2615 marker_value: "second",
2616 });
2617
2618 let ctx = PipelineContext::new();
2619 let result = pipeline.run(ctx).unwrap();
2620
2621 assert!(result.get("mark1_marker").is_some());
2622 assert!(result.get("mark2_marker").is_some());
2623 assert!(result.get("slow_output").is_some());
2624 }
2625
2626 #[test]
2627 fn test_pipeline_error_from_brick_error_explicit() {
2628 use crate::brick::BrickError;
2629
2630 let brick_err = BrickError::MissingChild {
2631 expected: "child_brick".to_string(),
2632 };
2633 let pipeline_err = PipelineError::from(brick_err);
2634
2635 match pipeline_err {
2636 PipelineError::BrickError(msg) => {
2637 assert!(msg.contains("child_brick"));
2638 }
2639 _ => panic!("Expected BrickError variant"),
2640 }
2641 }
2642
2643 #[test]
2644 fn test_pipeline_context_multiple_traces() {
2645 let mut ctx = PipelineContext::new();
2646
2647 for i in 0..5 {
2648 ctx.add_trace(StageTrace {
2649 stage_name: format!("stage_{}", i),
2650 duration: Duration::from_millis(10 * i as u64),
2651 success: i % 2 == 0,
2652 error: if i % 2 == 1 {
2653 Some(format!("Error at stage {}", i))
2654 } else {
2655 None
2656 },
2657 });
2658 }
2659
2660 assert_eq!(ctx.trace.len(), 5);
2661 assert!(ctx.trace[0].success);
2662 assert!(!ctx.trace[1].success);
2663 assert!(ctx.trace[1].error.is_some());
2664 }
2665
2666 #[test]
2667 fn test_validation_result_with_info_level() {
2668 let result = ValidationResult {
2669 valid: true,
2670 messages: vec![ValidationMessage {
2671 level: ValidationLevel::Info,
2672 message: "Just some info".to_string(),
2673 }],
2674 };
2675
2676 assert!(result.valid);
2677 assert_eq!(result.messages.len(), 1);
2678 assert_eq!(result.messages[0].level, ValidationLevel::Info);
2679 }
2680
2681 #[test]
2682 fn test_pipeline_metadata_multiple_tags() {
2683 let mut meta = PipelineMetadata::new();
2684
2685 meta.tag("key1", "value1");
2686 meta.tag("key2", "value2");
2687 meta.tag("key3", "value3");
2688 meta.tag("key1", "new_value1");
2690
2691 assert_eq!(meta.tags.len(), 3);
2692 assert_eq!(meta.tags.get("key1"), Some(&"new_value1".to_string()));
2693 }
2694
2695 #[test]
2696 fn test_pipeline_context_get_nonexistent() {
2697 let ctx = PipelineContext::new();
2698
2699 assert!(ctx.get("nonexistent").is_none());
2700 assert!(ctx.get("").is_none());
2701 assert!(ctx.get("some_key").is_none());
2702 }
2703
2704 #[test]
2705 fn test_pipeline_data_empty_tensor() {
2706 let data = PipelineData::tensor(vec![], vec![0]);
2707
2708 let (values, shape) = data.as_tensor().unwrap();
2709 assert!(values.is_empty());
2710 assert_eq!(shape, &[0]);
2711 }
2712
2713 #[test]
2714 fn test_pipeline_data_empty_text() {
2715 let data = PipelineData::Text(String::new());
2716
2717 assert_eq!(data.as_text(), Some(""));
2718 }
2719
2720 #[test]
2721 fn test_audit_entry_with_empty_io() {
2722 let entry = AuditEntry {
2723 stage: "empty_io".to_string(),
2724 timestamp: Instant::now(),
2725 duration: Duration::from_nanos(1),
2726 success: true,
2727 inputs: Vec::new(),
2728 outputs: Vec::new(),
2729 };
2730
2731 assert!(entry.inputs.is_empty());
2732 assert!(entry.outputs.is_empty());
2733 }
2734
2735 #[test]
2736 fn test_checkpoint_with_empty_context() {
2737 let checkpoint = Checkpoint {
2738 stage_index: 0,
2739 context: PipelineContext::new(),
2740 created_at: Instant::now(),
2741 };
2742
2743 assert_eq!(checkpoint.stage_index, 0);
2744 assert!(checkpoint.context.data.is_empty());
2745 }
2746
2747 #[test]
2748 fn test_pipeline_run_single_stage() {
2749 let mut pipeline = BrickPipeline::new("single").stage(TestStage {
2750 name: "only",
2751 should_fail: false,
2752 });
2753
2754 let ctx = PipelineContext::new();
2755 let result = pipeline.run(ctx).unwrap();
2756
2757 assert!(result.get("only_output").is_some());
2758 assert_eq!(result.trace.len(), 1);
2759 }
2760
2761 #[test]
2762 fn test_pipeline_first_stage_fails() {
2763 let mut pipeline = BrickPipeline::new("first-fail").stage(TestStage {
2764 name: "first",
2765 should_fail: true,
2766 });
2767
2768 let ctx = PipelineContext::new();
2769 let result = pipeline.run(ctx);
2770
2771 assert!(result.is_err());
2772 match result {
2773 Err(PipelineError::ExecutionFailed { stage, .. }) => {
2774 assert_eq!(stage, "first");
2775 }
2776 _ => panic!("Expected ExecutionFailed"),
2777 }
2778 }
2779
2780 #[test]
2781 fn test_pipeline_first_stage_validation_fails() {
2782 let mut pipeline = BrickPipeline::new("first-val-fail")
2783 .stage(FailingValidationStage { name: "first_fail" });
2784
2785 let ctx = PipelineContext::new();
2786 let result = pipeline.run(ctx);
2787
2788 assert!(result.is_err());
2789 match result {
2790 Err(PipelineError::ValidationFailed { stage, .. }) => {
2791 assert_eq!(stage, "first_fail");
2792 }
2793 _ => panic!("Expected ValidationFailed"),
2794 }
2795 }
2796
2797 #[test]
2798 fn test_pipeline_checkpoint_skip_first_stage() {
2799 let mut pipeline = BrickPipeline::new("skip-first")
2800 .stage(TestStage {
2801 name: "skipped",
2802 should_fail: false,
2803 })
2804 .stage(TestStage {
2805 name: "executed",
2806 should_fail: false,
2807 });
2808
2809 pipeline.last_checkpoint = Some(Checkpoint {
2811 stage_index: 1,
2812 context: PipelineContext::from_input("from_checkpoint", PipelineData::Bool(true)),
2813 created_at: Instant::now(),
2814 });
2815
2816 let ctx = PipelineContext::new();
2817 let result = pipeline.run(ctx).unwrap();
2818
2819 assert!(result.get("skipped_output").is_none());
2821 assert!(result.get("executed_output").is_some());
2823 assert!(result.get("from_checkpoint").is_some());
2825 }
2826
2827 #[test]
2828 fn test_pipeline_all_stages_skipped_by_checkpoint() {
2829 let mut pipeline = BrickPipeline::new("all-skipped")
2830 .stage(TestStage {
2831 name: "s1",
2832 should_fail: false,
2833 })
2834 .stage(TestStage {
2835 name: "s2",
2836 should_fail: false,
2837 });
2838
2839 pipeline.last_checkpoint = Some(Checkpoint {
2841 stage_index: 2, context: PipelineContext::from_input("final_data", PipelineData::Int(999)),
2843 created_at: Instant::now(),
2844 });
2845
2846 let ctx = PipelineContext::new();
2847 let result = pipeline.run(ctx).unwrap();
2848
2849 assert!(result.get("s1_output").is_none());
2851 assert!(result.get("s2_output").is_none());
2852 assert!(result.get("final_data").is_some());
2854 }
2855
2856 #[test]
2857 fn test_pipeline_with_many_stages() {
2858 let mut pipeline = BrickPipeline::new("many-stages");
2859
2860 for i in 0..10 {
2861 pipeline = pipeline.stage(TestStage {
2862 name: Box::leak(format!("stage_{}", i).into_boxed_str()),
2863 should_fail: false,
2864 });
2865 }
2866
2867 assert_eq!(pipeline.stage_count(), 10);
2868
2869 let ctx = PipelineContext::new();
2870 let result = pipeline.run(ctx).unwrap();
2871
2872 assert_eq!(result.trace.len(), 10);
2873 }
2874
2875 #[test]
2876 fn test_pipeline_error_std_error_trait() {
2877 let err = PipelineError::MissingInput {
2878 stage: "s".to_string(),
2879 input: "i".to_string(),
2880 };
2881
2882 fn accepts_error<E: std::error::Error>(_e: &E) {}
2884 accepts_error(&err);
2885
2886 assert!(std::error::Error::source(&err).is_none());
2888 }
2889
2890 #[test]
2891 fn test_pipeline_context_set_overwrite() {
2892 let mut ctx = PipelineContext::new();
2893
2894 ctx.set("key", PipelineData::Int(1));
2895 assert!(matches!(ctx.get("key"), Some(PipelineData::Int(1))));
2896
2897 ctx.set("key", PipelineData::Int(2));
2898 assert!(matches!(ctx.get("key"), Some(PipelineData::Int(2))));
2899
2900 ctx.set("key", PipelineData::Text("text".into()));
2901 assert!(matches!(ctx.get("key"), Some(PipelineData::Text(_))));
2902 }
2903
2904 #[test]
2905 fn test_stage_trace_zero_duration() {
2906 let trace = StageTrace {
2907 stage_name: "instant".to_string(),
2908 duration: Duration::ZERO,
2909 success: true,
2910 error: None,
2911 };
2912
2913 assert_eq!(trace.duration, Duration::ZERO);
2914 }
2915
2916 #[test]
2917 fn test_pipeline_with_privacy_and_checkpointing() {
2918 let pipeline = BrickPipeline::new("full-config")
2919 .with_privacy(PrivacyTier::Sovereign)
2920 .with_checkpointing(Duration::from_secs(10))
2921 .stage(TestStage {
2922 name: "s1",
2923 should_fail: false,
2924 });
2925
2926 assert_eq!(pipeline.privacy_tier(), PrivacyTier::Sovereign);
2927 assert_eq!(pipeline.stage_count(), 1);
2928 }
2929
2930 #[test]
2931 fn test_pipeline_json_data_complex() {
2932 let complex_json = serde_json::json!({
2933 "array": [1, 2, 3],
2934 "nested": {
2935 "key": "value",
2936 "number": 42
2937 },
2938 "boolean": true,
2939 "null_value": null
2940 });
2941
2942 let data = PipelineData::Json(complex_json);
2943
2944 if let PipelineData::Json(value) = data {
2945 assert_eq!(value["array"][0], 1);
2946 assert_eq!(value["nested"]["key"], "value");
2947 } else {
2948 panic!("Expected Json variant");
2949 }
2950 }
2951
2952 #[test]
2953 fn test_pipeline_bytes_large() {
2954 let large_bytes: Vec<u8> = (0..=255).collect();
2955 let data = PipelineData::Bytes(large_bytes);
2956
2957 if let PipelineData::Bytes(bytes) = data {
2958 assert_eq!(bytes.len(), 256);
2959 assert_eq!(bytes[0], 0);
2960 assert_eq!(bytes[255], 255);
2961 } else {
2962 panic!("Expected Bytes variant");
2963 }
2964 }
2965
2966 #[test]
2967 fn test_validation_result_fail_empty_reason() {
2968 let result = ValidationResult::fail("");
2969
2970 assert!(!result.valid);
2971 assert_eq!(result.messages[0].message, "");
2972 }
2973
2974 #[test]
2975 fn test_pipeline_context_from_input_preserves_metadata() {
2976 let ctx = PipelineContext::from_input("key", PipelineData::Bool(false));
2977
2978 assert!(ctx.metadata.run_id.starts_with("run-"));
2979 assert!(ctx.trace.is_empty());
2980 }
2981
2982 #[test]
2983 fn test_pipeline_stage_middle_fails() {
2984 let mut pipeline = BrickPipeline::new("middle-fail")
2985 .stage(TestStage {
2986 name: "first",
2987 should_fail: false,
2988 })
2989 .stage(TestStage {
2990 name: "middle",
2991 should_fail: true,
2992 })
2993 .stage(TestStage {
2994 name: "last",
2995 should_fail: false,
2996 });
2997
2998 let ctx = PipelineContext::new();
2999 let result = pipeline.run(ctx);
3000
3001 assert!(result.is_err());
3002
3003 let trail = pipeline.audit_trail();
3005 assert_eq!(trail.len(), 2);
3006 }
3007
3008 #[test]
3009 fn test_pipeline_stage_last_fails() {
3010 let mut pipeline = BrickPipeline::new("last-fail")
3011 .stage(TestStage {
3012 name: "first",
3013 should_fail: false,
3014 })
3015 .stage(TestStage {
3016 name: "second",
3017 should_fail: false,
3018 })
3019 .stage(TestStage {
3020 name: "last",
3021 should_fail: true,
3022 });
3023
3024 let ctx = PipelineContext::new();
3025 let result = pipeline.run(ctx);
3026
3027 assert!(result.is_err());
3028
3029 let trail = pipeline.audit_trail();
3030 assert_eq!(trail.len(), 3);
3031 assert!(trail[0].success);
3032 assert!(trail[1].success);
3033 assert!(!trail[2].success);
3034 }
3035
3036 #[test]
3037 fn test_pipeline_checkpoint_at_exact_interval() {
3038 let mut pipeline = BrickPipeline::new("exact-interval")
3040 .with_checkpointing(Duration::from_millis(0)) .stage(TestStage {
3042 name: "s1",
3043 should_fail: false,
3044 })
3045 .stage(TestStage {
3046 name: "s2",
3047 should_fail: false,
3048 });
3049
3050 let ctx = PipelineContext::new();
3051 let result = pipeline.run(ctx);
3052
3053 assert!(result.is_ok());
3054 assert!(pipeline.last_checkpoint.is_none());
3056 }
3057
3058 #[test]
3059 fn test_validation_level_copy_and_clone() {
3060 let info = ValidationLevel::Info;
3061 let copied = info;
3062 let cloned = copied;
3063
3064 assert_eq!(info, copied);
3065 assert_eq!(copied, cloned);
3066 }
3067
3068 #[test]
3069 fn test_privacy_tier_all_variants_equality() {
3070 let tiers = [
3071 PrivacyTier::Sovereign,
3072 PrivacyTier::Private,
3073 PrivacyTier::Standard,
3074 ];
3075
3076 for (i, tier1) in tiers.iter().enumerate() {
3077 for (j, tier2) in tiers.iter().enumerate() {
3078 if i == j {
3079 assert_eq!(tier1, tier2);
3080 } else {
3081 assert_ne!(tier1, tier2);
3082 }
3083 }
3084 }
3085 }
3086
3087 #[test]
3088 fn test_pipeline_metadata_started_at_is_set() {
3089 let mut pipeline = BrickPipeline::new("started").stage(TestStage {
3090 name: "s",
3091 should_fail: false,
3092 });
3093
3094 let ctx = PipelineContext::new();
3095 assert!(ctx.metadata.started_at.is_none());
3096
3097 let result = pipeline.run(ctx).unwrap();
3098 assert!(result.metadata.started_at.is_some());
3099 }
3100
3101 #[test]
3102 fn test_pipeline_tensor_high_dimensional() {
3103 let data = PipelineData::tensor(
3104 vec![1.0; 24], vec![2, 3, 4],
3106 );
3107
3108 let (values, shape) = data.as_tensor().unwrap();
3109 assert_eq!(values.len(), 24);
3110 assert_eq!(shape.len(), 3);
3111 }
3112
3113 #[test]
3114 fn test_pipeline_context_debug() {
3115 let ctx = PipelineContext::from_input("debug_key", PipelineData::Int(42));
3116 let debug_str = format!("{:?}", ctx);
3117
3118 assert!(debug_str.contains("PipelineContext"));
3119 assert!(debug_str.contains("debug_key"));
3120 }
3121
3122 #[test]
3123 fn test_stage_trace_long_error_message() {
3124 let long_error = "Error ".repeat(1000);
3125 let trace = StageTrace {
3126 stage_name: "long_error".to_string(),
3127 duration: Duration::from_millis(1),
3128 success: false,
3129 error: Some(long_error.clone()),
3130 };
3131
3132 assert_eq!(trace.error.as_ref().unwrap().len(), long_error.len());
3133 }
3134
3135 #[test]
3136 fn test_pipeline_with_checkpoint_and_failure() {
3137 let mut pipeline = BrickPipeline::new("checkpoint-fail")
3138 .with_checkpointing(Duration::from_nanos(1))
3139 .stage(SlowStage {
3140 name: "slow",
3141 delay_ms: 2,
3142 })
3143 .stage(TestStage {
3144 name: "fail",
3145 should_fail: true,
3146 });
3147
3148 let ctx = PipelineContext::new();
3149 let result = pipeline.run(ctx);
3150
3151 assert!(result.is_err());
3152 }
3153
3154 #[test]
3155 fn test_audit_collector_single_entry_duration() {
3156 let mut collector = PipelineAuditCollector::new();
3157 collector.record("single", Duration::from_secs(5), true);
3158
3159 assert_eq!(collector.total_duration(), Duration::from_secs(5));
3160 }
3161}