loopd: job files, run ledger, jobs-invalid

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
2026-10-03 14:20:19 -07:00
co-authored by ornith-1.5-35b-a3b
parent 587b3924d7
commit 670b5bbabe
6 changed files with 377 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
//! Tests for job files and the run ledger. Do not edit.
use bxcore::{SessionId, Timestamp};
use loopd::jobs::{self, JobsError, RunStatus};
fn ts() -> Timestamp {
Timestamp::parse("2026-10-03T07:00:00.000Z").unwrap()
}
struct Scratch {
dir: std::path::PathBuf,
}
impl Scratch {
fn new() -> Scratch {
let n = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let dir = std::env::temp_dir().join(format!("loopd-jobs-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
Scratch { dir }
}
}
impl Drop for Scratch {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.dir);
}
}
#[test]
fn a_valid_job_file_loads() {
let home = Scratch::new();
let jobs = home.dir.join("jobs");
std::fs::create_dir(&jobs).unwrap();
std::fs::write(
jobs.join("mail.toml"),
"schedule = \"0 7 * * *\"\nprompt = \"Check mail.\"\n",
)
.unwrap();
let loaded = jobs::load(&jobs).unwrap();
assert_eq!(loaded.len(), 1);
assert_eq!(loaded[0].name, "mail");
assert_eq!(loaded[0].schedule, "0 7 * * *");
assert_eq!(loaded[0].prompt, "Check mail.");
}
#[test]
fn unknown_keys_are_errors_with_the_runbook() {
let home = Scratch::new();
let jobs = home.dir.join("jobs");
std::fs::create_dir(&jobs).unwrap();
std::fs::write(
jobs.join("mail.toml"),
"schedule = \"* * * * *\"\nprompt = \"x\"\nwhen = \"now\"\n",
)
.unwrap();
let err = jobs::load(&jobs).unwrap_err();
let s = err.to_string();
assert!(s.contains("mail.toml"), "{s}");
assert!(s.ends_with("see docs/runbook.md#jobs-invalid"), "{s}");
}
#[test]
fn a_bad_name_is_an_error() {
let home = Scratch::new();
let jobs = home.dir.join("jobs");
std::fs::create_dir(&jobs).unwrap();
std::fs::write(
jobs.join("Mail.toml"),
"schedule = \"* * * * *\"\nprompt = \"x\"\n",
)
.unwrap();
let err = jobs::load(&jobs).unwrap_err();
assert!(matches!(err, JobsError::Name(_)), "{err:?}");
}
#[test]
fn session_id_fits_and_is_lowercase() {
let id = jobs::session_id("mail", ts()).unwrap();
assert_eq!(id.as_str(), "job-mail-20261003t0700");
SessionId::new(id.as_str()).unwrap();
}
#[test]
fn a_name_too_long_for_a_session_id_is_an_error() {
let long = "n".repeat(50);
let err = jobs::session_id(&long, ts()).unwrap_err();
assert!(matches!(err, JobsError::Name(_)), "{err:?}");
}
#[test]
fn a_missing_jobs_directory_is_empty() {
let home = Scratch::new();
let loaded = jobs::load(&home.dir.join("jobs")).unwrap();
assert!(loaded.is_empty());
}
#[test]
fn leftover_started_is_closed_as_failed() {
let home = Scratch::new();
let path = home.dir.join("runs.jsonl");
let session = jobs::session_id("mail", ts()).unwrap();
jobs::append(
&path,
&jobs::Run {
time: ts(),
name: "mail".to_string(),
session: session.clone(),
status: RunStatus::Started,
},
)
.unwrap();
let n = jobs::close_leftovers(&path, ts()).unwrap();
assert_eq!(n, 1);
assert!(jobs::live_session(&path, "mail").unwrap().is_none());
}
#[test]
fn a_started_run_without_a_close_is_live() {
let home = Scratch::new();
let path = home.dir.join("runs.jsonl");
let session = jobs::session_id("mail", ts()).unwrap();
jobs::append(
&path,
&jobs::Run {
time: ts(),
name: "mail".to_string(),
session: session.clone(),
status: RunStatus::Started,
},
)
.unwrap();
assert_eq!(
jobs::live_session(&path, "mail").unwrap().as_ref(),
Some(&session)
);
jobs::append(
&path,
&jobs::Run {
time: ts(),
name: "mail".to_string(),
session,
status: RunStatus::Finished,
},
)
.unwrap();
assert!(jobs::live_session(&path, "mail").unwrap().is_none());
}