2026-04-13 07:48:27 +00:00
|
|
|
use rusqlite::{Connection, Result};
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
|
|
const MIGRATION_001: &str = include_str!("../migrations/001_init.sql");
|
2026-04-13 12:20:45 +00:00
|
|
|
const MIGRATION_002: &str = include_str!("../migrations/002_add_last_polled.sql");
|
2026-04-14 13:59:23 +00:00
|
|
|
const MIGRATION_003: &str = include_str!("../migrations/003_add_agents.sql");
|
2026-04-13 07:48:27 +00:00
|
|
|
|
|
|
|
|
pub fn init(db_path: &Path) -> Result<Connection> {
|
|
|
|
|
let conn = Connection::open(db_path)?;
|
|
|
|
|
configure(&conn)?;
|
|
|
|
|
migrate(&conn)?;
|
|
|
|
|
Ok(conn)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 08:07:40 +00:00
|
|
|
#[cfg(test)]
|
2026-04-13 07:48:27 +00:00
|
|
|
pub fn init_in_memory() -> Result<Connection> {
|
|
|
|
|
let conn = Connection::open_in_memory()?;
|
|
|
|
|
configure(&conn)?;
|
|
|
|
|
migrate(&conn)?;
|
|
|
|
|
Ok(conn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn configure(conn: &Connection) -> Result<()> {
|
|
|
|
|
conn.pragma_update(None, "journal_mode", "wal")?;
|
|
|
|
|
conn.pragma_update(None, "foreign_keys", "ON")?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn migrate(conn: &Connection) -> Result<()> {
|
|
|
|
|
let version: i32 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
|
|
|
|
|
|
|
|
|
if version < 1 {
|
|
|
|
|
conn.execute_batch(MIGRATION_001)?;
|
|
|
|
|
conn.pragma_update(None, "user_version", 1)?;
|
|
|
|
|
}
|
2026-04-13 12:20:45 +00:00
|
|
|
if version < 2 {
|
|
|
|
|
conn.execute_batch(MIGRATION_002)?;
|
|
|
|
|
conn.pragma_update(None, "user_version", 2)?;
|
|
|
|
|
}
|
2026-04-14 13:59:23 +00:00
|
|
|
if version < 3 {
|
|
|
|
|
conn.execute_batch(MIGRATION_003)?;
|
|
|
|
|
conn.pragma_update(None, "user_version", 3)?;
|
|
|
|
|
}
|
2026-04-13 07:48:27 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_init_in_memory_creates_tables() {
|
|
|
|
|
let conn = init_in_memory().expect("should initialize");
|
|
|
|
|
|
2026-04-14 13:59:23 +00:00
|
|
|
// Verify all 7 tables exist
|
2026-04-13 07:48:27 +00:00
|
|
|
let tables: Vec<String> = conn
|
|
|
|
|
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.query_map([], |row| row.get(0))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.collect::<Result<Vec<String>, _>>()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
tables,
|
|
|
|
|
vec![
|
2026-04-14 13:59:23 +00:00
|
|
|
"agents",
|
2026-04-13 07:48:27 +00:00
|
|
|
"notifications",
|
|
|
|
|
"processed_tickets",
|
|
|
|
|
"projects",
|
|
|
|
|
"tuleap_credentials",
|
|
|
|
|
"watched_trackers",
|
|
|
|
|
"worktrees",
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_init_in_memory_enables_foreign_keys() {
|
|
|
|
|
let conn = init_in_memory().expect("should initialize");
|
|
|
|
|
let fk_enabled: i32 = conn
|
|
|
|
|
.query_row("PRAGMA foreign_keys", [], |row| row.get(0))
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(fk_enabled, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_migration_is_idempotent() {
|
|
|
|
|
let conn = init_in_memory().expect("should initialize");
|
|
|
|
|
// Running init again on same connection should not fail
|
|
|
|
|
let version: i32 = conn
|
|
|
|
|
.pragma_query_value(None, "user_version", |row| row.get(0))
|
|
|
|
|
.unwrap();
|
2026-04-14 13:59:23 +00:00
|
|
|
assert_eq!(version, 3);
|
2026-04-13 07:48:27 +00:00
|
|
|
}
|
|
|
|
|
}
|