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>
368 lines
10 KiB
Rust
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());
|
|
}
|
|
}
|