fix(infrastructure): corrige le background task store utilisé par idea_ask_agents
Le store de tâches d'arrière-plan produisait un état incohérent lors de la délégation multi-agent via idea_ask_agents. Tests ciblés ajoutés/étendus, suite verte. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
//! B2 integration tests for [`FsBackgroundTaskStore`].
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
AgentId, BackgroundTask, BackgroundTaskKind, BackgroundTaskResult, BackgroundTaskState,
|
||||
@ -33,6 +34,17 @@ impl TempDir {
|
||||
fn task_tmp_file(&self, project_id: ProjectId) -> PathBuf {
|
||||
self.task_dir().join(format!("{project_id}.json.tmp"))
|
||||
}
|
||||
|
||||
fn tmp_files(&self) -> Vec<PathBuf> {
|
||||
let Ok(entries) = std::fs::read_dir(self.task_dir()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
entries
|
||||
.filter_map(Result::ok)
|
||||
.map(|entry| entry.path())
|
||||
.filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("tmp"))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempDir {
|
||||
@ -198,6 +210,93 @@ async fn persistence_is_segmented_by_project_id() {
|
||||
assert!(!raw_p2.contains(&p1.id.to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
|
||||
async fn concurrent_creates_for_same_project_do_not_lose_tasks() {
|
||||
let tmp = TempDir::new();
|
||||
let store = Arc::new(FsBackgroundTaskStore::new(&tmp.project_path()));
|
||||
let project = project_id(10);
|
||||
let owner = agent_id(100);
|
||||
|
||||
let mut handles = Vec::new();
|
||||
for n in 1..=64 {
|
||||
let store = Arc::clone(&store);
|
||||
handles.push(tokio::spawn(async move {
|
||||
let task = queued_task(n, 10, 100, 1_000 + n as u64);
|
||||
store.create(&task).await.map(|()| task.id)
|
||||
}));
|
||||
}
|
||||
|
||||
let mut expected = Vec::new();
|
||||
for handle in handles {
|
||||
expected.push(handle.await.unwrap().unwrap());
|
||||
}
|
||||
expected.sort();
|
||||
|
||||
let reborn = FsBackgroundTaskStore::new(&tmp.project_path());
|
||||
for id in &expected {
|
||||
assert!(
|
||||
reborn.get(*id).await.unwrap().is_some(),
|
||||
"created task {id} must be durable"
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
store.registry_open_task_ids_for_agent(owner).await,
|
||||
expected,
|
||||
"registry must retain every concurrent create"
|
||||
);
|
||||
assert!(tmp.tmp_files().is_empty(), "tmp files left behind");
|
||||
assert!(tmp.task_file(project).exists());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
|
||||
async fn concurrent_saves_for_same_project_do_not_overwrite_each_other() {
|
||||
let tmp = TempDir::new();
|
||||
let store = Arc::new(FsBackgroundTaskStore::new(&tmp.project_path()));
|
||||
|
||||
for n in 1..=64 {
|
||||
store
|
||||
.create(&running_task(n, 10, 100, 1_000))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let mut handles = Vec::new();
|
||||
for n in 1..=64 {
|
||||
let store = Arc::clone(&store);
|
||||
handles.push(tokio::spawn(async move {
|
||||
let task = completed_task(n, 10, 100, 2_000 + n as u64);
|
||||
store.save(&task).await.map(|()| task.id)
|
||||
}));
|
||||
}
|
||||
|
||||
let mut expected = Vec::new();
|
||||
for handle in handles {
|
||||
expected.push(handle.await.unwrap().unwrap());
|
||||
}
|
||||
expected.sort();
|
||||
|
||||
let reborn = FsBackgroundTaskStore::new(&tmp.project_path());
|
||||
for id in &expected {
|
||||
let task = reborn.get(*id).await.unwrap().unwrap();
|
||||
assert_eq!(task.state, BackgroundTaskState::Completed);
|
||||
}
|
||||
|
||||
assert!(
|
||||
reborn
|
||||
.list_open_for_agent(agent_id(100))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty(),
|
||||
"all concurrently saved tasks must be terminal"
|
||||
);
|
||||
assert_eq!(
|
||||
reborn.list_undelivered_completions().await.unwrap().len(),
|
||||
expected.len()
|
||||
);
|
||||
assert!(tmp.tmp_files().is_empty(), "tmp files left behind");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reconcile_marks_running_without_live_handle_failed_and_pending() {
|
||||
let tmp = TempDir::new();
|
||||
|
||||
Reference in New Issue
Block a user