#[test]
fn test_violation_type_display() {
assert_eq!(ViolationType::CommandFailure.to_string(), "command_failure");
assert_eq!(ViolationType::TimingAnomaly.to_string(), "timing_anomaly");
assert_eq!(ViolationType::TrustViolation.to_string(), "trust_violation");
}
#[test]
fn test_violation_tracker_default() {
let tracker = ViolationTracker::default();
assert!(tracker.violations.is_empty());
assert!(tracker.timings.is_empty());
}
#[test]
fn test_record_failure() {
let mut tracker = ViolationTracker::default();
tracker.record_failure("TEST-001", "cargo test", 1, "test failed");
assert_eq!(tracker.violations.len(), 1);
assert_eq!(tracker.violations[0].violation_type, ViolationType::CommandFailure);
assert_eq!(tracker.violations[0].exit_code, Some(1));
assert_eq!(tracker.violations[0].command, "cargo test");
}
#[test]
fn test_record_execution_no_anomaly() {
let mut tracker = ViolationTracker::default();
let anomalous = tracker.record_execution("TEST-001", "cargo build", 5000);
assert!(!anomalous);
assert!(tracker.violations.is_empty());
assert!(tracker.timings.contains_key("cargo build"));
assert_eq!(tracker.timings["cargo build"].durations.len(), 1);
}
#[test]
fn test_record_execution_timing_anomaly() {
let mut tracker = ViolationTracker::default();
for _ in 0..10 {
tracker.record_execution("TEST-001", "npm test", 1000);
}
assert!(tracker.violations.is_empty());
let anomalous = tracker.record_execution("TEST-001", "npm test", 100_000);
assert!(anomalous);
assert_eq!(tracker.violations.len(), 1);
assert_eq!(tracker.violations[0].violation_type, ViolationType::TimingAnomaly);
assert!(tracker.violations[0].duration_ms.is_some());
}
#[test]
fn test_record_trust_violation() {
let mut tracker = ViolationTracker::default();
tracker.record_trust_violation("TEST-001", ".dbc-stack.toml", "manifest hash changed");
assert_eq!(tracker.violations.len(), 1);
assert_eq!(tracker.violations[0].violation_type, ViolationType::TrustViolation);
assert_eq!(tracker.violations[0].command, ".dbc-stack.toml");
}
#[test]
fn test_violation_summary() {
let mut tracker = ViolationTracker::default();
tracker.record_failure("TEST-001", "cmd1", 1, "fail");
tracker.record_failure("TEST-001", "cmd2", 2, "fail");
tracker.record_trust_violation("TEST-001", "manifest", "changed");
let summary = tracker.summary(1.0);
assert_eq!(summary.total_violations, 3);
assert_eq!(summary.command_failures, 2);
assert_eq!(summary.timing_anomalies, 0);
assert_eq!(summary.trust_violations, 1);
assert!(summary.elevated); }
#[test]
fn test_violation_summary_not_elevated() {
let mut tracker = ViolationTracker::default();
tracker.record_failure("TEST-001", "cmd1", 1, "fail");
let summary = tracker.summary(5.0);
assert_eq!(summary.total_violations, 1);
assert!(!summary.elevated); }
#[test]
fn test_command_timing_new() {
let timing = CommandTiming::new("cargo test".to_string(), 3000);
assert_eq!(timing.mean_ms, 3000.0);
assert_eq!(timing.std_dev_ms, 0.0);
assert_eq!(timing.durations.len(), 1);
}
#[test]
fn test_command_timing_record() {
let mut timing = CommandTiming::new("cargo test".to_string(), 1000);
timing.record(2000);
timing.record(3000);
assert_eq!(timing.durations.len(), 3);
assert!((timing.mean_ms - 2000.0).abs() < f64::EPSILON);
assert!(timing.std_dev_ms > 0.0);
}
#[test]
fn test_command_timing_is_anomalous_insufficient_data() {
let timing = CommandTiming::new("cmd".to_string(), 1000);
assert!(!timing.is_anomalous(999999));
}
#[test]
fn test_command_timing_is_anomalous_with_data() {
let mut timing = CommandTiming::new("cmd".to_string(), 100);
for _ in 0..20 {
timing.record(100);
}
assert!(timing.is_anomalous(100000));
assert!(!timing.is_anomalous(100));
}
#[test]
fn test_command_timing_caps_at_50() {
let mut timing = CommandTiming::new("cmd".to_string(), 100);
for i in 0..100 {
timing.record(100 + i);
}
assert_eq!(timing.durations.len(), 50);
}
#[test]
fn test_trust_chain_entry_new() {
let entry = TrustChainEntry::new(".dbc-stack.toml", "abc123", "");
assert_eq!(entry.manifest_path, ".dbc-stack.toml");
assert_eq!(entry.content_hash, "abc123");
assert_eq!(entry.prev_hash, "");
assert!(!entry.chain_hash.is_empty());
}
#[test]
fn test_trust_chain_entry_verify() {
let entry = TrustChainEntry::new(".dbc-stack.toml", "abc123", "prev456");
assert!(entry.verify());
let mut tampered = entry.clone();
tampered.content_hash = "tampered".to_string();
assert!(!tampered.verify());
}
#[test]
fn test_trust_chain_linking() {
let entry1 = TrustChainEntry::new(".dbc-stack.toml", "hash1", "");
let entry2 = TrustChainEntry::new(".dbc-stack.toml", "hash2", &entry1.chain_hash);
assert!(entry1.verify());
assert!(entry2.verify());
assert_eq!(entry2.prev_hash, entry1.chain_hash);
}
#[test]
fn test_violation_tracker_save_load() {
let tmp = tempfile::tempdir().unwrap();
let mut tracker = ViolationTracker::default();
tracker.record_failure("TEST-001", "cmd", 1, "fail");
tracker.save(tmp.path(), "TEST-001").unwrap();
let loaded = ViolationTracker::load(tmp.path(), "TEST-001");
assert_eq!(loaded.violations.len(), 1);
assert_eq!(loaded.violations[0].command, "cmd");
}
#[test]
fn test_violation_tracker_load_missing() {
let tmp = tempfile::tempdir().unwrap();
let loaded = ViolationTracker::load(tmp.path(), "NONEXISTENT");
assert!(loaded.violations.is_empty());
}
#[test]
fn test_runtime_violation_serialization() {
let violation = RuntimeViolation {
work_item_id: "TEST-001".to_string(),
timestamp: "12345Z".to_string(),
command: "cargo test".to_string(),
violation_type: ViolationType::CommandFailure,
duration_ms: None,
exit_code: Some(1),
message: "test failed".to_string(),
};
let json = serde_json::to_string(&violation).unwrap();
let deserialized: RuntimeViolation = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.work_item_id, "TEST-001");
assert_eq!(deserialized.violation_type, ViolationType::CommandFailure);
}
#[test]
fn test_violation_summary_serialization() {
let summary = ViolationSummary {
total_violations: 5,
command_failures: 3,
timing_anomalies: 1,
trust_violations: 1,
violations_per_session_avg: 2.0,
elevated: true,
};
let json = serde_json::to_string(&summary).unwrap();
let deserialized: ViolationSummary = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.total_violations, 5);
assert!(deserialized.elevated);
}