#[cfg(test)]
mod tests {
use crate::mcp::core::plan::{clear_plan_data, execute_plan};
use crate::mcp::McpToolCall;
use serde_json::json;
use serial_test::serial;
fn create_plan_call(
command: &str,
additional_params: Option<serde_json::Value>,
) -> McpToolCall {
let mut params = serde_json::Map::new();
params.insert("command".to_string(), json!(command));
if let Some(serde_json::Value::Object(map)) = additional_params {
for (key, value) in map {
params.insert(key, value);
}
}
McpToolCall {
tool_name: "plan".to_string(),
parameters: serde_json::Value::Object(params),
tool_id: "test-id".to_string(),
}
}
#[serial]
#[tokio::test]
async fn test_plan_start_command_success() {
let _ = clear_plan_data().await;
let call = create_plan_call(
"start",
Some(json!({
"content": "Test Plan",
"tasks": [
{"title": "Task 1", "description": "First task description"},
{"title": "Task 2", "description": "Second task description"}
]
})),
);
let result = execute_plan(&call).await.unwrap();
assert!(!result.is_error());
let content = result.extract_content();
assert!(content.contains("Test Plan"));
assert!(content.contains("Task 1"));
assert!(content.contains("Task 2"));
assert!(content.contains("CURRENT: Task 1/2 - Task 1"));
let _ = clear_plan_data().await;
}
#[serial]
#[tokio::test]
async fn test_plan_start_command_validation_errors() {
let _ = clear_plan_data().await;
let call = create_plan_call(
"start",
Some(json!({
"content": "Test Plan"
})),
);
let result = execute_plan(&call).await.unwrap();
assert!(result.is_error());
assert!(result
.extract_content()
.contains("Missing required parameter 'tasks'"));
let call = create_plan_call(
"start",
Some(json!({
"content": "Test Plan",
"tasks": []
})),
);
let result = execute_plan(&call).await.unwrap();
assert!(result.is_error());
assert!(result
.extract_content()
.contains("Tasks array cannot be empty"));
let call = create_plan_call(
"start",
Some(json!({
"content": "Test Plan",
"tasks": [{"description": "No title here"}]
})),
);
let result = execute_plan(&call).await.unwrap();
assert!(result.is_error());
assert!(result
.extract_content()
.contains("missing required 'title' field"));
let call = create_plan_call(
"start",
Some(json!({
"content": "Test Plan",
"tasks": [{"title": "Task 1"}]
})),
);
let result = execute_plan(&call).await.unwrap();
assert!(result.is_error());
assert!(result
.extract_content()
.contains("missing required 'description' field"));
}
#[serial]
#[tokio::test]
async fn test_plan_step_command() {
let _ = clear_plan_data().await;
let start_call = create_plan_call(
"start",
Some(json!({
"title": "Test Plan",
"tasks": [
{"title": "Task 1", "description": "First task description"},
{"title": "Task 2", "description": "Second task description"}
]
})),
);
let _ = execute_plan(&start_call).await;
let step_call = create_plan_call(
"step",
Some(json!({
"content": "Working on authentication logic"
})),
);
let result = execute_plan(&step_call).await.unwrap();
assert!(!result.is_error());
assert!(result
.extract_content()
.contains("Step details added to Task"));
let get_call = create_plan_call("step", None);
let result = execute_plan(&get_call).await.unwrap();
assert!(!result.is_error());
let content = result.extract_content();
assert!(content.contains("CURRENT TASK"));
assert!(content.contains("Working on authentication logic"));
let empty_call = create_plan_call(
"step",
Some(json!({
"content": ""
})),
);
let result = execute_plan(&empty_call).await.unwrap();
assert!(result.is_error());
assert!(result
.extract_content()
.contains("Content parameter cannot be empty"));
let _ = clear_plan_data().await;
}
#[serial]
#[tokio::test]
async fn test_plan_list_command() {
let _ = clear_plan_data().await;
let start_call = create_plan_call(
"start",
Some(json!({
"content": "Development Tasks",
"tasks": [
{"title": "Design", "description": "Design the system"},
{"title": "Implement", "description": "Implement the features"},
{"title": "Test", "description": "Test the implementation"},
{"title": "Deploy", "description": "Deploy to production"}
]
})),
);
let _ = execute_plan(&start_call).await;
let next_call = create_plan_call(
"next",
Some(json!({
"content": "Design completed"
})),
);
let _ = execute_plan(&next_call).await;
let list_call = create_plan_call("list", None);
let result = execute_plan(&list_call).await.unwrap();
assert!(!result.is_error());
let content = result.extract_content();
assert!(content.contains("Development Tasks"));
assert!(content.contains("✅ 1. Design"));
assert!(content.contains("🔄 2. Implement (IN PROGRESS)"));
assert!(content.contains("⏳ 3. Test"));
assert!(content.contains("⏳ 4. Deploy"));
let _ = clear_plan_data().await;
}
#[serial]
#[tokio::test]
async fn test_plan_done_command() {
let _ = clear_plan_data().await;
let start_call = create_plan_call(
"start",
Some(json!({
"content": "Simple Task",
"tasks": [{"title": "Complete project", "description": "Finish all remaining work"}]
})),
);
let _ = execute_plan(&start_call).await;
let done_call = create_plan_call(
"done",
Some(json!({
"content": "Project completed successfully"
})),
);
let result = execute_plan(&done_call).await.unwrap();
assert!(!result.is_error());
let content = result.extract_content();
assert!(content.contains("PLAN COMPLETED"));
assert!(content.contains("Simple Task"));
let _ = clear_plan_data().await;
}
#[serial]
#[tokio::test]
async fn test_plan_reset_command() {
let _ = clear_plan_data().await;
let start_call = create_plan_call(
"start",
Some(json!({
"title": "Test Plan",
"tasks": [{"title": "Task 1", "description": "First task description"}]
})),
);
let _ = execute_plan(&start_call).await;
let reset_call = create_plan_call("reset", None);
let result = execute_plan(&reset_call).await.unwrap();
assert!(!result.is_error());
assert!(result
.extract_content()
.contains("Plan data cleared successfully"));
let list_call = create_plan_call("list", None);
let result = execute_plan(&list_call).await.unwrap();
assert!(result.is_error());
assert!(result.extract_content().contains("No active plan"));
}
#[serial]
#[tokio::test]
async fn test_plan_invalid_command() {
let call = create_plan_call("invalid_command", None);
let result = execute_plan(&call).await.unwrap();
assert!(result.is_error());
assert!(result
.extract_content()
.contains("Unknown command 'invalid_command'"));
}
#[serial]
#[tokio::test]
async fn test_plan_step_vs_next_behavior() {
let _ = clear_plan_data().await;
let start_call = create_plan_call(
"start",
Some(json!({
"title": "Behavior Test",
"tasks": [
{"title": "Task 1", "description": "First task description"},
{"title": "Task 2", "description": "Second task description"}
]
})),
);
let _ = execute_plan(&start_call).await;
let step_call = create_plan_call(
"step",
Some(json!({
"content": "Working on task 1"
})),
);
let _ = execute_plan(&step_call).await;
let list_call = create_plan_call("list", None);
let result = execute_plan(&list_call).await.unwrap();
let content = result.extract_content();
assert!(content.contains("🔄 1. Task 1 (IN PROGRESS)")); assert!(content.contains("⏳ 2. Task 2"));
let next_call = create_plan_call(
"next",
Some(json!({
"content": "Task 1 completed"
})),
);
let _ = execute_plan(&next_call).await;
let list_call = create_plan_call("list", None);
let result = execute_plan(&list_call).await.unwrap();
let content = result.extract_content();
assert!(content.contains("✅ 1. Task 1")); assert!(content.contains("🔄 2. Task 2 (IN PROGRESS)"));
let _ = clear_plan_data().await;
}
#[serial]
#[tokio::test]
async fn test_plan_start_prevents_overwrite() {
let _ = clear_plan_data().await;
let start_call1 = create_plan_call(
"start",
Some(json!({
"content": "First Plan",
"tasks": [
{"title": "Task A", "description": "Task A description"},
{"title": "Task B", "description": "Task B description"}
]
})),
);
let result1 = execute_plan(&start_call1).await.unwrap();
assert!(!result1.is_error());
let content1 = result1.extract_content();
assert!(content1.contains("First Plan"));
assert!(content1.contains("Task A"));
let step_call = create_plan_call(
"step",
Some(json!({
"content": "Working on Task A"
})),
);
let _ = execute_plan(&step_call).await;
let start_call2 = create_plan_call(
"start",
Some(json!({
"content": "Second Plan",
"tasks": [
{"title": "Task X", "description": "Task X description"},
{"title": "Task Y", "description": "Task Y description"},
{"title": "Task Z", "description": "Task Z description"}
]
})),
);
let result2 = execute_plan(&start_call2).await.unwrap();
assert!(result2.is_error()); let error_content = result2.extract_content();
assert!(error_content.contains("Active plan already exists"));
assert!(error_content.contains("'done' to complete current plan"));
assert!(error_content.contains("'reset' to clear it"));
assert!(error_content.contains("'list' to view current progress"));
let list_call = create_plan_call("list", None);
let result = execute_plan(&list_call).await.unwrap();
let content = result.extract_content();
assert!(content.contains("First Plan"));
assert!(content.contains("Task A"));
assert!(!content.contains("Second Plan")); assert!(!content.contains("Task X"));
let _ = clear_plan_data().await;
}
#[serial]
#[tokio::test]
async fn test_plan_start_after_done_works() {
let _ = clear_plan_data().await;
let start_call1 = create_plan_call(
"start",
Some(json!({
"content": "First Plan",
"tasks": [{"title": "Task A", "description": "Task A description"}]
})),
);
let _ = execute_plan(&start_call1).await;
let done_call = create_plan_call(
"done",
Some(json!({
"content": "First plan completed"
})),
);
let _ = execute_plan(&done_call).await;
let start_call2 = create_plan_call(
"start",
Some(json!({
"content": "Second Plan",
"tasks": [
{"title": "Task X", "description": "Task X description"},
{"title": "Task Y", "description": "Task Y description"}
]
})),
);
let result2 = execute_plan(&start_call2).await.unwrap();
assert!(!result2.is_error()); let content2 = result2.extract_content();
assert!(content2.contains("Second Plan"));
assert!(content2.contains("Task X"));
let _ = clear_plan_data().await;
}
#[serial]
#[tokio::test]
async fn test_plan_start_after_reset_works() {
let _ = clear_plan_data().await;
let start_call1 = create_plan_call(
"start",
Some(json!({
"content": "First Plan",
"tasks": [
{"title": "Task A", "description": "Task A description"},
{"title": "Task B", "description": "Task B description"}
]
})),
);
let _ = execute_plan(&start_call1).await;
let step_call = create_plan_call(
"step",
Some(json!({
"content": "Working on Task A"
})),
);
let _ = execute_plan(&step_call).await;
let reset_call = create_plan_call("reset", None);
let _ = execute_plan(&reset_call).await;
let start_call2 = create_plan_call(
"start",
Some(json!({
"content": "Second Plan",
"tasks": [
{"title": "Task X", "description": "Task X description"},
{"title": "Task Y", "description": "Task Y description"}
]
})),
);
let result2 = execute_plan(&start_call2).await.unwrap();
assert!(!result2.is_error()); let content2 = result2.extract_content();
assert!(content2.contains("Second Plan"));
assert!(content2.contains("Task X"));
let _ = clear_plan_data().await;
}
}