use do_memory_core::episode::relationship_manager::RelationshipManager;
use do_memory_core::episode::{EpisodeRelationship, RelationshipMetadata, RelationshipType};
use proptest::prelude::*;
use std::collections::HashSet;
use uuid::Uuid;
proptest! {
#[test]
fn self_relationships_rejected(rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
let episode_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
let result = manager.add_with_validation(
episode_id,
episode_id, rel_type,
metadata,
);
assert!(result.is_err(), "Self-relationship should be rejected");
}
#[test]
fn duplicate_relationships_rejected(rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
let result1 = manager.add_with_validation(from_id, to_id, rel_type, metadata.clone());
assert!(result1.is_ok(), "First relationship should succeed");
let result2 = manager.add_with_validation(from_id, to_id, rel_type, metadata);
assert!(result2.is_err(), "Duplicate relationship should be rejected");
}
#[test]
fn invalid_priority_rejected(rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
for priority in [0, 11, 100, 255] {
let mut metadata = RelationshipMetadata::with_reason("Test".to_string());
metadata.priority = Some(priority);
let result = manager.add_with_validation(from_id, to_id, rel_type, metadata.clone());
assert!(result.is_err(), "Priority {priority} should be rejected");
}
for priority in 1..=10 {
let mut metadata = RelationshipMetadata::with_reason("Test".to_string());
metadata.priority = Some(priority);
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let result = manager.add_with_validation(from_id, to_id, rel_type, metadata.clone());
assert!(result.is_ok(), "Priority {priority} should be accepted");
}
}
}
proptest! {
#[test]
fn depends_on_prevents_cycles(relationship_chain_length in 2..10usize) {
let mut manager = RelationshipManager::new();
let episode_ids: Vec<Uuid> = (0..=relationship_chain_length)
.map(|_| Uuid::new_v4())
.collect();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
for i in 0..relationship_chain_length {
let result = manager.add_with_validation(
episode_ids[i],
episode_ids[i + 1],
RelationshipType::DependsOn,
metadata.clone(),
);
assert!(result.is_ok(), "Chain link {i} should succeed");
}
let result = manager.add_with_validation(
episode_ids[relationship_chain_length],
episode_ids[0],
RelationshipType::DependsOn,
metadata,
);
assert!(result.is_err(), "Closing the cycle should be rejected");
}
#[test]
fn parent_child_prevents_cycles(chain_length in 2..10usize) {
let mut manager = RelationshipManager::new();
let episode_ids: Vec<Uuid> = (0..=chain_length)
.map(|_| Uuid::new_v4())
.collect();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
for i in 0..chain_length {
let result = manager.add_with_validation(
episode_ids[i],
episode_ids[i + 1],
RelationshipType::ParentChild,
metadata.clone(),
);
assert!(result.is_ok(), "Chain link {i} should succeed");
}
let result = manager.add_with_validation(
episode_ids[chain_length],
episode_ids[0],
RelationshipType::ParentChild,
metadata,
);
assert!(result.is_err(), "Closing the cycle should be rejected");
}
#[test]
fn blocks_prevents_cycles(chain_length in 2..10usize) {
let mut manager = RelationshipManager::new();
let episode_ids: Vec<Uuid> = (0..=chain_length)
.map(|_| Uuid::new_v4())
.collect();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
for i in 0..chain_length {
let result = manager.add_with_validation(
episode_ids[i],
episode_ids[i + 1],
RelationshipType::Blocks,
metadata.clone(),
);
assert!(result.is_ok(), "Chain link {i} should succeed");
}
let result = manager.add_with_validation(
episode_ids[chain_length],
episode_ids[0],
RelationshipType::Blocks,
metadata,
);
assert!(result.is_err(), "Closing the cycle should be rejected");
}
#[test]
fn non_acyclic_allows_cycles(chain_length in 2..10usize) {
let non_acyclic_types = vec![
RelationshipType::Follows,
RelationshipType::RelatedTo,
RelationshipType::Duplicates,
RelationshipType::References,
];
for rel_type in non_acyclic_types {
let mut manager = RelationshipManager::new();
let episode_ids: Vec<Uuid> = (0..=chain_length)
.map(|_| Uuid::new_v4())
.collect();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
for i in 0..chain_length {
let result = manager.add_with_validation(
episode_ids[i],
episode_ids[i + 1],
rel_type,
metadata.clone(),
);
assert!(result.is_ok(), "Chain link {i} with {rel_type:?} should succeed");
}
let result = manager.add_with_validation(
episode_ids[chain_length],
episode_ids[0],
rel_type,
metadata,
);
assert!(result.is_ok(), "{rel_type:?} should allow cycles");
}
}
}
proptest! {
#[test]
fn relationship_removal_idempotent(rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
let rel = manager.add_with_validation(from_id, to_id, rel_type, metadata).unwrap();
let result1 = manager.remove_relationship(rel.id);
assert!(result1.is_ok(), "First removal should succeed");
let result2 = manager.remove_relationship(rel.id);
assert!(result2.is_err(), "Second removal should fail (idempotent)");
let result3 = manager.remove_relationship(rel.id);
assert!(result3.is_err(), "Third removal should also fail");
}
#[test]
fn removal_updates_indexes(rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
let rel = manager.add_with_validation(from_id, to_id, rel_type, metadata).unwrap();
assert!(manager.relationship_exists(from_id, to_id, rel_type));
assert!(!manager.get_outgoing(from_id).is_empty());
assert!(!manager.get_incoming(to_id).is_empty());
manager.remove_relationship(rel.id).unwrap();
assert!(!manager.relationship_exists(from_id, to_id, rel_type));
assert!(manager.get_outgoing(from_id).is_empty());
assert!(manager.get_incoming(to_id).is_empty());
}
}
proptest! {
#[test]
fn relationship_exists_consistency(rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
assert!(!manager.relationship_exists(from_id, to_id, rel_type));
manager.add_with_validation(from_id, to_id, rel_type, metadata).unwrap();
assert!(manager.relationship_exists(from_id, to_id, rel_type));
let rel = manager.get_outgoing(from_id)[0].clone();
manager.remove_relationship(rel.id).unwrap();
assert!(!manager.relationship_exists(from_id, to_id, rel_type));
}
#[test]
fn outgoing_incoming_symmetry(rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
let rel = manager.add_with_validation(from_id, to_id, rel_type, metadata).unwrap();
let outgoing = manager.get_outgoing(from_id);
assert_eq!(outgoing.len(), 1);
assert_eq!(outgoing[0].id, rel.id);
let incoming = manager.get_incoming(to_id);
assert_eq!(incoming.len(), 1);
assert_eq!(incoming[0].id, rel.id);
assert_eq!(outgoing[0].id, incoming[0].id);
}
#[test]
fn get_by_type_returns_both_directions(rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
let rel1 = manager.add_with_validation(from_id, to_id, rel_type, metadata.clone()).unwrap();
let another_id = Uuid::new_v4();
let rel2 = manager.add_with_validation(another_id, from_id, rel_type, metadata).unwrap();
let from_rels = manager.get_by_type(from_id, rel_type);
assert_eq!(from_rels.len(), 2);
let rel_ids: HashSet<_> = from_rels.iter().map(|r| r.id).collect();
assert!(rel_ids.contains(&rel1.id));
assert!(rel_ids.contains(&rel2.id));
}
#[test]
fn relationship_count_accurate(count in 1..10usize, rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
for _ in 0..count {
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
manager.add_with_validation(from_id, to_id, rel_type, metadata).unwrap();
}
assert_eq!(manager.relationship_count(), count);
}
}
proptest! {
#[test]
fn relationship_type_string_roundtrip(rel_type in any::<RelationshipType>()) {
let s = rel_type.as_str();
let parsed = RelationshipType::parse(s).unwrap();
assert_eq!(rel_type, parsed);
}
#[test]
fn relationship_type_serializable(rel_type in any::<RelationshipType>()) {
let json = serde_json::to_string(&rel_type).unwrap();
let parsed: RelationshipType = serde_json::from_str(&json).unwrap();
assert_eq!(rel_type, parsed);
}
#[test]
fn directionality_property_consistent(rel_type in any::<RelationshipType>()) {
let is_directional = rel_type.is_directional();
let has_inverse = rel_type.inverse().is_some();
assert!(
is_directional || !has_inverse,
"Directional types should have inverse, non-directional should not"
);
}
#[test]
fn acyclic_requirement_consistent(rel_type in any::<RelationshipType>()) {
let requires_acyclic = rel_type.requires_acyclic();
if requires_acyclic {
assert!(rel_type.is_directional());
}
}
}
proptest! {
#[test]
fn load_relationships_preserves_state(count in 1..10usize, rel_type in any::<RelationshipType>()) {
let mut manager1 = RelationshipManager::new();
let mut relationships = Vec::new();
for _ in 0..count {
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
let rel = EpisodeRelationship::new(from_id, to_id, rel_type, metadata);
relationships.push(rel);
}
manager1.load_relationships(relationships.clone());
let mut manager2 = RelationshipManager::new();
manager2.load_relationships(relationships);
assert_eq!(manager1.relationship_count(), manager2.relationship_count());
assert_eq!(manager1.episode_count(), manager2.episode_count());
}
#[test]
fn multiple_relationships_between_pairs(count in 1..10usize, rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
for _ in 0..count {
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let metadata = RelationshipMetadata::with_reason("Test".to_string());
let result = manager.add_with_validation(from_id, to_id, rel_type, metadata);
assert!(result.is_ok());
}
assert_eq!(manager.relationship_count(), count);
}
#[test]
fn custom_fields_preserved(rel_type in any::<RelationshipType>()) {
let mut manager = RelationshipManager::new();
let from_id = Uuid::new_v4();
let to_id = Uuid::new_v4();
let mut metadata = RelationshipMetadata::with_reason("Test".to_string());
metadata.custom_fields.insert("key1".to_string(), "value1".to_string());
metadata.custom_fields.insert("key2".to_string(), "value2".to_string());
let result = manager.add_with_validation(from_id, to_id, rel_type, metadata).unwrap();
assert!(result.metadata.custom_fields.contains_key("key1"));
assert!(result.metadata.custom_fields.contains_key("key2"));
assert_eq!(result.metadata.custom_fields.get("key1"), Some(&"value1".to_string()));
}
}