use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
fn test_db_path() -> PathBuf {
let id = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
std::env::temp_dir().join(format!(
"mneme_entities_test_{}_{}.db",
std::process::id(),
id
))
}
use mneme::store::db::Database;
use mneme::store::entities::{EntityStore, EntityType};
use mneme::store::memory::{CreateMemoryInput, Importance, MemoryType, Scope};
fn make_test_db() -> Database {
Database::open(&test_db_path()).unwrap()
}
fn make_input(project: &str, title: &str, content: &str) -> CreateMemoryInput {
CreateMemoryInput {
encrypt: false,
project: project.to_string(),
scope: Some(Scope::Project),
title: title.to_string(),
content: content.to_string(),
what: None,
why: None,
context: None,
learned: None,
memory_type: MemoryType::Note,
importance: Importance::Medium,
tags: vec![],
topic_key: None,
capture_prompt: None,
valid_from: None,
valid_until: None,
provenance: None,
}
}
#[test]
fn test_extract_url_entity() {
let content = "Check out https://github.com/rust-lang/rust for more info";
let entities = EntityStore::extract_entities_from_text(content);
let has_url = entities
.iter()
.any(|(name, etype, _)| name.contains("github.com") && etype == "url");
assert!(has_url, "Should detect URL entity, got: {:?}", entities);
}
#[test]
fn test_extract_file_path_entity() {
let content = "Edit src/main.rs and tests/store_tests.rs for changes";
let entities = EntityStore::extract_entities_from_text(content);
let _ = entities.len();
}
#[test]
fn test_extract_technology_entity() {
let content = "We use Rust and tokio for async runtime performance";
let entities = EntityStore::extract_entities_from_text(content);
let has_rust = entities
.iter()
.any(|(name, _, _)| name.to_lowercase() == "rust");
let has_tokio = entities
.iter()
.any(|(name, _, _)| name.to_lowercase() == "tokio");
assert!(
has_rust,
"Should detect Rust technology, got: {:?}",
entities
);
assert!(
has_tokio,
"Should detect tokio technology, got: {:?}",
entities
);
}
#[test]
fn test_extract_camelcase_concept() {
let content = "The RustCompiler and CargoBuildSystem are key components";
let entities = EntityStore::extract_entities_from_text(content);
let _ = entities.len();
}
#[test]
fn test_extract_dependency_pattern() {
let content = "dependency: rusqlite\nlibrary: serde";
let entities = EntityStore::extract_entities_from_text(content);
let has_dep = entities.iter().any(|(name, etype, _)| {
etype == "library" && (name.contains("rusqlite") || name.contains("serde"))
});
assert!(
has_dep,
"Should detect dependency entity, got: {:?}",
entities
);
}
#[test]
fn test_extract_no_entities_in_short_text() {
let entities = EntityStore::extract_entities_from_text("Hi there");
assert!(entities.len() <= 1, "Short text should have few entities");
}
#[test]
fn test_extract_empty_text() {
let entities = EntityStore::extract_entities_from_text("");
assert!(entities.is_empty(), "Empty text should yield no entities");
}
#[test]
fn test_extract_from_text_returns_strings_not_types() {
let entities =
EntityStore::extract_entities_from_text("Check https://example.com for Rust tips");
for (_name, etype, _conf) in &entities {
assert!(!etype.is_empty(), "Entity type should not be empty");
}
}
#[test]
fn test_entity_extraction_on_save() {
let db = make_test_db();
let store = db.memories();
let entity_store = db.entities();
let memory = store
.save(
make_input(
"test",
"Rust Memory",
"Use Rust with tokio and https://github.com/tokio-rs/tokio",
),
None,
None,
)
.unwrap();
let entities = entity_store.get_memory_entities(memory.id).unwrap();
assert!(!entities.is_empty(), "Should have extracted entities");
let has_url = entities.iter().any(|e| e.entity_type == EntityType::Url);
let has_tech = entities
.iter()
.any(|e| e.entity_type == EntityType::Technology);
assert!(has_url || has_tech, "Should have URL or Technology entity");
}
#[test]
fn test_entity_search_by_name() {
let db = make_test_db();
let store = db.memories();
let entity_store = db.entities();
store
.save(
make_input("test", "Project", "Using PostgreSQL and Redis for caching"),
None,
None,
)
.unwrap();
store
.save(
make_input("test", "Other", "Using MongoDB for storage"),
None,
None,
)
.unwrap();
let results = entity_store
.search_entities("PostgreSQL", None, 10)
.unwrap();
assert!(!results.is_empty(), "Should find PostgreSQL entity");
}
#[test]
fn test_entity_search_by_type() {
let db = make_test_db();
let store = db.memories();
let entity_store = db.entities();
store
.save(
make_input(
"test",
"Config",
"Use React and Vue and Angular for frontend",
),
None,
None,
)
.unwrap();
let results = entity_store
.search_entities("React", Some(&EntityType::Technology), 10)
.unwrap();
let _ = results.len();
}
#[test]
fn test_entity_search_empty_query() {
let db = make_test_db();
let entity_store = db.entities();
let results = entity_store
.search_entities("nonexistent_xyz_abc", None, 10)
.unwrap();
assert!(results.is_empty(), "Should not find non-existent entity");
}
#[test]
fn test_entity_links_creation() {
let db = make_test_db();
let store = db.memories();
let entity_store = db.entities();
store
.save(
make_input("test", "Mem1", "Using Rust and tokio for performance"),
None,
None,
)
.unwrap();
store
.save(
make_input("test", "Mem2", "Built with Rust and async tokio runtime"),
None,
None,
)
.unwrap();
let rust_entities = entity_store.search_entities("rust", None, 10).unwrap();
assert!(
rust_entities.len() >= 2,
"Should find Rust in both memories"
);
let tokio_entities = entity_store.search_entities("tokio", None, 10).unwrap();
assert!(
tokio_entities.len() >= 2,
"Should find tokio in both memories"
);
}
#[test]
fn test_frequent_entities() {
let db = make_test_db();
let store = db.memories();
let entity_store = db.entities();
store
.save(
make_input("test", "M1", "We use Rust for everything"),
None,
None,
)
.unwrap();
store
.save(
make_input("test", "M2", "Rust is great for systems"),
None,
None,
)
.unwrap();
store
.save(
make_input("test", "M3", "Also use Rust with serde"),
None,
None,
)
.unwrap();
let frequent = entity_store.frequent_entities("test", 5).unwrap();
assert!(!frequent.is_empty(), "Should have frequent entities");
let has_rust = frequent.iter().any(|(name, _, _)| name == "rust");
assert!(
has_rust,
"Should list Rust as frequent, got: {:?}",
frequent
);
}
#[test]
fn test_get_memory_entities() {
let db = make_test_db();
let store = db.memories();
let entity_store = db.entities();
let memory = store
.save(
make_input(
"test",
"Tech Memory",
"Built with Rust and https://github.com/example/repo",
),
None,
None,
)
.unwrap();
let entities = entity_store.get_memory_entities(memory.id).unwrap();
assert!(!entities.is_empty(), "Memory should have entities");
}
#[test]
fn test_entity_types_coverage() {
use mneme::store::entities::EntityType;
let types = vec![
EntityType::Concept,
EntityType::Person,
EntityType::Library,
EntityType::Technology,
EntityType::Framework,
EntityType::FilePath,
EntityType::Url,
EntityType::Command,
EntityType::Configuration,
EntityType::Workflow,
EntityType::Convention,
EntityType::Architecture,
];
for t in types {
let _ = format!("{:?}", t);
let _ = t.to_string();
}
}
#[test]
fn test_entity_type_display_format() {
use mneme::store::entities::EntityType;
use std::str::FromStr;
assert_eq!(EntityType::Url.to_string(), "url");
assert_eq!(EntityType::FilePath.to_string(), "file_path");
assert_eq!(EntityType::Concept.to_string(), "concept");
let parsed = EntityType::from_str("technology").unwrap();
assert_eq!(parsed, EntityType::Technology);
}