#[cfg(test)]
mod tests {
use kasl::db::db::Db;
use kasl::db::migrations::{MigrationManager, get_db_version, needs_migration};
use serial_test::serial;
use tempfile::TempDir;
use test_context::{TestContext, test_context};
struct MigrationTestContext {
_temp_dir: TempDir,
}
impl TestContext for MigrationTestContext {
fn setup() -> Self {
let temp_dir = tempfile::tempdir().unwrap();
unsafe {
std::env::set_var("HOME", temp_dir.path());
}
unsafe {
std::env::set_var("LOCALAPPDATA", temp_dir.path());
}
MigrationTestContext { _temp_dir: temp_dir }
}
}
#[test_context(MigrationTestContext)]
#[serial]
#[test]
fn test_migrations_run_automatically(_ctx: &mut MigrationTestContext) {
let db = Db::new().unwrap();
let version = get_db_version(&db.conn).unwrap();
assert!(version > 0);
assert!(!needs_migration(&db.conn).unwrap());
}
#[test_context(MigrationTestContext)]
#[serial]
#[test]
fn test_migration_history(_ctx: &mut MigrationTestContext) {
let mut conn = Db::new_without_migrations().unwrap();
let manager = MigrationManager::new();
manager.run_migrations(&mut conn).unwrap();
let history = manager.get_migration_history(&conn).unwrap();
assert!(!history.is_empty());
for (i, entry) in history.iter().enumerate() {
assert_eq!(entry.0 as usize, i + 1);
}
}
#[test_context(MigrationTestContext)]
#[serial]
#[test]
fn test_migration_idempotency(_ctx: &mut MigrationTestContext) {
let mut conn = Db::new_without_migrations().unwrap();
let manager = MigrationManager::new();
manager.run_migrations(&mut conn).unwrap();
let version1 = get_db_version(&conn).unwrap();
manager.run_migrations(&mut conn).unwrap();
let version2 = get_db_version(&conn).unwrap();
assert_eq!(version1, version2);
}
#[test_context(MigrationTestContext)]
#[serial]
#[test]
fn tasks_on_chain_zero_get_chains_of_their_own(_ctx: &mut MigrationTestContext) {
let mut conn = Db::new_without_migrations().unwrap();
let manager = MigrationManager::new();
manager.run_migrations(&mut conn).unwrap();
manager.rollback_to(&mut conn, 16).unwrap();
drop_inbox_rule_columns(&conn);
conn.execute("ALTER TABLE jira_inbox DROP COLUMN left_reason", []).unwrap();
conn.execute("ALTER TABLE jira_inbox DROP COLUMN resolution", []).unwrap();
conn.execute(
"INSERT INTO tasks (id, task_id, name, comment, completeness) VALUES (7, 0, 'KA-1 taken', '', 0), (9, 0, 'imported', '', 100), (12, 7, 'KA-1 taken', '', 50)",
[],
)
.unwrap();
manager.run_migrations(&mut conn).unwrap();
let chains: Vec<(i32, i32)> = conn
.prepare("SELECT id, task_id FROM tasks ORDER BY id")
.unwrap()
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
.unwrap()
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(
chains,
vec![(7, 7), (9, 9), (12, 7)],
"chain 0 rows become their own chain; a real chain is kept"
);
conn.execute(
"INSERT INTO jira_inbox (issue_key, issue_id, summary, priority_rank, url, first_seen, last_seen, notified, pinned, dismissed) VALUES ('KA-1', '1', 's', 3, 'u', datetime('now'), datetime('now'), 0, 0, 0)",
[],
)
.unwrap();
assert!(
conn.execute("UPDATE jira_inbox SET left_reason = 'maybe' WHERE issue_key = 'KA-1'", [])
.is_err(),
"a third reason must be unwritable"
);
}
fn drop_inbox_rule_columns(conn: &rusqlite::Connection) {
for column in ["issue_type", "project", "ruled_by"] {
conn.execute(&format!("ALTER TABLE jira_inbox DROP COLUMN {column}"), []).unwrap();
}
conn.execute("DROP TABLE jira_priorities", []).unwrap();
}
#[test_context(MigrationTestContext)]
#[serial]
#[test]
fn existing_issues_get_the_project_their_key_names(_ctx: &mut MigrationTestContext) {
let mut conn = Db::new_without_migrations().unwrap();
let manager = MigrationManager::new();
manager.run_migrations(&mut conn).unwrap();
manager.rollback_to(&mut conn, 17).unwrap();
drop_inbox_rule_columns(&conn);
conn.execute(
"INSERT INTO jira_inbox (issue_key, issue_id, summary, priority, priority_rank, url, first_seen, last_seen, notified, pinned, dismissed)
VALUES ('PAY-12', '1', 's', 'High', 2, 'u', datetime('now'), datetime('now'), 0, 0, 0),
('odd', '2', 's', NULL, 999, 'u', datetime('now'), datetime('now'), 0, 0, 0)",
[],
)
.unwrap();
manager.run_migrations(&mut conn).unwrap();
let rows: Vec<(String, Option<String>, Option<String>)> = conn
.prepare("SELECT issue_key, project, issue_type FROM jira_inbox ORDER BY issue_key")
.unwrap()
.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))
.unwrap()
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(
rows,
vec![("PAY-12".to_string(), Some("PAY".to_string()), None), ("odd".to_string(), None, None)],
"a key's prefix is its project; a key without one is left alone, and the type waits for Jira"
);
let priorities: Vec<(String, i32)> = conn
.prepare("SELECT name, rank FROM jira_priorities")
.unwrap()
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
.unwrap()
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(priorities, vec![("High".to_string(), 2)], "the vocabulary starts with the names the rows carry");
}
}