//! Boundary and edge-case validation tests. //! //! Tests for infinite confidence/weight, future timestamps, //! and boundary values (0.0, 1.0) for confidence. use super::*; use crate::error::IngestError; /// Test: Assertions with infinite confidence are rejected. #[tokio::test] async fn test_rejects_infinite_confidence() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); let mut csprng = OsRng; let signing_key = SigningKey::generate(&mut csprng); let verifying_key = signing_key.verifying_key(); let message = "Test:inf_confidence"; let signature = signing_key.sign(message.as_bytes()); let assertion = Assertion { subject: "Test".to_string(), predicate: "inf_confidence".to_string(), object: ObjectValue::Text("test".to_string()), parent_hash: None, source_hash: [0u8; 32], source_class: SourceClass::Expert, visual_hash: None, epoch: None, source_metadata: None, lifecycle: LifecycleStage::Proposed, signatures: vec![SignatureEntry { agent_id: verifying_key.to_bytes(), signature: signature.to_bytes(), timestamp: 1000, }], confidence: f32::INFINITY, timestamp: 1000, vector: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_assertion(&assertion).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); let result = worker.step().await; assert!(result.is_err(), "Should reject infinite confidence"); let err = result.unwrap_err(); assert!( matches!(err, IngestError::InputValidation(_)), "Error should be InputValidation, got: {:?}", err ); assert!(err.to_string().contains("infinite")); } /// Test: Votes with NaN weight are rejected. #[tokio::test] async fn test_rejects_nan_vote_weight() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); let vote = Vote { assertion_hash: [1u8; 32], agent_id: [2u8; 32], weight: f32::NAN, signature: [3u8; 64], timestamp: 1000, source_url: None, observed_context: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_vote(&vote).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); let result = worker.step().await; assert!(result.is_err(), "Should reject NaN vote weight"); let err = result.unwrap_err(); assert!( matches!(err, IngestError::InputValidation(_)), "Error should be InputValidation, got: {:?}", err ); assert!(err.to_string().contains("NaN")); } /// Test: Votes with infinite weight are rejected. #[tokio::test] async fn test_rejects_infinite_vote_weight() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); let vote = Vote { assertion_hash: [1u8; 32], agent_id: [2u8; 32], weight: f32::INFINITY, signature: [3u8; 64], timestamp: 1000, source_url: None, observed_context: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_vote(&vote).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); let result = worker.step().await; assert!(result.is_err(), "Should reject infinite vote weight"); let err = result.unwrap_err(); assert!( matches!(err, IngestError::InputValidation(_)), "Error should be InputValidation, got: {:?}", err ); assert!(err.to_string().contains("infinite")); } /// Test: Assertions with timestamp far in the future are rejected. #[tokio::test] async fn test_rejects_future_timestamp() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); let mut csprng = OsRng; let signing_key = SigningKey::generate(&mut csprng); let verifying_key = signing_key.verifying_key(); let message = "Test:future"; let signature = signing_key.sign(message.as_bytes()); // Create timestamp 2 hours in the future (should be rejected) let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let future_timestamp = now + 7200; // 2 hours ahead let assertion = Assertion { subject: "Test".to_string(), predicate: "future".to_string(), object: ObjectValue::Text("test".to_string()), parent_hash: None, source_hash: [0u8; 32], source_class: SourceClass::Expert, visual_hash: None, epoch: None, source_metadata: None, lifecycle: LifecycleStage::Proposed, signatures: vec![SignatureEntry { agent_id: verifying_key.to_bytes(), signature: signature.to_bytes(), timestamp: 1000, }], confidence: 0.9, timestamp: future_timestamp, vector: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_assertion(&assertion).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); let result = worker.step().await; assert!(result.is_err(), "Should reject timestamp more than 1 hour in future"); let err = result.unwrap_err(); assert!( matches!(err, IngestError::InputValidation(_)), "Error should be InputValidation, got: {:?}", err ); assert!(err.to_string().contains("timestamp")); } /// Test: Assertions with timestamp slightly in the future (< 1 hour) are accepted. #[tokio::test] async fn test_accepts_near_future_timestamp() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); let mut csprng = OsRng; let signing_key = SigningKey::generate(&mut csprng); let verifying_key = signing_key.verifying_key(); let message = "Test:near_future"; let signature = signing_key.sign(message.as_bytes()); // Create timestamp 30 minutes in the future (should be accepted) let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let near_future_timestamp = now + 1800; // 30 minutes ahead let assertion = Assertion { subject: "Test".to_string(), predicate: "near_future".to_string(), object: ObjectValue::Text("test".to_string()), parent_hash: None, source_hash: [0u8; 32], source_class: SourceClass::Expert, visual_hash: None, epoch: None, source_metadata: None, lifecycle: LifecycleStage::Proposed, signatures: vec![SignatureEntry { agent_id: verifying_key.to_bytes(), signature: signature.to_bytes(), timestamp: 1000, }], confidence: 0.9, timestamp: near_future_timestamp, vector: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_assertion(&assertion).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); let result = worker.step().await; assert!(result.is_ok(), "Should accept timestamp within 1 hour clock skew"); } /// Test: Edge case - confidence exactly 0.0 is accepted. #[tokio::test] async fn test_accepts_zero_confidence() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); let mut csprng = OsRng; let signing_key = SigningKey::generate(&mut csprng); let verifying_key = signing_key.verifying_key(); let message = "Test:zero_conf"; let signature = signing_key.sign(message.as_bytes()); let assertion = Assertion { subject: "Test".to_string(), predicate: "zero_conf".to_string(), object: ObjectValue::Text("test".to_string()), parent_hash: None, source_hash: [0u8; 32], source_class: SourceClass::Expert, visual_hash: None, epoch: None, source_metadata: None, lifecycle: LifecycleStage::Proposed, signatures: vec![SignatureEntry { agent_id: verifying_key.to_bytes(), signature: signature.to_bytes(), timestamp: 1000, }], confidence: 0.0, // Valid: boundary case timestamp: 1000, vector: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_assertion(&assertion).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); let result = worker.step().await; assert!(result.is_ok(), "Should accept confidence = 0.0"); } /// Test: Edge case - confidence exactly 1.0 is accepted. #[tokio::test] async fn test_accepts_one_confidence() { let dir = tempdir().expect("Failed to create temp dir"); let wal_dir = dir.path().join("wal"); let db_dir = dir.path().join("db"); let mut csprng = OsRng; let signing_key = SigningKey::generate(&mut csprng); let verifying_key = signing_key.verifying_key(); let message = "Test:one_conf"; let signature = signing_key.sign(message.as_bytes()); let assertion = Assertion { subject: "Test".to_string(), predicate: "one_conf".to_string(), object: ObjectValue::Text("test".to_string()), parent_hash: None, source_hash: [0u8; 32], source_class: SourceClass::Expert, visual_hash: None, epoch: None, source_metadata: None, lifecycle: LifecycleStage::Proposed, signatures: vec![SignatureEntry { agent_id: verifying_key.to_bytes(), signature: signature.to_bytes(), timestamp: 1000, }], confidence: 1.0, // Valid: boundary case timestamp: 1000, vector: None, }; let mut journal = Journal::open(&wal_dir).expect("Failed to open journal"); let store = HybridStore::open(&db_dir).expect("Failed to open store"); journal.append(serialize_assertion(&assertion).expect("ser")).expect("append"); let journal = Arc::new(Mutex::new(journal)); let store = Arc::new(store); let mut worker = IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker"); let result = worker.step().await; assert!(result.is_ok(), "Should accept confidence = 1.0"); }