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:
Hunter Bown
2026-05-01 01:51:35 -05:00
parent 9a39e69c4d
commit c424f3ec08
2 changed files with 111 additions and 0 deletions
+48
View File
@@ -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();
+63
View File
@@ -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}"
);
}
}