From 3fbd64f131aa50bf9043d81823a046eafbffe570 Mon Sep 17 00:00:00 2001 From: Eric Wendland Date: Sun, 5 Jul 2026 23:40:39 +0200 Subject: [PATCH] test: add upgrade home fixture coverage --- crates/geth/tests/bootstrap.rs | 100 +++++++++++++++++++++++++++ docs/production-readiness-roadmap.md | 11 +-- docs/release-support-policy.md | 13 ++++ 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/crates/geth/tests/bootstrap.rs b/crates/geth/tests/bootstrap.rs index adbc425..cc965c3 100644 --- a/crates/geth/tests/bootstrap.rs +++ b/crates/geth/tests/bootstrap.rs @@ -207,6 +207,106 @@ fn backup_create_and_restore_use_separate_home_without_private_identity_keys() { assert!(!restore_home.join("identity/agent.ed25519").exists()); } +#[test] +fn synthetic_pre_release_home_migrates_forward() { + let home = tempfile::tempdir().expect("tempdir"); + let init = run_geth(home.path(), &["init"]); + assert!( + init.status.success(), + "stderr: {}", + String::from_utf8_lossy(&init.stderr) + ); + + let db_path = home.path().join("geth.sqlite"); + let conn = rusqlite::Connection::open(&db_path).expect("open metadata"); + conn.execute( + "INSERT OR REPLACE INTO keychain_ops(op_id, op_json, created_at_ms) VALUES (?1, ?2, ?3)", + ( + "keychain-op:upgrade-fixture", + r#"{"id":"keychain-op:upgrade-fixture"}"#, + 10_i64, + ), + ) + .expect("insert keychain op"); + conn.execute( + "INSERT OR REPLACE INTO auth_ops(op_id, resource_id, op_json, created_at_ms) VALUES (?1, ?2, ?3, ?4)", + ( + "auth-op:upgrade-fixture", + "resource:kv:prefs", + r#"{"id":"auth-op:upgrade-fixture"}"#, + 11_i64, + ), + ) + .expect("insert auth op"); + conn.execute( + "INSERT OR REPLACE INTO cas_objects(hash, size_bytes, path, created_at_ms) VALUES (?1, ?2, ?3, ?4)", + ( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 4_i64, + "cas/blobs/fixture", + 12_i64, + ), + ) + .expect("insert cas object"); + conn.execute( + "INSERT OR REPLACE INTO cas_pins(hash, pinned_at_ms) VALUES (?1, ?2)", + ( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 13_i64, + ), + ) + .expect("insert cas pin"); + conn.execute( + "INSERT OR REPLACE INTO peer_cards(peer_id, card_json, updated_at_ms) VALUES (?1, ?2, ?3)", + ( + "node:upgrade-peer", + r#"{"node_id":"node:upgrade-peer","fixture":true}"#, + 14_i64, + ), + ) + .expect("insert peer card"); + conn.execute( + "INSERT INTO meta(key, value) VALUES ('schema_version', '1') + ON CONFLICT(key) DO UPDATE SET value = excluded.value", + [], + ) + .expect("downgrade schema marker"); + drop(conn); + + let upgrade = run_geth(home.path(), &["init"]); + assert!( + upgrade.status.success(), + "stderr: {}", + String::from_utf8_lossy(&upgrade.stderr) + ); + + let conn = rusqlite::Connection::open(&db_path).expect("open upgraded metadata"); + let schema_version: String = conn + .query_row( + "SELECT value FROM meta WHERE key = 'schema_version'", + [], + |row| row.get(0), + ) + .expect("schema version"); + assert_eq!( + schema_version, + geth_store::CURRENT_SCHEMA_VERSION.to_string() + ); + for (table, column) in [ + ("keychain_ops", "op_id"), + ("auth_ops", "op_id"), + ("cas_pins", "hash"), + ("peer_cards", "peer_id"), + ] { + let count: i64 = conn + .query_row(&format!("SELECT COUNT({column}) FROM {table}"), [], |row| { + row.get(0) + }) + .expect("fixture count"); + assert!(count > 0, "{table} fixture row should survive upgrade"); + } +} + #[test] fn cli_help_documents_owner_init_keys() { let home = tempfile::tempdir().expect("tempdir"); diff --git a/docs/production-readiness-roadmap.md b/docs/production-readiness-roadmap.md index d40aa1e..6f01f47 100644 --- a/docs/production-readiness-roadmap.md +++ b/docs/production-readiness-roadmap.md @@ -308,12 +308,15 @@ Goal: make first deployment the start of a controlled compatibility story. `cargo run`. - `[x]` Archives include relevant docs and license files. -- `[ ]` Add upgrade tests. +- `[x]` Add upgrade tests. Acceptance criteria: - - `[ ]` Fixture homes from previous tagged pre-releases migrate forward. - - `[ ]` Upgrade tests include store schema, config, keychain/auth logs, CAS + - `[x]` Fixture homes from previous tagged pre-releases migrate forward. + There are no tagged pre-releases yet, so the current test uses a synthetic + pre-release home and the release policy requires retaining real tagged + fixture homes once tags exist. + - `[x]` Upgrade tests include store schema, config, keychain/auth logs, CAS metadata, and peer cards. - - `[ ]` Rollback expectations are documented. + - `[x]` Rollback expectations are documented. - `[x]` Publish release and support policy. Acceptance criteria: diff --git a/docs/release-support-policy.md b/docs/release-support-policy.md index 35c8ce3..2e19337 100644 --- a/docs/release-support-policy.md +++ b/docs/release-support-policy.md @@ -34,6 +34,19 @@ After the first deployment tag: - Signed operation formats must not be changed without a new version or explicit migration behavior. +## Upgrade And Rollback + +Before the first deployment tag, upgrade tests use synthetic pre-release homes +that exercise the same on-disk contracts as tagged fixtures: config, SQLite +schema, signed keychain/auth logs, CAS metadata, and peer-card metadata. Once a +tagged pre-release exists, its fixture home must be kept and migrated by the +current test suite. + +Store migrations are forward-only. A newer daemon may upgrade `geth.sqlite` in +place, so rollback means restoring a backup made before the upgrade or using a +copied fixture home. Downgrading a migrated home with an older binary is not a +supported recovery path unless a release note explicitly says so. + ## Security Updates Security fixes should be released as soon as practical after validation. The