#![cfg(feature = "shared-learning")]
use terraphim_agent::shared_learning::{
MarkdownStoreConfig, SharedLearning, SharedLearningSource, SharedLearningStore, StoreConfig,
TrustLevel,
};
async fn create_store() -> SharedLearningStore {
let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.keep();
let markdown_config = MarkdownStoreConfig {
learnings_dir: path,
shared_dir_name: "shared".to_string(),
};
let store_config = StoreConfig {
similarity_threshold: 0.8,
auto_promote_l2: true,
markdown: markdown_config,
};
SharedLearningStore::open(store_config)
.await
.expect("should open store")
}
#[tokio::test]
async fn shared_list_empty_store() {
let store = create_store().await;
let all = store.list_all().await.expect("list_all should succeed");
assert!(all.is_empty(), "empty store should return no learnings");
}
#[tokio::test]
async fn shared_list_with_trust_level_filter() {
let store = create_store().await;
let l1 = SharedLearning::new(
"L1 learning".to_string(),
"content".to_string(),
SharedLearningSource::Manual,
"test-agent".to_string(),
);
store.insert(l1).await.expect("insert l1");
let mut l2 = SharedLearning::new(
"L2 learning".to_string(),
"content".to_string(),
SharedLearningSource::Manual,
"test-agent".to_string(),
);
l2.promote_to_l2();
store.insert(l2).await.expect("insert l2");
let all = store.list_all().await.expect("list_all");
assert_eq!(all.len(), 2);
let l1_only = store
.list_by_trust_level(TrustLevel::L1)
.await
.expect("list l1");
assert_eq!(l1_only.len(), 1);
assert_eq!(l1_only[0].title, "L1 learning");
let l2_only = store
.list_by_trust_level(TrustLevel::L2)
.await
.expect("list l2");
assert_eq!(l2_only.len(), 1);
assert_eq!(l2_only[0].title, "L2 learning");
let l3_only = store
.list_by_trust_level(TrustLevel::L3)
.await
.expect("list l3");
assert!(l3_only.is_empty());
}
#[tokio::test]
async fn shared_promote_l1_to_l2() {
let store = create_store().await;
let learning = SharedLearning::new(
"Promotable learning".to_string(),
"content".to_string(),
SharedLearningSource::BashHook,
"test-agent".to_string(),
);
let id = learning.id.clone();
store.insert(learning).await.expect("insert");
store.promote_to_l2(&id).await.expect("promote to l2");
let fetched = store.get(&id).await.expect("get after promote");
assert_eq!(fetched.trust_level, TrustLevel::L2);
assert!(fetched.promoted_at.is_some());
}
#[tokio::test]
async fn shared_promote_to_l3() {
let store = create_store().await;
let learning = SharedLearning::new(
"L3 candidate".to_string(),
"content".to_string(),
SharedLearningSource::Manual,
"test-agent".to_string(),
);
let id = learning.id.clone();
store.insert(learning).await.expect("insert");
store.promote_to_l3(&id).await.expect("promote to l3");
let fetched = store.get(&id).await.expect("get after promote");
assert_eq!(fetched.trust_level, TrustLevel::L3);
}
#[tokio::test]
async fn shared_stats_counts() {
let store = create_store().await;
for i in 0..2 {
let l = SharedLearning::new(
format!("L1 item {}", i),
"content".to_string(),
SharedLearningSource::Manual,
"agent".to_string(),
);
store.insert(l).await.expect("insert l1");
}
let mut l2 = SharedLearning::new(
"L2 item".to_string(),
"content".to_string(),
SharedLearningSource::Manual,
"agent".to_string(),
);
l2.promote_to_l2();
store.insert(l2).await.expect("insert l2");
let all = store.list_all().await.expect("list_all");
assert_eq!(all.len(), 3);
let l1_count = all
.iter()
.filter(|l| l.trust_level == TrustLevel::L1)
.count();
let l2_count = all
.iter()
.filter(|l| l.trust_level == TrustLevel::L2)
.count();
let l3_count = all
.iter()
.filter(|l| l.trust_level == TrustLevel::L3)
.count();
assert_eq!(l1_count, 2);
assert_eq!(l2_count, 1);
assert_eq!(l3_count, 0);
}
#[tokio::test]
async fn shared_import_creates_l1_entries() {
let store = create_store().await;
let command = "git push --force".to_string();
let error = "remote: error: denied".to_string();
let tags = vec!["git".to_string(), "push".to_string()];
let shared = SharedLearning::new(
command.clone(),
error.clone(),
SharedLearningSource::BashHook,
"cli-import".to_string(),
)
.with_original_command(command)
.with_error_context(error)
.with_keywords(tags);
store
.insert(shared)
.await
.expect("insert imported learning");
let all = store.list_all().await.expect("list_all");
assert_eq!(all.len(), 1);
assert_eq!(all[0].trust_level, TrustLevel::L1);
assert_eq!(all[0].source_agent, "cli-import");
assert!(all[0].original_command.is_some());
assert!(all[0].error_context.is_some());
assert_eq!(all[0].keywords.len(), 2);
}
#[tokio::test]
async fn shared_trust_level_parse() {
assert_eq!("l1".parse::<TrustLevel>().unwrap(), TrustLevel::L1);
assert_eq!("L2".parse::<TrustLevel>().unwrap(), TrustLevel::L2);
assert_eq!("l3".parse::<TrustLevel>().unwrap(), TrustLevel::L3);
assert!("invalid".parse::<TrustLevel>().is_err());
}
#[tokio::test]
async fn shared_store_survives_restart() {
let temp_dir = tempfile::tempdir().unwrap();
let markdown_config = MarkdownStoreConfig {
learnings_dir: temp_dir.path().to_path_buf(),
shared_dir_name: "shared".to_string(),
};
let store_config = StoreConfig {
similarity_threshold: 0.8,
auto_promote_l2: true,
markdown: markdown_config.clone(),
};
let store = SharedLearningStore::open(store_config)
.await
.expect("should open store");
let mut learning = SharedLearning::new(
"Restart Test".to_string(),
"Testing restart persistence.".to_string(),
SharedLearningSource::Manual,
"test-agent".to_string(),
);
learning.quality.applied_count = 3;
learning.quality.effective_count = 3;
learning.quality.agent_names = vec!["agent1".to_string(), "agent2".to_string()];
let id = learning.id.clone();
store.insert(learning).await.expect("insert");
store.promote_to_l2(&id).await.expect("promote to l2");
drop(store);
let store_config2 = StoreConfig {
similarity_threshold: 0.8,
auto_promote_l2: true,
markdown: markdown_config,
};
let reopened = SharedLearningStore::open(store_config2)
.await
.expect("should reopen store");
let retrieved = reopened.get(&id).await.expect("get after reopen");
assert_eq!(retrieved.trust_level, TrustLevel::L2);
assert_eq!(retrieved.title, "Restart Test");
assert!(retrieved.promoted_at.is_some());
assert_eq!(retrieved.quality.applied_count, 3);
assert_eq!(retrieved.quality.effective_count, 3);
assert_eq!(retrieved.quality.agent_names.len(), 2);
}
#[tokio::test]
async fn shared_store_dedups_on_restart() {
let temp_dir = tempfile::tempdir().unwrap();
let markdown_config = MarkdownStoreConfig {
learnings_dir: temp_dir.path().to_path_buf(),
shared_dir_name: "shared".to_string(),
};
let store_config = StoreConfig {
similarity_threshold: 0.8,
auto_promote_l2: true,
markdown: markdown_config.clone(),
};
let store = SharedLearningStore::open(store_config)
.await
.expect("should open store");
let learning = SharedLearning::new(
"Dedup Test".to_string(),
"Testing deduplication on restart.".to_string(),
SharedLearningSource::Manual,
"test-agent".to_string(),
);
let id = learning.id.clone();
store.insert(learning).await.expect("insert");
let canonical_path = temp_dir
.path()
.join("test-agent")
.join(format!("{}.md", id));
let shared_dir = temp_dir.path().join("shared");
tokio::fs::create_dir_all(&shared_dir).await.unwrap();
let shared_path = shared_dir.join(format!("test-agent-{}.md", id));
tokio::fs::copy(&canonical_path, &shared_path)
.await
.expect("copy to shared dir");
drop(store);
let store_config2 = StoreConfig {
similarity_threshold: 0.8,
auto_promote_l2: true,
markdown: markdown_config,
};
let reopened = SharedLearningStore::open(store_config2)
.await
.expect("should reopen store");
let all = reopened.list_all().await.expect("list_all");
assert_eq!(
all.len(),
1,
"should deduplicate canonical and shared copies"
);
assert_eq!(all[0].id, id);
}