test(persistence): cover schema-version rejection in session + runtime_threads (#233)
The architecture promises that session_manager, runtime_threads, and task_manager reject persisted state from a newer schema_version on load, so a downgraded binary fails loud instead of silently truncating or corrupting data. Existing tests covered: - session_manager::test_checkpoint_rejects_newer_schema - task_manager (newer task schema rejection) - runtime_threads::store_load_thread_rejects_newer_schema_version Adds the missing coverage for the other persistence paths: - session_manager::test_load_session_rejects_newer_schema - session_manager::test_load_offline_queue_rejects_newer_schema - runtime_threads::store_load_turn_rejects_newer_schema_version - runtime_threads::store_load_item_rejects_newer_schema_version Each writes a JSON file with schema_version = CURRENT + 1 (or 999), loads through the public API, and asserts the error message contains "newer than supported". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2824,6 +2824,54 @@ mod tests {
|
||||
assert_eq!(CURRENT_RUNTIME_SCHEMA_VERSION, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_load_turn_rejects_newer_schema_version() {
|
||||
let dir = test_runtime_dir();
|
||||
let store = RuntimeThreadStore::open(dir.clone()).expect("open store");
|
||||
|
||||
let mut turn = sample_turn("thr_t", "trn_future", RuntimeTurnStatus::InProgress);
|
||||
turn.schema_version = CURRENT_RUNTIME_SCHEMA_VERSION + 1;
|
||||
|
||||
let path = store.turns_dir.join(format!("{}.json", turn.id));
|
||||
std::fs::create_dir_all(path.parent().unwrap()).expect("mkdirs");
|
||||
std::fs::write(&path, serde_json::to_string(&turn).expect("serialize turn"))
|
||||
.expect("write turn");
|
||||
|
||||
let err = store
|
||||
.load_turn(&turn.id)
|
||||
.expect_err("load_turn must reject newer schema");
|
||||
assert!(
|
||||
format!("{err:#}").contains("newer than supported"),
|
||||
"got: {err:#}"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_load_item_rejects_newer_schema_version() {
|
||||
let dir = test_runtime_dir();
|
||||
let store = RuntimeThreadStore::open(dir.clone()).expect("open store");
|
||||
|
||||
let mut item = sample_item("trn_t", "itm_future", TurnItemLifecycleStatus::InProgress);
|
||||
item.schema_version = CURRENT_RUNTIME_SCHEMA_VERSION + 1;
|
||||
|
||||
let path = store.items_dir.join(format!("{}.json", item.id));
|
||||
std::fs::create_dir_all(path.parent().unwrap()).expect("mkdirs");
|
||||
std::fs::write(&path, serde_json::to_string(&item).expect("serialize item"))
|
||||
.expect("write item");
|
||||
|
||||
let err = store
|
||||
.load_item(&item.id)
|
||||
.expect_err("load_item must reject newer schema");
|
||||
assert!(
|
||||
format!("{err:#}").contains("newer than supported"),
|
||||
"got: {err:#}"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforce_lru_capacity_does_not_loop_when_all_threads_are_active() {
|
||||
let mut active = ActiveThreads::default();
|
||||
|
||||
@@ -809,4 +809,67 @@ mod tests {
|
||||
let err = manager.load_checkpoint().expect_err("should reject schema");
|
||||
assert!(err.to_string().contains("newer than supported"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_session_rejects_newer_schema() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let sessions_dir = tmp.path().join("sessions");
|
||||
let manager = SessionManager::new(sessions_dir.clone()).expect("new");
|
||||
|
||||
let id = "future-session";
|
||||
let path = sessions_dir.join(format!("{id}.json"));
|
||||
fs::write(
|
||||
&path,
|
||||
r#"{
|
||||
"schema_version": 999,
|
||||
"metadata": {
|
||||
"id": "future-session",
|
||||
"title": "future",
|
||||
"created_at": "2026-01-01T00:00:00Z",
|
||||
"updated_at": "2026-01-01T00:00:00Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "m",
|
||||
"workspace": "/tmp",
|
||||
"mode": null
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}"#,
|
||||
)
|
||||
.expect("write session");
|
||||
|
||||
let err = manager.load_session(id).expect_err("should reject schema");
|
||||
assert!(
|
||||
err.to_string().contains("newer than supported"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_offline_queue_rejects_newer_schema() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let sessions_dir = tmp.path().join("sessions");
|
||||
let manager = SessionManager::new(sessions_dir.clone()).expect("new");
|
||||
let checkpoints = sessions_dir.join("checkpoints");
|
||||
fs::create_dir_all(&checkpoints).expect("create checkpoints dir");
|
||||
let path = checkpoints.join("offline_queue.json");
|
||||
fs::write(
|
||||
&path,
|
||||
r#"{
|
||||
"schema_version": 999,
|
||||
"messages": [],
|
||||
"draft": null
|
||||
}"#,
|
||||
)
|
||||
.expect("write queue");
|
||||
|
||||
let err = manager
|
||||
.load_offline_queue_state()
|
||||
.expect_err("should reject schema");
|
||||
assert!(
|
||||
err.to_string().contains("newer than supported"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user