diff --git a/src/tools/builtin/job.rs b/src/tools/builtin/job.rs index a571773e..f502259f 100644 --- a/src/tools/builtin/job.rs +++ b/src/tools/builtin/job.rs @@ -1416,6 +1416,185 @@ mod tests { ); } + #[tokio::test] + async fn test_create_job_params() { + let manager = Arc::new(ContextManager::new(5)); + let tool = CreateJobTool::new(manager); + let ctx = JobContext::default(); + + let missing_title = tool + .execute(serde_json::json!({ "description": "A test job" }), &ctx) + .await; + assert!(missing_title.is_err()); + assert!( + missing_title + .unwrap_err() + .to_string() + .contains("missing 'title' parameter") + ); + + let missing_description = tool + .execute(serde_json::json!({ "title": "Test Job" }), &ctx) + .await; + assert!(missing_description.is_err()); + assert!( + missing_description + .unwrap_err() + .to_string() + .contains("missing 'description' parameter") + ); + } + + #[tokio::test] + async fn test_list_jobs_formatting() { + let manager = Arc::new(ContextManager::new(10)); + let pending_id = manager + .create_job_for_user("default", "Pending Job", "Todo") + .await + .unwrap(); + let completed_id = manager + .create_job_for_user("default", "Completed Job", "Done") + .await + .unwrap(); + let failed_id = manager + .create_job_for_user("default", "Failed Job", "Oops") + .await + .unwrap(); + manager + .create_job_for_user("other-user", "Other User Job", "Ignore") + .await + .unwrap(); + + manager + .update_context(completed_id, |ctx| { + ctx.transition_to(JobState::InProgress, None)?; + ctx.transition_to(JobState::Completed, Some("done".to_string())) + }) + .await + .unwrap() + .unwrap(); + manager + .update_context(failed_id, |ctx| { + ctx.transition_to(JobState::InProgress, None)?; + ctx.transition_to(JobState::Failed, Some("boom".to_string())) + }) + .await + .unwrap() + .unwrap(); + + let tool = ListJobsTool::new(Arc::clone(&manager)); + let ctx = JobContext::default(); + let result = tool.execute(serde_json::json!({}), &ctx).await.unwrap(); + + let jobs = result.result.get("jobs").unwrap().as_array().unwrap(); + assert_eq!(jobs.len(), 3); + assert!(jobs.iter().any(|job| { + job.get("job_id").and_then(|v| v.as_str()) == Some(&pending_id.to_string()) + && job.get("status").and_then(|v| v.as_str()) == Some("Pending") + })); + assert!(jobs.iter().any(|job| { + job.get("job_id").and_then(|v| v.as_str()) == Some(&completed_id.to_string()) + && job.get("status").and_then(|v| v.as_str()) == Some("Completed") + })); + assert!(jobs.iter().any(|job| { + job.get("job_id").and_then(|v| v.as_str()) == Some(&failed_id.to_string()) + && job.get("status").and_then(|v| v.as_str()) == Some("Failed") + })); + + let summary = result.result.get("summary").unwrap(); + assert_eq!(summary.get("total").and_then(|v| v.as_u64()), Some(3)); + assert_eq!(summary.get("pending").and_then(|v| v.as_u64()), Some(1)); + assert_eq!(summary.get("completed").and_then(|v| v.as_u64()), Some(1)); + assert_eq!(summary.get("failed").and_then(|v| v.as_u64()), Some(1)); + } + + #[tokio::test] + async fn test_job_status_transitions() { + let manager = Arc::new(ContextManager::new(5)); + let job_id = manager + .create_job_for_user("default", "Transition Job", "Track me") + .await + .unwrap(); + manager + .update_context(job_id, |ctx| { + ctx.transition_to(JobState::InProgress, Some("started".to_string()))?; + ctx.transition_to(JobState::Completed, Some("finished".to_string())) + }) + .await + .unwrap() + .unwrap(); + + let tool = JobStatusTool::new(Arc::clone(&manager)); + let ctx = JobContext::default(); + let result = tool + .execute(serde_json::json!({ "job_id": job_id.to_string() }), &ctx) + .await + .unwrap(); + + assert_eq!( + result.result.get("status").and_then(|v| v.as_str()), + Some("Completed") + ); + assert!(result.result.get("started_at").unwrap().is_string()); + assert!(result.result.get("completed_at").unwrap().is_string()); + } + + #[tokio::test] + async fn test_cancel_job_running() { + let manager = Arc::new(ContextManager::new(5)); + let job_id = manager + .create_job_for_user("default", "Running Job", "In progress") + .await + .unwrap(); + manager + .update_context(job_id, |ctx| ctx.transition_to(JobState::InProgress, None)) + .await + .unwrap() + .unwrap(); + + let tool = CancelJobTool::new(Arc::clone(&manager)); + let ctx = JobContext::default(); + let result = tool + .execute(serde_json::json!({ "job_id": job_id.to_string() }), &ctx) + .await + .unwrap(); + + assert_eq!( + result.result.get("status").and_then(|v| v.as_str()), + Some("cancelled") + ); + let updated = manager.get_context(job_id).await.unwrap(); + assert_eq!(updated.state, JobState::Cancelled); + } + + #[tokio::test] + async fn test_cancel_job_completed() { + let manager = Arc::new(ContextManager::new(5)); + let job_id = manager + .create_job_for_user("default", "Completed Job", "Already done") + .await + .unwrap(); + manager + .update_context(job_id, |ctx| { + ctx.transition_to(JobState::InProgress, None)?; + ctx.transition_to(JobState::Completed, Some("done".to_string())) + }) + .await + .unwrap() + .unwrap(); + + let tool = CancelJobTool::new(Arc::clone(&manager)); + let ctx = JobContext::default(); + let result = tool + .execute(serde_json::json!({ "job_id": job_id.to_string() }), &ctx) + .await + .unwrap(); + + let error = result.result.get("error").and_then(|v| v.as_str()).unwrap(); + assert!(error.contains("Cannot cancel job")); + assert!(error.contains("completed")); + } + #[test] fn test_resolve_project_dir_auto() { let project_id = Uuid::new_v4();