//! HTTP integration tests for full pipeline roundtrip. //! //! Coverage: //! - Full assertion roundtrip: POST → Ingest → GET //! - Integration with Ingestor for processing WAL entries //! - Signature verification through the pipeline //! - Query results after ingestion //! //! These tests validate the complete data flow through the system: //! 1. Client POSTs assertion to /v1/assert (writes to WAL) //! 2. Ingestor processes WAL entries (signature verification, storage) //! 3. Client GETs via /v1/query (reads from indexed storage) #![allow(clippy::expect_used)] mod common; use axum::{ body::Body, http::{Request, StatusCode}, }; use tower::ServiceExt; use stemedb_api::create_router; // ============================================================================ // Full Pipeline Integration Tests (POST → Ingest → GET roundtrip) // ============================================================================ /// Test full assertion roundtrip: POST → Ingest → GET /// /// This test validates the complete pipeline: /// 1. POST an assertion to /v1/assert (writes to WAL) /// 2. Run the ingestor to process the WAL entry /// 3. GET /v1/query to verify the assertion is queryable #[tokio::test] async fn test_assertion_roundtrip_with_ingestor() { let env = common::create_test_env_with_ingestor().await; let app = create_router(env.state.clone()); let subject = "RoundtripTest_Entity"; let predicate = "test_property"; let value = 42.0; // 1. POST the assertion let assertion = common::create_signed_assertion_json(subject, predicate, value); let request = Request::builder() .uri("/v1/assert") .method("POST") .header("content-type", "application/json") .body(Body::from(serde_json::to_vec(&assertion).expect("JSON"))) .expect("Request"); let response = app.clone().oneshot(request).await.expect("Request"); assert_eq!(response.status(), StatusCode::CREATED, "POST should succeed with 201"); let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body"); let post_result: serde_json::Value = serde_json::from_slice(&body).expect("JSON"); assert_eq!(post_result["status"], "created"); let assertion_hash = post_result["hash"].as_str().expect("hash should be present"); assert_eq!(assertion_hash.len(), 64, "hash should be 64 hex chars"); // 2. Run the ingestor to process the pending record (synchronous for testing) let bytes_processed = env.ingestor.process_pending().await.expect("ingestor should process"); assert!(bytes_processed > 0, "Should have processed WAL bytes"); // 3. GET the assertion via query let request = Request::builder() .uri(format!("/v1/query?subject={}&predicate={}", subject, predicate)) .method("GET") .body(Body::empty()) .expect("Request"); let response = app.oneshot(request).await.expect("Request"); assert_eq!(response.status(), StatusCode::OK); let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body"); let query_result: serde_json::Value = serde_json::from_slice(&body).expect("JSON"); // Verify the assertion is returned correctly assert_eq!(query_result["total_count"], 1, "Should find exactly 1 assertion"); let assertions = query_result["assertions"].as_array().expect("assertions array"); assert_eq!(assertions.len(), 1); let found = &assertions[0]; assert_eq!(found["subject"], subject); assert_eq!(found["predicate"], predicate); assert_eq!(found["object"]["type"], "Number"); assert!((found["object"]["value"].as_f64().expect("value") - value).abs() < 0.001); assert_eq!(found["confidence"], 0.95); } /// Test multiple assertions through the pipeline #[tokio::test] async fn test_multiple_assertions_roundtrip() { let env = common::create_test_env_with_ingestor().await; let app = create_router(env.state.clone()); let subject = "MultiTest_Entity"; // POST multiple assertions for the same subject for i in 1..=3 { let predicate = format!("property_{}", i); let value = i as f64 * 10.0; let assertion = common::create_signed_assertion_json(subject, &predicate, value); let request = Request::builder() .uri("/v1/assert") .method("POST") .header("content-type", "application/json") .body(Body::from(serde_json::to_vec(&assertion).expect("JSON"))) .expect("Request"); let response = app.clone().oneshot(request).await.expect("Request"); assert_eq!(response.status(), StatusCode::CREATED, "POST {} should succeed", i); } // Process all pending WAL entries let bytes_processed = env.ingestor.process_pending().await.expect("ingestor should process"); assert!(bytes_processed > 0, "Should have processed WAL bytes"); // Query by subject only - should get all 3 assertions let request = Request::builder() .uri(format!("/v1/query?subject={}", subject)) .method("GET") .body(Body::empty()) .expect("Request"); let response = app.oneshot(request).await.expect("Request"); assert_eq!(response.status(), StatusCode::OK); let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body"); let query_result: serde_json::Value = serde_json::from_slice(&body).expect("JSON"); assert_eq!(query_result["total_count"], 3, "Should find all 3 assertions"); let assertions = query_result["assertions"].as_array().expect("assertions array"); assert_eq!(assertions.len(), 3); // Verify all predicates are present let predicates: Vec = assertions .iter() .map(|a| a["predicate"].as_str().expect("predicate").to_string()) .collect(); assert!(predicates.contains(&"property_1".to_string())); assert!(predicates.contains(&"property_2".to_string())); assert!(predicates.contains(&"property_3".to_string())); }