use super::*;
#[tokio::test]
async fn test_post_and_query_facts() {
let kb = SharedKnowledge::new();
kb.post_fact("agent-1", "file:src/main.rs", "Contains main function")
.await;
kb.post_fact("agent-2", "file:src/lib.rs", "Library root")
.await;
kb.post_fact("agent-1", "arch:pattern", "Uses MVC pattern")
.await;
let file_facts = kb.query_facts("file:").await;
assert_eq!(file_facts.len(), 2);
let arch_facts = kb.query_facts("arch:").await;
assert_eq!(arch_facts.len(), 1);
}
#[tokio::test]
async fn test_file_read_dedup() {
let kb = SharedKnowledge::new();
assert!(kb.file_already_read("src/main.rs").await.is_none());
kb.record_file_read("agent-1", "src/main.rs", "Main entry", 100)
.await;
let summary = kb.file_already_read("src/main.rs").await;
assert!(summary.is_some());
assert_eq!(summary.unwrap().agent_id, "agent-1");
}
#[tokio::test]
async fn test_conflict_detection() {
let kb = SharedKnowledge::new();
kb.record_file_modification(
"agent-1",
"src/lib.rs",
ModificationType::Created,
111,
None,
)
.await;
kb.record_file_modification(
"agent-2",
"src/lib.rs",
ModificationType::Created,
222,
None,
)
.await;
kb.record_file_modification(
"agent-1",
"src/main.rs",
ModificationType::Created,
333,
None,
)
.await;
let conflicts = kb.conflicting_files().await;
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].0, "src/lib.rs");
}
#[tokio::test]
async fn test_announcements() {
let kb = SharedKnowledge::new();
let id1 = kb.announce("agent-1", "Starting task A").await;
let _id2 = kb.announce("agent-2", "Starting task B").await;
let since = kb.announcements_since(id1).await;
assert_eq!(since.len(), 1);
assert_eq!(since[0].agent_id, "agent-2");
}
#[tokio::test]
async fn test_context_summary() {
let kb = SharedKnowledge::new();
kb.record_file_read("agent-1", "src/main.rs", "Entry point", 50)
.await;
kb.post_fact("agent-1", "arch:pattern", "Uses async/await")
.await;
let summary = kb.build_context_summary().await;
assert!(summary.contains("src/main.rs"));
assert!(summary.contains("async/await"));
}
#[tokio::test]
async fn test_relevant_summary_filters() {
let kb = SharedKnowledge::new();
kb.record_file_read("agent-1", "src/auth/middleware.rs", "Auth middleware", 200)
.await;
kb.record_file_read("agent-2", "src/db/connection.rs", "Database pool", 150)
.await;
kb.record_file_read("agent-3", "src/ui/theme.rs", "Theme colors", 80)
.await;
kb.post_fact(
"agent-1",
"file:src/auth/middleware.rs",
"Uses JWT tokens for auth",
)
.await;
kb.post_fact(
"agent-2",
"arch:database",
"PostgreSQL with connection pooling",
)
.await;
kb.post_fact("agent-3", "arch:ui", "Ratatui TUI framework")
.await;
kb.announce("agent-1", "Auth module analysis complete")
.await;
kb.announce("agent-2", "Database schema mapped").await;
let auth_summary = kb
.build_relevant_summary(
"Implement JWT authentication middleware",
&["src/auth/middleware.rs".to_string()],
5,
)
.await;
assert!(auth_summary.contains("auth"), "Should contain auth content");
assert!(
auth_summary.contains("middleware") || auth_summary.contains("JWT"),
"Should contain auth details"
);
let full_summary = kb.build_context_summary().await;
assert!(
full_summary.len() > auth_summary.len(),
"Full summary ({}) should be larger than filtered ({})",
full_summary.len(),
auth_summary.len()
);
}
#[tokio::test]
async fn test_relevant_summary_target_boost() {
let kb = SharedKnowledge::new();
kb.record_file_read("agent-1", "src/auth/jwt.rs", "JWT token handling", 100)
.await;
kb.record_file_read(
"agent-2",
"src/db/pool.rs",
"Connection pool management",
100,
)
.await;
let summary = kb
.build_relevant_summary(
"implement token validation",
&["src/auth/jwt.rs".to_string()],
3,
)
.await;
assert!(
summary.contains("jwt") || summary.contains("JWT") || summary.contains("auth"),
"Target-boosted auth file should appear in summary"
);
}
#[tokio::test]
async fn test_index_size() {
let kb = SharedKnowledge::new();
assert_eq!(kb.index_size().await, 0);
kb.post_fact("a1", "topic", "content").await;
assert_eq!(kb.index_size().await, 1);
kb.record_file_read("a1", "path.rs", "summary", 10).await;
assert_eq!(kb.index_size().await, 2);
kb.announce("a1", "hello").await;
assert_eq!(kb.index_size().await, 3);
}
#[tokio::test]
async fn test_tokenize_camel_snake() {
use crate::search::tokenizer;
let tf = tokenizer::tokenize_text("buildContextSummary shared_knowledge");
assert!(tf.contains_key("build"));
assert!(tf.contains_key("context"));
assert!(tf.contains_key("summary"));
assert!(tf.contains_key("shared"));
assert!(tf.contains_key("knowledge"));
}
#[tokio::test]
async fn test_blackboard_post_and_read() {
let kb = SharedKnowledge::new();
kb.post_to_blackboard(
"design:auth",
"Use JWT + refresh tokens",
"agent-1",
BlackboardKind::Proposal,
)
.await;
let entry = kb.read_blackboard("design:auth").await;
assert!(entry.is_some());
let entry = entry.unwrap();
assert_eq!(entry.value, "Use JWT + refresh tokens");
assert_eq!(entry.votes_for.len(), 1); }
#[tokio::test]
async fn test_blackboard_voting_consensus() {
let kb = SharedKnowledge::new();
kb.post_to_blackboard(
"design:db",
"Use PostgreSQL",
"agent-1",
BlackboardKind::Proposal,
)
.await;
kb.vote_on_proposal("design:db", "agent-2", true).await;
kb.vote_on_proposal("design:db", "agent-3", true).await;
let result = kb.check_consensus("design:db", 3).await;
assert!(result.is_some());
let result = result.unwrap();
assert!(result.approved, "Should be approved with 3/3 votes for");
assert_eq!(result.votes_for, 3);
}
#[tokio::test]
async fn test_blackboard_voting_rejection() {
let kb = SharedKnowledge::new();
kb.post_to_blackboard(
"design:cache",
"Use Redis",
"agent-1",
BlackboardKind::Proposal,
)
.await;
kb.vote_on_proposal("design:cache", "agent-2", false).await;
kb.vote_on_proposal("design:cache", "agent-3", false).await;
let result = kb.check_consensus("design:cache", 3).await;
assert!(result.is_some());
assert!(
!result.unwrap().approved,
"Should be rejected with 2/3 against"
);
}
#[tokio::test]
async fn test_blackboard_finalize() {
let kb = SharedKnowledge::new();
kb.post_to_blackboard(
"plan:step1",
"Refactor config",
"agent-1",
BlackboardKind::Proposal,
)
.await;
assert!(kb.finalize_proposal("plan:step1").await);
let entry = kb.read_blackboard("plan:step1").await.unwrap();
assert_eq!(entry.kind, BlackboardKind::Decision);
}
#[tokio::test]
async fn test_file_access_tracking() {
let kb = SharedKnowledge::new();
kb.track_file_access("agent-1", "src/main.rs").await;
kb.track_file_access("agent-1", "src/lib.rs").await;
kb.track_file_access("agent-1", "src/main.rs").await;
let files = kb.actual_files_for_agent("agent-1").await;
assert_eq!(files.len(), 2); }
#[tokio::test]
async fn test_file_prediction_accuracy() {
let kb = SharedKnowledge::new();
kb.track_file_access("agent-1", "src/main.rs").await;
kb.track_file_access("agent-1", "src/lib.rs").await;
kb.track_file_access("agent-1", "src/config.rs").await;
let predicted = vec![
"src/main.rs".to_string(),
"src/lib.rs".to_string(),
"src/app.rs".to_string(),
];
let (correct, missed, extra) = kb.file_prediction_accuracy("agent-1", &predicted).await;
assert_eq!(correct, 2); assert_eq!(missed, 1); assert_eq!(extra, 1); }
#[tokio::test]
async fn test_snapshot_and_restore() {
let kb = SharedKnowledge::new();
kb.post_fact("a1", "topic1", "content1").await;
kb.record_file_read("a1", "src/main.rs", "Entry", 100).await;
kb.announce("a1", "Hello").await;
kb.post_to_blackboard("k1", "v1", "a1", BlackboardKind::Status)
.await;
let snapshot = kb.snapshot().await;
assert_eq!(snapshot.facts.len(), 1);
assert_eq!(snapshot.files_read.len(), 1);
assert_eq!(snapshot.announcements.len(), 1);
assert_eq!(snapshot.blackboard.len(), 1);
let kb2 = SharedKnowledge::new();
kb2.restore(snapshot).await;
assert_eq!(kb2.index_size().await, 3); let facts = kb2.query_facts("topic1").await;
assert_eq!(facts.len(), 1);
assert_eq!(facts[0].content, "content1");
let bb = kb2.read_blackboard("k1").await;
assert!(bb.is_some());
assert_eq!(bb.unwrap().value, "v1");
}
#[tokio::test]
async fn test_save_and_load_file() {
let kb = SharedKnowledge::new();
kb.post_fact("a1", "topic", "test content").await;
kb.record_file_read("a1", "foo.rs", "summary", 10).await;
let tmp =
std::env::temp_dir().join(format!("collet_knowledge_test_{}.json", std::process::id()));
kb.save_to_file(&tmp).await.unwrap();
let kb2 = SharedKnowledge::new();
kb2.load_from_file(&tmp).await.unwrap();
assert_eq!(kb2.index_size().await, 2);
let facts = kb2.query_facts("topic").await;
assert_eq!(facts.len(), 1);
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_checkpoint_lifecycle() {
use crate::agent::swarm::knowledge::types::{CheckpointResult, CheckpointTask};
let tasks = vec![
CheckpointTask {
id: "t1".to_string(),
prompt: "task 1".to_string(),
role: "worker".to_string(),
dependencies: vec![],
target_files: vec!["a.rs".to_string()],
agent_name: None,
},
CheckpointTask {
id: "t2".to_string(),
prompt: "task 2".to_string(),
role: "worker".to_string(),
dependencies: vec!["t1".to_string()],
target_files: vec!["b.rs".to_string()],
agent_name: None,
},
CheckpointTask {
id: "t3".to_string(),
prompt: "task 3".to_string(),
role: "worker".to_string(),
dependencies: vec![],
target_files: vec!["c.rs".to_string()],
agent_name: None,
},
];
let mut cp = ExecutionCheckpoint::new("do stuff".to_string(), tasks);
assert!(cp.is_incomplete());
assert_eq!(cp.remaining_task_ids().len(), 3);
assert!(!cp.finished);
cp.record_completion(CheckpointResult {
id: "t1".to_string(),
success: true,
response: "done".to_string(),
modified_files: vec!["a.rs".to_string()],
tool_calls: 5,
input_tokens: 100,
output_tokens: 50,
});
assert!(cp.is_incomplete());
assert_eq!(cp.remaining_task_ids().len(), 2);
cp.record_completion(CheckpointResult {
id: "t3".to_string(),
success: false,
response: "error".to_string(),
modified_files: vec![],
tool_calls: 1,
input_tokens: 50,
output_tokens: 10,
});
assert!(cp.is_incomplete());
assert_eq!(cp.remaining_task_ids(), vec!["t2".to_string()]);
cp.record_completion(CheckpointResult {
id: "t2".to_string(),
success: true,
response: "done".to_string(),
modified_files: vec!["b.rs".to_string()],
tool_calls: 3,
input_tokens: 80,
output_tokens: 40,
});
assert!(!cp.is_incomplete());
assert!(cp.remaining_task_ids().is_empty());
cp.mark_finished();
assert!(cp.finished);
}
#[test]
fn test_checkpoint_save_load() {
use crate::agent::swarm::knowledge::checkpoint::{
clear_checkpoint, load_checkpoint, save_checkpoint,
};
use crate::agent::swarm::knowledge::types::{CheckpointResult, CheckpointTask};
let tasks = vec![CheckpointTask {
id: "t1".to_string(),
prompt: "test".to_string(),
role: "worker".to_string(),
dependencies: vec![],
target_files: vec![],
agent_name: None,
}];
let mut cp = ExecutionCheckpoint::new("msg".to_string(), tasks);
cp.record_completion(CheckpointResult {
id: "t1".to_string(),
success: true,
response: "ok".to_string(),
modified_files: vec!["x.rs".to_string()],
tool_calls: 2,
input_tokens: 10,
output_tokens: 5,
});
cp.mark_finished();
let tmp = std::env::temp_dir().join("collet_checkpoint_test.json");
save_checkpoint(&cp, &tmp).unwrap();
let loaded = load_checkpoint(&tmp).unwrap();
assert!(loaded.finished);
assert_eq!(loaded.completed_task_ids, vec!["t1"]);
assert_eq!(loaded.completed_results.len(), 1);
assert_eq!(loaded.user_message, "msg");
let _ = clear_checkpoint(&tmp);
assert!(!tmp.exists());
}
#[test]
fn test_checkpoint_staleness() {
use crate::agent::swarm::knowledge::types::CheckpointTask;
let tasks = vec![CheckpointTask {
id: "t1".to_string(),
prompt: "test".to_string(),
role: "worker".to_string(),
dependencies: vec![],
target_files: vec![],
agent_name: None,
}];
let mut cp = ExecutionCheckpoint::new("msg".to_string(), tasks);
assert!(!cp.is_stale(120_000));
cp.last_heartbeat_ms = cp.last_heartbeat_ms.saturating_sub(60_000);
assert!(cp.is_stale(30_000)); assert!(!cp.is_stale(120_000)); }