stemedb/crates/stemedb-core/src/testing.rs
jordan d3a88585fe feat: Phase 6 UAT - Admission control, HLC recency, cluster coordination
This commit includes comprehensive work on Phase 6 features:

## Admission Control (Phase 6 admission middleware)
- AdmissionStore implementation backed by TrustRankStore
- PoW verification with tier-based difficulty computation
- Trust tier progression (Newcomer → Established → Trusted → Authority)
- API integration with admission status endpoints

## HLC Recency Lens (Phase 6C)
- HlcRecencyLens for distributed system ordering
- Hybrid logical clock integration with causality preservation

## Cluster Coordination (Phase 6C)
- Multi-node cluster tests (availability, partition tolerance)
- CRDT convergence tests for anti-entropy sync
- Gateway handler improvements

## Aphoria Code Linter (Phase 2A)
- RFC/OWASP corpus builders with network fetching and caching
- Concept hierarchy with auto-alias creation on conflict detection
- Multiple security extractors (TLS, JWT, CORS, secrets, rate limiting)

## Code Organization
- Split large files into modules to comply with 500-line limit
- Improved test organization with separate test modules
- Fixed rkyv serialization for EigenTrustState (AgentScore struct)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 00:43:37 -07:00

368 lines
10 KiB
Rust

