use super::*;
#[test]
fn migration_replay_does_not_trip_on_already_present_column() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let _db = YantrikDB::new(path, 8).unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '23')",
[],
)
.unwrap();
}
let db = YantrikDB::new(path, 8)
.expect("v0.7.3 idempotent migration runner must heal rewound-meta deployments");
db.record(
"post-heal smoke",
"episodic",
0.5,
0.0,
604800.0,
&empty_meta(),
&vec_seed(1.0, 8),
"default",
0.8,
"general",
"user",
None,
)
.unwrap();
}
#[test]
fn migration_replay_does_not_trip_on_alter_table_against_view() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let _db = YantrikDB::new(path, 8).unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
let kind: String = conn
.query_row(
"SELECT type FROM sqlite_master WHERE name = 'edges'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
kind, "view",
"fixture precondition: at current schema, edges should be a backward-compat view"
);
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '14')",
[],
)
.unwrap();
}
let db = YantrikDB::new(path, 8)
.expect("v0.7.8 idempotent runner must heal rewound-meta DBs that hit ALTER-on-view");
db.record(
"post-heal smoke (issue 10)",
"episodic",
0.5,
0.0,
604800.0,
&empty_meta(),
&vec_seed(1.0, 8),
"default",
0.8,
"general",
"user",
None,
)
.unwrap();
}
#[test]
fn migration_meta_stamp_does_not_downgrade() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '999');",
)
.unwrap();
}
let _db = YantrikDB::new(path, 8).unwrap();
let stamped: String = {
let conn = rusqlite::Connection::open(path).unwrap();
conn.query_row(
"SELECT value FROM meta WHERE key = 'schema_version'",
[],
|row| row.get(0),
)
.unwrap()
};
assert_eq!(
stamped, "999",
"MAX-stamp invariant: open() must never rewind meta.schema_version below the on-disk value"
);
}
#[test]
fn schema_v26_fresh_install_has_provenance_columns_and_indexes() {
let db = YantrikDB::new(":memory:", 8).unwrap();
let conn = db.conn();
let cols = table_columns(&conn, "memories");
for required in [
"prior_rid",
"resolution_kind",
"dismissal_reason",
"confidence_at_write",
] {
assert!(
cols.iter().any(|c| c == required),
"v26: fresh-install memories table missing column {required}, got: {cols:?}"
);
}
assert!(
index_exists(&conn, "idx_memories_prior_rid"),
"v26: fresh-install missing partial index idx_memories_prior_rid"
);
assert!(
index_exists(&conn, "idx_memories_resolution_kind"),
"v26: fresh-install missing partial index idx_memories_resolution_kind"
);
}
#[test]
fn schema_v26_migration_from_v25_adds_columns_and_normalizes_source() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let db = YantrikDB::new(path, 8).unwrap();
let conn = db.conn();
for (rid, src) in [
("01900000-0000-7000-8000-000000000001", "user"),
("01900000-0000-7000-8000-000000000002", "inference"),
("01900000-0000-7000-8000-000000000003", "legacy-freetext"),
] {
conn.execute(
"INSERT INTO memories (rid, type, text, created_at, updated_at, last_access, source) \
VALUES (?1, 'episodic', 'test', 0.0, 0.0, 0.0, ?2)",
params![rid, src],
)
.unwrap();
}
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '25')",
[],
)
.unwrap();
conn.execute("DROP INDEX IF EXISTS idx_memories_prior_rid", [])
.unwrap();
conn.execute("DROP INDEX IF EXISTS idx_memories_resolution_kind", [])
.unwrap();
}
let db = YantrikDB::new(path, 8)
.expect("v26 migration must run cleanly against a rewound-meta v25 DB");
let conn = db.conn();
let cols = table_columns(&conn, "memories");
for required in [
"prior_rid",
"resolution_kind",
"dismissal_reason",
"confidence_at_write",
] {
assert!(
cols.iter().any(|c| c == required),
"v26 migration: missing column {required} after re-open"
);
}
assert!(
index_exists(&conn, "idx_memories_prior_rid"),
"v26 migration: missing partial index idx_memories_prior_rid"
);
assert!(
index_exists(&conn, "idx_memories_resolution_kind"),
"v26 migration: missing partial index idx_memories_resolution_kind"
);
let normalized: String = conn
.query_row(
"SELECT source FROM memories WHERE rid = '01900000-0000-7000-8000-000000000003'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
normalized, "user",
"v26 migration must coerce non-enum source to 'user'"
);
let preserved: String = conn
.query_row(
"SELECT source FROM memories WHERE rid = '01900000-0000-7000-8000-000000000002'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
preserved, "inference",
"v26 migration must preserve enum-valid source values"
);
let log: String = conn
.query_row(
"SELECT value FROM meta WHERE key = 'source_normalization_log_v26'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
log.contains("normalized 1 rows"),
"v26 migration must log normalization count, got: {log}"
);
}
#[test]
fn schema_v26_migration_replay_is_idempotent() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let _db = YantrikDB::new(path, 8).unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '25')",
[],
)
.unwrap();
}
let db = YantrikDB::new(path, 8)
.expect("v26 migration runner must heal rewound-meta deployments on a v26-schema DB");
db.record(
"post-v26-heal smoke",
"episodic",
0.5,
0.0,
604800.0,
&empty_meta(),
&vec_seed(1.0, 8),
"default",
0.8,
"general",
"user",
None,
)
.unwrap();
}