use pulsedb::{
CollectiveId, Config, NewExperience, NewExperienceRelation, PulseDB, RelationDirection,
RelationType,
};
use tempfile::tempdir;
const DIM: usize = 384;
fn dummy_embedding() -> Vec<f32> {
vec![0.1; DIM]
}
fn open_db() -> (PulseDB, tempfile::TempDir) {
let dir = tempdir().unwrap();
let path = dir.path().join("test.db");
let db = PulseDB::open(&path, Config::default()).unwrap();
(db, dir)
}
fn open_db_with_collective() -> (PulseDB, CollectiveId, tempfile::TempDir) {
let (db, dir) = open_db();
let cid = db.create_collective("test-collective").unwrap();
(db, cid, dir)
}
fn minimal_experience(collective_id: CollectiveId) -> NewExperience {
NewExperience {
collective_id,
content: "Test experience content".to_string(),
embedding: Some(dummy_embedding()),
..Default::default()
}
}
#[test]
fn test_store_and_get_relation() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "Second experience".to_string(),
..minimal_experience(cid)
})
.unwrap();
let rel_id = db
.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Supports,
strength: 0.8,
metadata: Some("test metadata".to_string()),
})
.unwrap();
let relation = db.get_relation(rel_id).unwrap().unwrap();
assert_eq!(relation.id, rel_id);
assert_eq!(relation.source_id, exp_a);
assert_eq!(relation.target_id, exp_b);
assert_eq!(relation.relation_type, RelationType::Supports);
assert!((relation.strength - 0.8).abs() < f32::EPSILON);
assert_eq!(relation.metadata, Some("test metadata".to_string()));
}
#[test]
fn test_get_relation_nonexistent() {
let (db, _dir) = open_db();
let fake_id = pulsedb::RelationId::new();
let result = db.get_relation(fake_id).unwrap();
assert!(result.is_none());
}
#[test]
fn test_get_related_outgoing() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "Target experience".to_string(),
..minimal_experience(cid)
})
.unwrap();
db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Elaborates,
strength: 0.7,
metadata: None,
})
.unwrap();
let related = db
.get_related_experiences(exp_a, RelationDirection::Outgoing)
.unwrap();
assert_eq!(related.len(), 1);
assert_eq!(related[0].0.id, exp_b);
assert_eq!(related[0].1.relation_type, RelationType::Elaborates);
let related = db
.get_related_experiences(exp_b, RelationDirection::Outgoing)
.unwrap();
assert!(related.is_empty());
}
#[test]
fn test_get_related_incoming() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "Target experience".to_string(),
..minimal_experience(cid)
})
.unwrap();
db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Implies,
strength: 0.5,
metadata: None,
})
.unwrap();
let related = db
.get_related_experiences(exp_b, RelationDirection::Incoming)
.unwrap();
assert_eq!(related.len(), 1);
assert_eq!(related[0].0.id, exp_a);
let related = db
.get_related_experiences(exp_a, RelationDirection::Incoming)
.unwrap();
assert!(related.is_empty());
}
#[test]
fn test_get_related_both() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "B experience".to_string(),
..minimal_experience(cid)
})
.unwrap();
let exp_c = db
.record_experience(NewExperience {
content: "C experience".to_string(),
..minimal_experience(cid)
})
.unwrap();
db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Supports,
strength: 0.9,
metadata: None,
})
.unwrap();
db.store_relation(NewExperienceRelation {
source_id: exp_c,
target_id: exp_a,
relation_type: RelationType::Contradicts,
strength: 0.6,
metadata: None,
})
.unwrap();
let related = db
.get_related_experiences(exp_a, RelationDirection::Both)
.unwrap();
assert_eq!(related.len(), 2);
let exp_ids: Vec<_> = related.iter().map(|(e, _)| e.id).collect();
assert!(exp_ids.contains(&exp_b));
assert!(exp_ids.contains(&exp_c));
}
#[test]
fn test_delete_relation() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "Second".to_string(),
..minimal_experience(cid)
})
.unwrap();
let rel_id = db
.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::RelatedTo,
strength: 0.5,
metadata: None,
})
.unwrap();
assert!(db.get_relation(rel_id).unwrap().is_some());
db.delete_relation(rel_id).unwrap();
assert!(db.get_relation(rel_id).unwrap().is_none());
let related = db
.get_related_experiences(exp_a, RelationDirection::Outgoing)
.unwrap();
assert!(related.is_empty());
}
#[test]
fn test_relation_cascade_delete_experience() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "B".to_string(),
..minimal_experience(cid)
})
.unwrap();
let rel_id = db
.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Supersedes,
strength: 1.0,
metadata: None,
})
.unwrap();
db.delete_experience(exp_a).unwrap();
assert!(db.get_relation(rel_id).unwrap().is_none());
assert!(db.get_experience(exp_b).unwrap().is_some());
}
#[test]
fn test_collective_cascade_deletes_relations() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "B".to_string(),
..minimal_experience(cid)
})
.unwrap();
let rel_id = db
.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Supports,
strength: 0.5,
metadata: None,
})
.unwrap();
db.delete_collective(cid).unwrap();
assert!(db.get_relation(rel_id).unwrap().is_none());
assert!(db.get_experience(exp_a).unwrap().is_none());
assert!(db.get_experience(exp_b).unwrap().is_none());
}
#[test]
fn test_self_relation_rejected() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let result = db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_a, relation_type: RelationType::Supports,
strength: 0.5,
metadata: None,
});
assert!(result.is_err());
assert!(result.unwrap_err().is_validation());
}
#[test]
fn test_cross_collective_rejected() {
let (db, _dir) = open_db();
let cid_a = db.create_collective("collective-a").unwrap();
let cid_b = db.create_collective("collective-b").unwrap();
let exp_a = db.record_experience(minimal_experience(cid_a)).unwrap();
let exp_b = db.record_experience(minimal_experience(cid_b)).unwrap();
let result = db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::RelatedTo,
strength: 0.5,
metadata: None,
});
assert!(result.is_err());
assert!(result.unwrap_err().is_validation());
}
#[test]
fn test_duplicate_relation_rejected() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "B".to_string(),
..minimal_experience(cid)
})
.unwrap();
db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Supports,
strength: 0.5,
metadata: None,
})
.unwrap();
let result = db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Supports,
strength: 0.9, metadata: None,
});
assert!(result.is_err());
assert!(result.unwrap_err().is_validation());
}
#[test]
fn test_relation_different_type_allowed() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "B".to_string(),
..minimal_experience(cid)
})
.unwrap();
db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Supports,
strength: 0.5,
metadata: None,
})
.unwrap();
db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Contradicts,
strength: 0.3,
metadata: None,
})
.unwrap();
let related = db
.get_related_experiences(exp_a, RelationDirection::Outgoing)
.unwrap();
assert_eq!(related.len(), 2);
}
#[test]
fn test_relation_type_filter() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let exp_b = db
.record_experience(NewExperience {
content: "B experience".to_string(),
..minimal_experience(cid)
})
.unwrap();
let exp_c = db
.record_experience(NewExperience {
content: "C experience".to_string(),
..minimal_experience(cid)
})
.unwrap();
db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_b,
relation_type: RelationType::Supports,
strength: 0.9,
metadata: None,
})
.unwrap();
db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: exp_c,
relation_type: RelationType::Contradicts,
strength: 0.6,
metadata: None,
})
.unwrap();
let supports = db
.get_related_experiences_filtered(
exp_a,
RelationDirection::Outgoing,
Some(RelationType::Supports),
)
.unwrap();
assert_eq!(supports.len(), 1);
assert_eq!(supports[0].0.id, exp_b);
assert_eq!(supports[0].1.relation_type, RelationType::Supports);
let contradicts = db
.get_related_experiences_filtered(
exp_a,
RelationDirection::Outgoing,
Some(RelationType::Contradicts),
)
.unwrap();
assert_eq!(contradicts.len(), 1);
assert_eq!(contradicts[0].0.id, exp_c);
assert_eq!(contradicts[0].1.relation_type, RelationType::Contradicts);
let elaborates = db
.get_related_experiences_filtered(
exp_a,
RelationDirection::Outgoing,
Some(RelationType::Elaborates),
)
.unwrap();
assert!(elaborates.is_empty());
let all = db
.get_related_experiences_filtered(exp_a, RelationDirection::Outgoing, None)
.unwrap();
assert_eq!(all.len(), 2);
}
#[test]
fn test_relation_nonexistent_source() {
let (db, cid, _dir) = open_db_with_collective();
let exp_b = db.record_experience(minimal_experience(cid)).unwrap();
let fake_id = pulsedb::ExperienceId::new();
let result = db.store_relation(NewExperienceRelation {
source_id: fake_id,
target_id: exp_b,
relation_type: RelationType::Supports,
strength: 0.5,
metadata: None,
});
assert!(result.is_err());
assert!(result.unwrap_err().is_not_found());
}
#[test]
fn test_relation_nonexistent_target() {
let (db, cid, _dir) = open_db_with_collective();
let exp_a = db.record_experience(minimal_experience(cid)).unwrap();
let fake_id = pulsedb::ExperienceId::new();
let result = db.store_relation(NewExperienceRelation {
source_id: exp_a,
target_id: fake_id,
relation_type: RelationType::Supports,
strength: 0.5,
metadata: None,
});
assert!(result.is_err());
assert!(result.unwrap_err().is_not_found());
}