Files
k-mood/crates/adapters/sqlite/tests/two_writers_test.rs
Gabriel Kaszewski 23d052278a
All checks were successful
CI / ci (push) Successful in 19m38s
changes
2026-08-26 20:58:14 +02:00

119 lines
3.5 KiB
Rust

use domain::entry::MoodEntryId;
use domain::job::{JobKind, JobSubject};
use domain::ports::JobQueueCommandPort;
use sqlite::repositories::SqliteJobQueueRepository;
const KIND: JobKind = JobKind::BackfillRecordingIdentity;
fn a_file() -> String {
let name = format!("k-mood-two-writers-{}.sqlite", uuid::Uuid::new_v4());
std::env::temp_dir()
.join(name)
.to_string_lossy()
.to_string()
}
#[tokio::test]
async fn two_processes_writing_one_file_both_succeed() {
let path = a_file();
let url = format!("sqlite://{path}");
let server = sqlite::create_pool(&url).await.unwrap();
sqlite::run_migrations(&server).await.unwrap();
let worker = sqlite::create_pool(&url).await.unwrap();
let enqueuing = tokio::spawn({
let queue = SqliteJobQueueRepository::new(server.clone());
async move {
for _ in 0..80 {
queue
.enqueue(KIND, &JobSubject::Entry(MoodEntryId::generate()))
.await
.expect("the server's write should not be refused");
}
}
});
let working = tokio::spawn({
let queue = SqliteJobQueueRepository::new(worker.clone());
async move {
let mut claimed = 0;
for _ in 0..80 {
let batch = queue
.claim(KIND, 5)
.await
.expect("the worker's write should not be refused");
for job in &batch {
queue
.finish(job.id())
.await
.expect("finishing should not be refused");
}
claimed += batch.len();
}
claimed
}
});
enqueuing.await.unwrap();
let claimed = working.await.unwrap();
assert!(claimed > 0, "the worker never saw any of the server's work");
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_file(format!("{path}-wal"));
let _ = std::fs::remove_file(format!("{path}-shm"));
}
#[tokio::test]
async fn a_writer_waits_for_a_held_lock_rather_than_being_refused() {
let path = a_file();
let url = format!("sqlite://{path}");
let holder = sqlite::create_pool(&url).await.unwrap();
sqlite::run_migrations(&holder).await.unwrap();
let other = sqlite::create_pool(&url).await.unwrap();
let mut held = holder.begin().await.unwrap();
sqlx::query(
"INSERT INTO jobs (id, kind, subject, status, attempts, last_error, enqueued_at, updated_at)
VALUES (?, 'backfillRecordingIdentity', ?, 'pending', 0, NULL, ?, ?)",
)
.bind(uuid::Uuid::new_v4().to_string())
.bind(uuid::Uuid::new_v4().to_string())
.bind(chrono::Utc::now().to_rfc3339())
.bind(chrono::Utc::now().to_rfc3339())
.execute(&mut *held)
.await
.unwrap();
let waiting = tokio::spawn({
let queue = SqliteJobQueueRepository::new(other);
async move {
queue
.enqueue(KIND, &JobSubject::Entry(MoodEntryId::generate()))
.await
}
});
tokio::time::sleep(std::time::Duration::from_millis(400)).await;
held.commit().await.unwrap();
let written = waiting.await.unwrap();
assert!(
written.is_ok(),
"the second writer was refused instead of waiting: {written:?}"
);
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_file(format!("{path}-wal"));
let _ = std::fs::remove_file(format!("{path}-shm"));
}