git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
693 lines
22 KiB
Rust
693 lines
22 KiB
Rust
//! Comprehensive Storage Layer Tests
|
|
//!
|
|
//! Tests for:
|
|
//! - InMemoryStorage CRUD operations
|
|
//! - FileStorage persistence
|
|
//! - Concurrent access patterns
|
|
//! - Governance storage operations
|
|
|
|
use prime_radiant::storage::{
|
|
FileStorage, GovernanceStorage, GraphStorage, InMemoryStorage, StorageFormat,
|
|
};
|
|
use std::sync::{Arc, Barrier};
|
|
use std::thread;
|
|
use tempfile::TempDir;
|
|
|
|
// ============================================================================
|
|
// InMemoryStorage Unit Tests
|
|
// ============================================================================
|
|
|
|
mod in_memory_storage_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_store_and_retrieve_node() {
|
|
let storage = InMemoryStorage::new();
|
|
let state = vec![1.0, 2.0, 3.0];
|
|
|
|
storage.store_node("node-1", &state).unwrap();
|
|
let retrieved = storage.get_node("node-1").unwrap();
|
|
|
|
assert!(retrieved.is_some());
|
|
assert_eq!(retrieved.unwrap(), state);
|
|
}
|
|
|
|
#[test]
|
|
fn test_retrieve_nonexistent_node() {
|
|
let storage = InMemoryStorage::new();
|
|
let result = storage.get_node("nonexistent").unwrap();
|
|
assert!(result.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_update_node_state() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
storage.store_node("node-1", &[1.0, 0.0]).unwrap();
|
|
storage.store_node("node-1", &[0.0, 1.0]).unwrap();
|
|
|
|
let retrieved = storage.get_node("node-1").unwrap().unwrap();
|
|
assert_eq!(retrieved, vec![0.0, 1.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_store_and_delete_edge() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
storage.store_edge("a", "b", 1.5).unwrap();
|
|
storage.store_edge("b", "c", 2.0).unwrap();
|
|
|
|
// Delete one edge
|
|
storage.delete_edge("a", "b").unwrap();
|
|
|
|
// Should not fail on non-existent edge
|
|
storage.delete_edge("x", "y").unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_similar_vectors() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
// Store orthogonal vectors
|
|
storage.store_node("north", &[0.0, 1.0, 0.0]).unwrap();
|
|
storage.store_node("east", &[1.0, 0.0, 0.0]).unwrap();
|
|
storage.store_node("south", &[0.0, -1.0, 0.0]).unwrap();
|
|
storage.store_node("up", &[0.0, 0.0, 1.0]).unwrap();
|
|
|
|
// Query for similar to north
|
|
let results = storage.find_similar(&[0.0, 1.0, 0.0], 2).unwrap();
|
|
|
|
assert_eq!(results.len(), 2);
|
|
assert_eq!(results[0].0, "north"); // Exact match
|
|
assert!((results[0].1 - 1.0).abs() < 0.001); // Similarity = 1.0
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_similar_empty_query() {
|
|
let storage = InMemoryStorage::new();
|
|
storage.store_node("a", &[1.0, 2.0]).unwrap();
|
|
|
|
let results = storage.find_similar(&[], 5).unwrap();
|
|
assert!(results.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_governance_store_policy() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
let policy_data = b"test policy bundle data";
|
|
let id = storage.store_policy(policy_data).unwrap();
|
|
|
|
assert!(!id.is_empty());
|
|
|
|
let retrieved = storage.get_policy(&id).unwrap();
|
|
assert!(retrieved.is_some());
|
|
assert_eq!(retrieved.unwrap(), policy_data.to_vec());
|
|
}
|
|
|
|
#[test]
|
|
fn test_governance_store_witness() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
let witness_data = b"witness record data";
|
|
let id = storage.store_witness(witness_data).unwrap();
|
|
|
|
assert!(!id.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_governance_store_lineage() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
let lineage_data = b"lineage record data";
|
|
let id = storage.store_lineage(lineage_data).unwrap();
|
|
|
|
assert!(!id.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_concurrent_node_writes() {
|
|
let storage: Arc<InMemoryStorage> = Arc::new(InMemoryStorage::new());
|
|
let num_threads = 10;
|
|
let barrier = Arc::new(Barrier::new(num_threads));
|
|
let mut handles = vec![];
|
|
|
|
for i in 0..num_threads {
|
|
let storage_clone: Arc<InMemoryStorage> = Arc::clone(&storage);
|
|
let barrier_clone = Arc::clone(&barrier);
|
|
|
|
let handle = thread::spawn(move || {
|
|
// Wait for all threads to be ready
|
|
barrier_clone.wait();
|
|
|
|
for j in 0..100 {
|
|
let node_id = format!("node-{}-{}", i, j);
|
|
let state = vec![i as f32, j as f32];
|
|
storage_clone.store_node(&node_id, &state).unwrap();
|
|
}
|
|
});
|
|
|
|
handles.push(handle);
|
|
}
|
|
|
|
// Wait for all threads to complete
|
|
for handle in handles {
|
|
handle.join().unwrap();
|
|
}
|
|
|
|
// Verify we can retrieve a sample node
|
|
let result = storage.get_node("node-5-50").unwrap();
|
|
assert!(result.is_some());
|
|
assert_eq!(result.unwrap(), vec![5.0, 50.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_concurrent_reads_and_writes() {
|
|
let storage: Arc<InMemoryStorage> = Arc::new(InMemoryStorage::new());
|
|
|
|
// Pre-populate some data
|
|
for i in 0..100 {
|
|
storage
|
|
.store_node(&format!("node-{}", i), &[i as f32])
|
|
.unwrap();
|
|
}
|
|
|
|
let num_threads = 8;
|
|
let barrier = Arc::new(Barrier::new(num_threads));
|
|
let mut handles = vec![];
|
|
|
|
for i in 0..num_threads {
|
|
let storage_clone: Arc<InMemoryStorage> = Arc::clone(&storage);
|
|
let barrier_clone = Arc::clone(&barrier);
|
|
|
|
let handle = thread::spawn(move || {
|
|
barrier_clone.wait();
|
|
|
|
for j in 0..50 {
|
|
if i % 2 == 0 {
|
|
// Writers
|
|
let node_id = format!("new-node-{}-{}", i, j);
|
|
storage_clone.store_node(&node_id, &[j as f32]).unwrap();
|
|
} else {
|
|
// Readers
|
|
let node_id = format!("node-{}", j);
|
|
let _ = storage_clone.get_node(&node_id).unwrap();
|
|
}
|
|
}
|
|
});
|
|
|
|
handles.push(handle);
|
|
}
|
|
|
|
for handle in handles {
|
|
handle.join().unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_large_vector_storage() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
// Store a high-dimensional vector
|
|
let large_vec: Vec<f32> = (0..1024).map(|i| i as f32 / 1024.0).collect();
|
|
storage.store_node("large-vector", &large_vec).unwrap();
|
|
|
|
let retrieved = storage.get_node("large-vector").unwrap().unwrap();
|
|
assert_eq!(retrieved.len(), 1024);
|
|
assert!((retrieved[0] - 0.0).abs() < 0.001);
|
|
assert!((retrieved[1023] - 1023.0 / 1024.0).abs() < 0.001);
|
|
}
|
|
|
|
#[test]
|
|
fn test_many_nodes() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
// Store many nodes
|
|
for i in 0..1000 {
|
|
let node_id = format!("node-{}", i);
|
|
let state = vec![(i % 100) as f32, (i / 100) as f32];
|
|
storage.store_node(&node_id, &state).unwrap();
|
|
}
|
|
|
|
// Verify random access works
|
|
let n500 = storage.get_node("node-500").unwrap().unwrap();
|
|
assert_eq!(n500, vec![0.0, 5.0]);
|
|
|
|
let n999 = storage.get_node("node-999").unwrap().unwrap();
|
|
assert_eq!(n999, vec![99.0, 9.0]);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// FileStorage Unit Tests
|
|
// ============================================================================
|
|
|
|
mod file_storage_tests {
|
|
use super::*;
|
|
|
|
fn create_temp_storage() -> (FileStorage, TempDir) {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let storage = FileStorage::new(temp_dir.path()).unwrap();
|
|
(storage, temp_dir)
|
|
}
|
|
|
|
#[test]
|
|
fn test_store_and_retrieve_node() {
|
|
let (storage, _dir) = create_temp_storage();
|
|
let state = vec![1.0, 2.0, 3.0, 4.0];
|
|
|
|
storage.store_node("test-node", &state).unwrap();
|
|
let retrieved = storage.get_node("test-node").unwrap();
|
|
|
|
assert!(retrieved.is_some());
|
|
assert_eq!(retrieved.unwrap(), state);
|
|
}
|
|
|
|
#[test]
|
|
fn test_retrieve_nonexistent_node() {
|
|
let (storage, _dir) = create_temp_storage();
|
|
let result = storage.get_node("nonexistent").unwrap();
|
|
assert!(result.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_store_and_delete_edge() {
|
|
let (storage, _dir) = create_temp_storage();
|
|
|
|
storage.store_edge("source", "target", 0.75).unwrap();
|
|
let stats = storage.stats();
|
|
assert_eq!(stats.edge_count, 1);
|
|
|
|
storage.delete_edge("source", "target").unwrap();
|
|
let stats = storage.stats();
|
|
assert_eq!(stats.edge_count, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_persistence_across_instances() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
|
|
// First instance: write data
|
|
{
|
|
let storage = FileStorage::new(temp_dir.path()).unwrap();
|
|
storage
|
|
.store_node("persistent-node", &[1.0, 2.0, 3.0])
|
|
.unwrap();
|
|
storage.store_edge("a", "b", 1.5).unwrap();
|
|
storage.sync().unwrap();
|
|
}
|
|
|
|
// Second instance: read data back
|
|
{
|
|
let storage = FileStorage::new(temp_dir.path()).unwrap();
|
|
let node_state = storage.get_node("persistent-node").unwrap();
|
|
|
|
assert!(node_state.is_some());
|
|
assert_eq!(node_state.unwrap(), vec![1.0, 2.0, 3.0]);
|
|
|
|
let stats = storage.stats();
|
|
assert_eq!(stats.node_count, 1);
|
|
assert_eq!(stats.edge_count, 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_json_format() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let storage =
|
|
FileStorage::with_options(temp_dir.path(), StorageFormat::Json, false).unwrap();
|
|
|
|
storage.store_node("json-node", &[1.5, 2.5, 3.5]).unwrap();
|
|
|
|
let retrieved = storage.get_node("json-node").unwrap();
|
|
assert!(retrieved.is_some());
|
|
assert_eq!(retrieved.unwrap(), vec![1.5, 2.5, 3.5]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_bincode_format() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let storage =
|
|
FileStorage::with_options(temp_dir.path(), StorageFormat::Bincode, false).unwrap();
|
|
|
|
storage.store_node("bincode-node", &[1.0, 2.0]).unwrap();
|
|
|
|
let retrieved = storage.get_node("bincode-node").unwrap();
|
|
assert!(retrieved.is_some());
|
|
assert_eq!(retrieved.unwrap(), vec![1.0, 2.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_wal_recovery() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
|
|
// Write with WAL enabled
|
|
{
|
|
let storage =
|
|
FileStorage::with_options(temp_dir.path(), StorageFormat::Bincode, true).unwrap();
|
|
storage.store_node("wal-node", &[1.0, 2.0, 3.0]).unwrap();
|
|
// Don't call sync - simulate crash
|
|
}
|
|
|
|
// Re-open and verify WAL recovery
|
|
{
|
|
let storage =
|
|
FileStorage::with_options(temp_dir.path(), StorageFormat::Bincode, true).unwrap();
|
|
let node_state = storage.get_node("wal-node").unwrap();
|
|
assert!(node_state.is_some());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_governance_policy_persistence() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
|
|
let policy_id;
|
|
{
|
|
let storage = FileStorage::new(temp_dir.path()).unwrap();
|
|
policy_id = storage.store_policy(b"important policy data").unwrap();
|
|
storage.sync().unwrap();
|
|
}
|
|
|
|
{
|
|
let storage = FileStorage::new(temp_dir.path()).unwrap();
|
|
let policy = storage.get_policy(&policy_id).unwrap();
|
|
assert!(policy.is_some());
|
|
assert_eq!(policy.unwrap(), b"important policy data".to_vec());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_similar_vectors() {
|
|
let (storage, _dir) = create_temp_storage();
|
|
|
|
storage.store_node("a", &[1.0, 0.0, 0.0]).unwrap();
|
|
storage.store_node("b", &[0.9, 0.1, 0.0]).unwrap();
|
|
storage.store_node("c", &[0.0, 1.0, 0.0]).unwrap();
|
|
|
|
let results = storage.find_similar(&[1.0, 0.0, 0.0], 2).unwrap();
|
|
|
|
assert_eq!(results.len(), 2);
|
|
// "a" should be first (exact match)
|
|
assert_eq!(results[0].0, "a");
|
|
}
|
|
|
|
#[test]
|
|
fn test_storage_stats() {
|
|
let (storage, _dir) = create_temp_storage();
|
|
|
|
storage.store_node("n1", &[1.0]).unwrap();
|
|
storage.store_node("n2", &[2.0]).unwrap();
|
|
storage.store_edge("n1", "n2", 1.0).unwrap();
|
|
|
|
let stats = storage.stats();
|
|
assert_eq!(stats.node_count, 2);
|
|
assert_eq!(stats.edge_count, 1);
|
|
assert!(stats.wal_enabled);
|
|
}
|
|
|
|
#[test]
|
|
fn test_concurrent_file_operations() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let storage: Arc<FileStorage> = Arc::new(FileStorage::new(temp_dir.path()).unwrap());
|
|
|
|
let num_threads = 4;
|
|
let barrier = Arc::new(Barrier::new(num_threads));
|
|
let mut handles = vec![];
|
|
|
|
for i in 0..num_threads {
|
|
let storage_clone: Arc<FileStorage> = Arc::clone(&storage);
|
|
let barrier_clone = Arc::clone(&barrier);
|
|
|
|
let handle = thread::spawn(move || {
|
|
barrier_clone.wait();
|
|
|
|
for j in 0..25 {
|
|
let node_id = format!("concurrent-{}-{}", i, j);
|
|
storage_clone
|
|
.store_node(&node_id, &[i as f32, j as f32])
|
|
.unwrap();
|
|
}
|
|
});
|
|
|
|
handles.push(handle);
|
|
}
|
|
|
|
for handle in handles {
|
|
handle.join().unwrap();
|
|
}
|
|
|
|
// Verify all writes succeeded
|
|
let stats = storage.stats();
|
|
assert_eq!(stats.node_count, 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_witness_storage_and_retrieval() {
|
|
let (storage, _dir) = create_temp_storage();
|
|
|
|
// Store multiple witnesses
|
|
let w1_data = b"witness-1-action-abc";
|
|
let w2_data = b"witness-2-action-xyz";
|
|
let w3_data = b"witness-3-action-abc";
|
|
|
|
storage.store_witness(w1_data).unwrap();
|
|
storage.store_witness(w2_data).unwrap();
|
|
storage.store_witness(w3_data).unwrap();
|
|
|
|
// Search for witnesses containing "action-abc"
|
|
let results = storage.get_witnesses_for_action("action-abc").unwrap();
|
|
assert_eq!(results.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lineage_storage() {
|
|
let (storage, _dir) = create_temp_storage();
|
|
|
|
let lineage_data = b"lineage record with dependencies";
|
|
let id = storage.store_lineage(lineage_data).unwrap();
|
|
|
|
assert!(!id.is_empty());
|
|
// Lineages are write-only in the basic API, so just verify storage succeeded
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Integration Tests: Storage Pipelines
|
|
// ============================================================================
|
|
|
|
mod integration_tests {
|
|
use super::*;
|
|
|
|
/// Test the complete storage flow for a multi-tenant scenario
|
|
#[test]
|
|
fn test_multi_tenant_isolation() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
// Tenant A data (with namespace prefix)
|
|
storage.store_node("tenant-a::node-1", &[1.0, 0.0]).unwrap();
|
|
storage.store_node("tenant-a::node-2", &[0.0, 1.0]).unwrap();
|
|
|
|
// Tenant B data
|
|
storage.store_node("tenant-b::node-1", &[0.5, 0.5]).unwrap();
|
|
storage.store_node("tenant-b::node-2", &[0.3, 0.7]).unwrap();
|
|
|
|
// Verify isolation - tenant A's node-1 is different from tenant B's
|
|
let a_node = storage.get_node("tenant-a::node-1").unwrap().unwrap();
|
|
let b_node = storage.get_node("tenant-b::node-1").unwrap().unwrap();
|
|
|
|
assert_ne!(a_node, b_node);
|
|
|
|
// Find similar should respect prefixes
|
|
let results = storage.find_similar(&[1.0, 0.0], 4).unwrap();
|
|
assert!(results.iter().any(|(id, _)| id == "tenant-a::node-1"));
|
|
}
|
|
|
|
/// Test governance data isolation
|
|
#[test]
|
|
fn test_governance_policy_isolation() {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
// Store multiple policies
|
|
let policy_a = storage.store_policy(b"policy-for-tenant-a").unwrap();
|
|
let policy_b = storage.store_policy(b"policy-for-tenant-b").unwrap();
|
|
|
|
// Each policy should have a unique ID
|
|
assert_ne!(policy_a, policy_b);
|
|
|
|
// Retrieval should work independently
|
|
let a_data = storage.get_policy(&policy_a).unwrap().unwrap();
|
|
let b_data = storage.get_policy(&policy_b).unwrap().unwrap();
|
|
|
|
assert_eq!(a_data, b"policy-for-tenant-a".to_vec());
|
|
assert_eq!(b_data, b"policy-for-tenant-b".to_vec());
|
|
}
|
|
|
|
/// Test file storage survives process restart
|
|
#[test]
|
|
fn test_file_storage_durability() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
|
|
// Simulate first process
|
|
{
|
|
let storage = FileStorage::new(temp_dir.path()).unwrap();
|
|
|
|
// Store graph data
|
|
storage
|
|
.store_node("persistent-1", &[1.0, 2.0, 3.0])
|
|
.unwrap();
|
|
storage
|
|
.store_node("persistent-2", &[4.0, 5.0, 6.0])
|
|
.unwrap();
|
|
storage
|
|
.store_edge("persistent-1", "persistent-2", 0.5)
|
|
.unwrap();
|
|
|
|
// Store governance data
|
|
storage.store_policy(b"durable-policy").unwrap();
|
|
storage.store_witness(b"durable-witness").unwrap();
|
|
|
|
storage.sync().unwrap();
|
|
// Storage dropped here
|
|
}
|
|
|
|
// Simulate second process (restart)
|
|
{
|
|
let storage = FileStorage::new(temp_dir.path()).unwrap();
|
|
|
|
// All data should be present
|
|
let stats = storage.stats();
|
|
assert_eq!(stats.node_count, 2);
|
|
assert_eq!(stats.edge_count, 1);
|
|
|
|
let node1 = storage.get_node("persistent-1").unwrap().unwrap();
|
|
assert_eq!(node1, vec![1.0, 2.0, 3.0]);
|
|
}
|
|
}
|
|
|
|
/// Test hybrid storage (memory + file) fallback pattern
|
|
#[test]
|
|
fn test_storage_fallback_pattern() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_storage: Arc<FileStorage> = Arc::new(FileStorage::new(temp_dir.path()).unwrap());
|
|
let memory_cache = InMemoryStorage::new();
|
|
|
|
// Simulate a read-through cache pattern
|
|
let node_id = "cached-node";
|
|
let state = vec![1.0, 2.0, 3.0];
|
|
|
|
// Write to persistent storage
|
|
file_storage.store_node(node_id, &state).unwrap();
|
|
|
|
// Check cache first (miss)
|
|
let cached = memory_cache.get_node(node_id).unwrap();
|
|
assert!(cached.is_none());
|
|
|
|
// Read from persistent storage
|
|
let persistent = file_storage.get_node(node_id).unwrap().unwrap();
|
|
|
|
// Populate cache
|
|
memory_cache.store_node(node_id, &persistent).unwrap();
|
|
|
|
// Now cache hit
|
|
let cached = memory_cache.get_node(node_id).unwrap();
|
|
assert!(cached.is_some());
|
|
assert_eq!(cached.unwrap(), state);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Property-Based Tests
|
|
// ============================================================================
|
|
|
|
#[cfg(test)]
|
|
mod property_tests {
|
|
use super::*;
|
|
use proptest::prelude::*;
|
|
|
|
proptest! {
|
|
/// Energy (squared norm) is always non-negative
|
|
#[test]
|
|
fn energy_is_non_negative(
|
|
values in prop::collection::vec(-1000.0f32..1000.0, 1..100)
|
|
) {
|
|
// For any residual vector, its squared norm (energy) is non-negative
|
|
let energy: f32 = values.iter().map(|v| v * v).sum();
|
|
prop_assert!(energy >= 0.0);
|
|
}
|
|
|
|
/// Zero residual implies zero energy
|
|
#[test]
|
|
fn zero_residual_zero_energy(dim in 1usize..100) {
|
|
let zeros = vec![0.0f32; dim];
|
|
let energy: f32 = zeros.iter().map(|v| v * v).sum();
|
|
prop_assert!((energy - 0.0).abs() < 1e-10);
|
|
}
|
|
|
|
/// Storing and retrieving preserves data exactly
|
|
#[test]
|
|
fn store_retrieve_preserves_data(
|
|
node_id in "[a-z]{1,10}",
|
|
state in prop::collection::vec(-1000.0f32..1000.0, 1..10)
|
|
) {
|
|
let storage = InMemoryStorage::new();
|
|
storage.store_node(&node_id, &state).unwrap();
|
|
|
|
let retrieved = storage.get_node(&node_id).unwrap().unwrap();
|
|
prop_assert_eq!(retrieved, state);
|
|
}
|
|
|
|
/// File storage preserves data exactly
|
|
#[test]
|
|
fn file_store_retrieve_preserves_data(
|
|
node_id in "[a-z]{1,10}",
|
|
state in prop::collection::vec(-100.0f32..100.0, 1..10)
|
|
) {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let storage = FileStorage::new(temp_dir.path()).unwrap();
|
|
|
|
storage.store_node(&node_id, &state).unwrap();
|
|
let retrieved = storage.get_node(&node_id).unwrap().unwrap();
|
|
|
|
prop_assert_eq!(retrieved, state);
|
|
}
|
|
|
|
/// Similar vectors have high cosine similarity
|
|
#[test]
|
|
fn similar_vectors_high_similarity(
|
|
base in prop::collection::vec(0.1f32..1.0, 3..10)
|
|
) {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
// Normalize base
|
|
let norm: f32 = base.iter().map(|v| v * v).sum::<f32>().sqrt();
|
|
let normalized: Vec<f32> = base.iter().map(|v| v / norm).collect();
|
|
|
|
storage.store_node("base", &normalized).unwrap();
|
|
|
|
// Query with the same vector should give similarity ~1.0
|
|
let results = storage.find_similar(&normalized, 1).unwrap();
|
|
|
|
if let Some((id, sim)) = results.first() {
|
|
prop_assert_eq!(id, "base");
|
|
prop_assert!(*sim > 0.99);
|
|
}
|
|
}
|
|
|
|
/// Witness chain maintains order
|
|
#[test]
|
|
fn witness_chain_order(count in 1usize..20) {
|
|
let storage = InMemoryStorage::new();
|
|
|
|
for i in 0..count {
|
|
let data = format!("witness-{}", i);
|
|
let _ = storage.store_witness(data.as_bytes()).unwrap();
|
|
}
|
|
|
|
// Each witness should have been stored
|
|
// (We can't verify order without access to internal state,
|
|
// but this verifies no failures under load)
|
|
}
|
|
}
|
|
}
|