test(job): cover job tool validation and state transitions (#681)

Add focused coverage for create/list/status/cancel job tools so validation errors, summary formatting, and cancellation behavior stay stable. This locks in the current user-facing responses for running and completed jobs without changing production code.

Made-with: Cursor
This commit is contained in:
Protocol Zero
2026-03-09 03:49:58 +00:00
committed by GitHub
parent 553c306c52
commit 98e9a40762
+179
View File
@@ -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();