use std::sync::Arc;
use apalis::prelude::Data;
use chrono::Utc;
use gradatum_core::{
identity::NoteId, scope::VaultId, CurateSpec, GradatumJob, Job, JobClass, JobLifecycle,
JobLineage, JobMode, JobPriority, JobRecord, JobRetry, JobScheduling, JobScope, JobSpec,
JobStatus, TriggerSource,
};
use gradatum_db_sqlite::{apply_sqlite_pragmas, run_migrations, SqliteQueueStore};
use gradatum_index::SqliteIndex;
use gradatum_vault::Vault;
use gradatum_worker::apalis_handlers::handle_curate;
use sqlx::SqlitePool;
use tempfile::TempDir;
use ulid::Ulid;
async fn test_store() -> SqliteQueueStore {
let pool = SqlitePool::connect("sqlite::memory:")
.await
.expect("pool in-memory");
apply_sqlite_pragmas(&pool).await.expect("pragmas");
run_migrations(&pool).await.expect("migrations");
SqliteQueueStore::new(pool)
}
struct CurateFixture {
vault: Arc<Vault>,
index: Arc<SqliteIndex>,
_tmp: TempDir,
}
impl CurateFixture {
async fn new() -> Self {
let tmp = TempDir::new().expect("TempDir");
let vault = Arc::new(
Vault::create(tmp.path().join("vault").as_path(), VaultId::new("main"))
.await
.expect("Vault::create"),
);
let index = vault.index().clone();
CurateFixture {
vault,
index,
_tmp: tmp,
}
}
}
fn curate_job_vault_write(prealloc: Ulid, body: &str, tenant_id: &str) -> GradatumJob {
let now = Utc::now();
let class = JobClass::Agent;
GradatumJob {
priority: JobPriority::default_for(&class).as_u8(),
record: JobRecord {
id: Ulid::new(),
spec: JobSpec {
kind: Job::Curate(CurateSpec {
note_id: prealloc,
tenant_id: tenant_id.to_string(),
title: Some("[DECISIONS] Titre préalloué item C".to_string()),
body: Some(body.to_string()),
..Default::default()
}),
class,
mode: JobMode::Batch,
scope: JobScope::VaultWide,
priority: JobPriority::High,
},
scheduling: JobScheduling {
trigger: TriggerSource::Demand,
scheduled_at: now,
await_jobs: vec![],
deadline: None,
cron_expr: None,
},
lifecycle: JobLifecycle {
status: JobStatus::Running,
created_at: now,
started_at: Some(now),
completed_at: None,
lease_until: None,
result: None,
},
retry: JobRetry::default(),
lineage: JobLineage {
triggered_by: None,
parent_job: None,
pipeline_id: None,
pipeline_step: None,
children: vec![],
cost_usd: None,
},
},
}
}
#[tokio::test]
async fn handle_curate_vault_write_honors_prealloc_note_id() {
let fixture = CurateFixture::new().await;
let store = Arc::new(test_store().await);
let queue: Arc<dyn gradatum_core::QueueStore + Send + Sync> = Arc::clone(&store) as _;
let curator = Arc::new(gradatum_curator::CuratorPipeline::new());
let prealloc = Ulid::new();
let job = curate_job_vault_write(
prealloc,
"## Titre\nbody suffisant pour le curator.",
"main",
);
let out = handle_curate(
job,
Data::new(Arc::clone(&fixture.vault)),
Data::new(Arc::clone(&curator) as Arc<dyn gradatum_curator::CuratorProcess + Send + Sync>),
Data::new(Arc::clone(&fixture.index)),
Data::new(Arc::clone(&queue)),
)
.await
.expect("handle_curate");
assert!(
!out.notes_created.is_empty(),
"handle_curate doit créer la note pour un job vault_write — output={out:?}"
);
let read = fixture.vault.read_note(NoteId(prealloc)).await;
assert!(
read.is_ok(),
"la note doit être lisible à l'ULID préalloué — err={:?}",
read.err()
);
assert_eq!(
read.unwrap().id,
NoteId(prealloc),
"l'id stocké doit être l'id préalloué"
);
}