//! Shared test helpers for the Episteme workspace.
//!
//! Provides [`AssertionBuilder`] and factory functions to eliminate duplicate
//! test assertion construction across crates. Import via:
//!
//! ```rust,ignore
//! use stemedb_core::testing::{AssertionBuilder, test_vote, test_epoch};
//! ```
use crate::types::{
Assertion, Epoch, HlcTimestamp, LifecycleStage, ObjectValue, SignatureEntry, SourceClass,
SupersessionType, Vote,
};
/// Builder for constructing test [`Assertion`] instances.
///
/// Every field has a sensible default so callers only override what they need.
///
/// # Examples
///
/// ```rust,ignore
/// // Minimal
/// let a = AssertionBuilder::new().build();
///
/// // Override subject + timestamp
/// let a = AssertionBuilder::new()
/// .subject("Tesla")
/// .timestamp(2000)
/// .build();
///
/// // Full control
/// let a = AssertionBuilder::new()
/// .subject("Tesla")
/// .predicate("revenue")
/// .object_number(96.7)
/// .confidence(0.8)
/// .lifecycle(LifecycleStage::Proposed)
/// .agent_id([5u8; 32])
/// .timestamp(3000)
/// .build();
/// ```
pub struct AssertionBuilder {
subject: String,
predicate: String,
object: ObjectValue,
parent_hash: Option<[u8; 32]>,
source_hash: [u8; 32],
source_class: SourceClass,
visual_hash: Option<[u8; 8]>,
epoch: Option<[u8; 32]>,
source_metadata: Option<Vec<u8>>,
lifecycle: LifecycleStage,
signatures: Option<Vec<SignatureEntry>>,
agent_id: [u8; 32],
confidence: f32,
timestamp: u64,
hlc_timestamp: HlcTimestamp,
vector: Option<Vec<f32>>,
}
impl Default for AssertionBuilder {
fn default() -> Self {
Self::new()
}
}
impl AssertionBuilder {
/// Create a new builder with sensible test defaults.
pub fn new() -> Self {
Self {
subject: "test_subject".to_string(),
predicate: "test_predicate".to_string(),
object: ObjectValue::Number(100.0),
parent_hash: None,
source_hash: [0u8; 32],
source_class: SourceClass::Expert, // Default to middle tier for tests
visual_hash: None,
epoch: None,
source_metadata: None,
lifecycle: LifecycleStage::Approved,
signatures: None, // Will use agent_id to build default
agent_id: [1u8; 32],
confidence: 0.9,
timestamp: 1000,
hlc_timestamp: HlcTimestamp::default(),
vector: None,
}
}
/// Set the subject.
pub fn subject(mut self, subject: &str) -> Self {
self.subject = subject.to_string();
self
}
/// Set the predicate.
pub fn predicate(mut self, predicate: &str) -> Self {
self.predicate = predicate.to_string();
self
}
/// Set the object to a numeric value.
pub fn object_number(mut self, value: f64) -> Self {
self.object = ObjectValue::Number(value);
self
}
/// Set the object to a text value.
pub fn object_text(mut self, value: &str) -> Self {
self.object = ObjectValue::Text(value.to_string());
self
}
/// Set the object to an arbitrary ObjectValue.
pub fn object(mut self, value: ObjectValue) -> Self {
self.object = value;
self
}
/// Set the confidence (0.0 to 1.0).
pub fn confidence(mut self, confidence: f32) -> Self {
self.confidence = confidence;
self
}
/// Set the timestamp.
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
/// Set the HLC timestamp for distributed causal ordering.
///
/// This provides total ordering even with clock skew between nodes.
/// Most tests can rely on the default (HlcTimestamp::default()).
pub fn hlc_timestamp(mut self, hlc_timestamp: HlcTimestamp) -> Self {
self.hlc_timestamp = hlc_timestamp;
self
}
/// Set the lifecycle stage.
pub fn lifecycle(mut self, lifecycle: LifecycleStage) -> Self {
self.lifecycle = lifecycle;
self
}
/// Set the agent_id used in the default signature.
pub fn agent_id(mut self, agent_id: [u8; 32]) -> Self {
self.agent_id = agent_id;
self
}
/// Set the source hash.
pub fn source_hash(mut self, hash: [u8; 32]) -> Self {
self.source_hash = hash;
self
}
/// Set the source class (authority tier).
pub fn source_class(mut self, source_class: SourceClass) -> Self {
self.source_class = source_class;
self
}
/// Set the parent hash.
pub fn parent_hash(mut self, hash: [u8; 32]) -> Self {
self.parent_hash = Some(hash);
self
}
/// Set the visual hash.
pub fn visual_hash(mut self, hash: [u8; 8]) -> Self {
self.visual_hash = Some(hash);
self
}
/// Set the epoch.
pub fn epoch(mut self, epoch: [u8; 32]) -> Self {
self.epoch = Some(epoch);
self
}
/// Set the vector embedding.
pub fn vector(mut self, vector: Vec<f32>) -> Self {
self.vector = Some(vector);
self
}
/// Set the source metadata from a JSON string.
/// Stores as bytes for rkyv compatibility.
pub fn source_metadata_json(mut self, json: &str) -> Self {
self.source_metadata = Some(json.as_bytes().to_vec());
self
}
/// Set the source metadata as raw bytes.
pub fn source_metadata(mut self, metadata: Vec<u8>) -> Self {
self.source_metadata = Some(metadata);
self
}
/// Provide explicit signatures (overrides the default single-signature behavior).
pub fn signatures(mut self, signatures: Vec<SignatureEntry>) -> Self {
self.signatures = Some(signatures);
self
}
/// Build the [`Assertion`].
pub fn build(self) -> Assertion {
let signatures = self.signatures.unwrap_or_else(|| {
vec![SignatureEntry {
agent_id: self.agent_id,
signature: [2u8; 64],
timestamp: self.timestamp,
version: 1, // Default to v1 for backward compatibility
}]
});
Assertion {
subject: self.subject,
predicate: self.predicate,
object: self.object,
parent_hash: self.parent_hash,
source_hash: self.source_hash,
source_class: self.source_class,
visual_hash: self.visual_hash,
epoch: self.epoch,
source_metadata: self.source_metadata,
lifecycle: self.lifecycle,
signatures,
confidence: self.confidence,
timestamp: self.timestamp,
hlc_timestamp: self.hlc_timestamp,
vector: self.vector,
}
}
}
/// Create a test [`Vote`] with the given parameters.
pub fn test_vote(
assertion_hash: [u8; 32],
agent_id: [u8; 32],
weight: f32,
timestamp: u64,
) -> Vote {
Vote {
assertion_hash,
agent_id,
weight,
signature: [0u8; 64],
timestamp,
source_url: None,
observed_context: None,
}
}
/// Create a test [`Epoch`] with defaults.
pub fn test_epoch() -> Epoch {
Epoch {
id: [4u8; 32],
name: "Test Epoch".to_string(),
supersedes: None,
supersession_type: None,
start_timestamp: 1000,
end_timestamp: None,
}
}
/// Create a test [`Epoch`] with a supersession relationship.
pub fn test_epoch_with_supersession(
id: [u8; 32],
name: &str,
supersedes: [u8; 32],
supersession_type: SupersessionType,
) -> Epoch {
Epoch {
id,
name: name.to_string(),
supersedes: Some(supersedes),
supersession_type: Some(supersession_type),
start_timestamp: 1000,
end_timestamp: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_defaults() {
let a = AssertionBuilder::new().build();
assert_eq!(a.subject, "test_subject");
assert_eq!(a.predicate, "test_predicate");
assert_eq!(a.lifecycle, LifecycleStage::Approved);
assert!((a.confidence - 0.9).abs() < f32::EPSILON);
assert_eq!(a.timestamp, 1000);
assert_eq!(a.signatures.len(), 1);
assert_eq!(a.signatures[0].agent_id, [1u8; 32]);
}
#[test]
fn test_builder_overrides() {
let a = AssertionBuilder::new()
.subject("Tesla")
.predicate("revenue")
.object_number(96.7)
.confidence(0.85)
.timestamp(5000)
.lifecycle(LifecycleStage::Proposed)
.agent_id([5u8; 32])
.build();
assert_eq!(a.subject, "Tesla");
assert_eq!(a.predicate, "revenue");
assert_eq!(a.object, ObjectValue::Number(96.7));
assert!((a.confidence - 0.85).abs() < f32::EPSILON);
assert_eq!(a.timestamp, 5000);
assert_eq!(a.lifecycle, LifecycleStage::Proposed);
assert_eq!(a.signatures[0].agent_id, [5u8; 32]);
}
#[test]
fn test_builder_custom_signatures() {
let sigs = vec![
SignatureEntry {
agent_id: [10u8; 32],
signature: [11u8; 64],
timestamp: 100,
version: 1,
},
SignatureEntry {
agent_id: [20u8; 32],
signature: [21u8; 64],
timestamp: 200,
version: 1,
},
];
let a = AssertionBuilder::new().signatures(sigs).build();
assert_eq!(a.signatures.len(), 2);
assert_eq!(a.signatures[0].agent_id, [10u8; 32]);
}
#[test]
fn test_builder_text_object() {
let a = AssertionBuilder::new().object_text("hello").build();
assert_eq!(a.object, ObjectValue::Text("hello".to_string()));
}
#[test]
fn test_vote_factory() {
let v = test_vote([1u8; 32], [2u8; 32], 0.8, 3000);
assert_eq!(v.assertion_hash, [1u8; 32]);
assert_eq!(v.agent_id, [2u8; 32]);
assert!((v.weight - 0.8).abs() < f32::EPSILON);
assert_eq!(v.timestamp, 3000);
}
#[test]
fn test_epoch_factory() {
let e = test_epoch();
assert_eq!(e.id, [4u8; 32]);
assert_eq!(e.name, "Test Epoch");
assert!(e.supersedes.is_none());
}
}