KeiSeiKit-1.0/_primitives/_rust/kei-cron-scheduler/tests/store_persistence.rs
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

147 lines
3.9 KiB
Rust

//! Persistence round-trip and crash-recovery tests for [`JobStore`].
use std::time::Duration;
use kei_cron_scheduler::job::{Job, Schedule};
use kei_cron_scheduler::store::JobStore;
use tempfile::tempdir;
fn store_in(tmp: &std::path::Path) -> JobStore {
JobStore::new(tmp.join("jobs.json"))
}
#[test]
fn empty_load_returns_empty_map() {
let dir = tempdir().unwrap();
let store = store_in(dir.path());
let map = store.load_all().unwrap();
assert!(map.is_empty());
}
#[test]
fn upsert_then_load_roundtrip() {
let dir = tempdir().unwrap();
let store = store_in(dir.path());
let job = Job::new("abc123", "do thing", Schedule::AfterDuration {
delta: Duration::from_secs(60),
});
store.upsert(job.clone()).unwrap();
let loaded = store.get("abc123").unwrap().expect("job present");
assert_eq!(loaded.id, job.id);
assert_eq!(loaded.prompt, "do thing");
}
#[test]
fn upsert_overwrites_existing() {
let dir = tempdir().unwrap();
let store = store_in(dir.path());
let mut job = Job::new("dup", "v1", Schedule::AfterDuration {
delta: Duration::from_secs(60),
});
store.upsert(job.clone()).unwrap();
job.prompt = "v2".to_string();
store.upsert(job).unwrap();
let loaded = store.get("dup").unwrap().unwrap();
assert_eq!(loaded.prompt, "v2");
}
#[test]
fn remove_drops_job() {
let dir = tempdir().unwrap();
let store = store_in(dir.path());
let job = Job::new("gone", "x", Schedule::AfterDuration {
delta: Duration::from_secs(60),
});
store.upsert(job).unwrap();
store.remove("gone").unwrap();
assert!(store.get("gone").unwrap().is_none());
}
#[test]
fn remove_missing_errors() {
let dir = tempdir().unwrap();
let store = store_in(dir.path());
assert!(store.remove("never-existed").is_err());
}
#[test]
fn restart_preserves_state() {
let dir = tempdir().unwrap();
{
let store = store_in(dir.path());
let job = Job::new(
"persist-1",
"morning brief",
Schedule::Cron {
expr: "0 9 * * *".into(),
},
);
store.upsert(job).unwrap();
}
// Drop the first store; emulate process restart.
let store2 = store_in(dir.path());
let map = store2.load_all().unwrap();
assert_eq!(map.len(), 1);
assert!(map.contains_key("persist-1"));
}
#[test]
fn multiple_jobs_round_trip() {
let dir = tempdir().unwrap();
let store = store_in(dir.path());
for i in 0..5 {
let id = format!("j{i:03}");
let job = Job::new(
id.clone(),
format!("prompt {i}"),
Schedule::Interval {
every: Duration::from_secs(60 * (i as u64 + 1)),
},
);
store.upsert(job).unwrap();
}
let map = store.load_all().unwrap();
assert_eq!(map.len(), 5);
}
#[test]
fn modify_block_is_atomic() {
let dir = tempdir().unwrap();
let store = store_in(dir.path());
store
.modify(|map| {
for i in 0..3 {
let id = format!("batch-{i}");
map.insert(
id.clone(),
Job::new(
id,
"batch insert",
Schedule::AfterDuration {
delta: Duration::from_secs(60),
},
),
);
}
Ok(())
})
.unwrap();
assert_eq!(store.load_all().unwrap().len(), 3);
}
#[test]
fn job_mark_fired_advances_run_count() {
let mut job = Job::new(
"x",
"y",
Schedule::Interval {
every: Duration::from_secs(60),
},
);
let prior_next = job.next_run_at;
job.mark_fired(chrono::Utc::now());
assert_eq!(job.run_count, 1);
assert!(job.last_run_at.is_some());
// For interval, next_run_at must move forward.
assert!(job.next_run_at > prior_next);
}