Break monolith source files into focused modules: - stemedb-core/types.rs → types/ directory (assertion, source, gold_standard, etc.) - stemedb-storage: audit_store, quota_store, trust_rank_store, vector_index, vote_store → module directories - stemedb-ingest/worker.rs → worker/ with separate test modules - stemedb-query: engine, materializer, query → module directories - stemedb-lens: epoch_aware, skeptic → module directories - stemedb-sim/lib.rs → agent, arenas/, helpers, runner, strategy, types - stemedb-api/tests: integration_tests → http_basic, http_validation, http_epoch, http_pipeline - stemedb-api/tests: e2e_flow_test → e2e_full_pipeline, e2e_lens_resolution - stemedb-query/tests: e2e_pipeline → e2e_pipeline + e2e_decay Also adds new features: gold standard verification, escalation handlers, admin endpoints, concept hierarchy spec, arena roadmap, and Go SDK. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
149 lines
5.9 KiB
Rust
149 lines
5.9 KiB
Rust
//! 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<String> = 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()));
|
|
}
|