use rusqlite::Connection;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
fn run_dry_run(db_path: &Path) -> (i32, String) {
let output = Command::new(env!("CARGO"))
.arg("run")
.arg("--quiet")
.arg("--bin")
.arg("sqlite-graphrag")
.arg("--")
.arg("migrate")
.arg("--db")
.arg(db_path)
.arg("--dry-run")
.arg("--json")
.output()
.expect("spawn cargo run migrate --dry-run");
let status = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
(status, stdout)
}
fn count_user_tables(db_path: &Path) -> i64 {
let conn = match Connection::open(db_path) {
Ok(c) => c,
Err(_) => return 0,
};
conn.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
[],
|r| r.get::<_, i64>(0),
)
.unwrap_or(0)
}
#[test]
fn dry_run_does_not_mutate_schema_history() {
let tmp = TempDir::new().expect("tempdir");
let db_path = tmp.path().join("dryrun_target.sqlite");
assert!(
!db_path.exists(),
"precondition: db_path must not exist before invocation"
);
let (status, stdout) = run_dry_run(&db_path);
assert_ne!(
status, 2,
"migrate --dry-run must not be rejected by clap; stdout: {stdout}"
);
assert!(
!stdout.contains("unexpected argument '--dry-run'"),
"migrate must accept --dry-run; got: {stdout}"
);
assert_eq!(status, 0, "dry-run must exit 0; got status {status}");
let has_pending_key =
stdout.contains("\"pending_migrations\"") || stdout.contains("\"pending_count\"");
assert!(
has_pending_key,
"response must include pending_migrations/pending_count; got: {stdout}"
);
let has_status = stdout.contains("\"status\"");
assert!(
has_status,
"response must include status field; got: {stdout}"
);
if db_path.exists() {
let table_count = count_user_tables(&db_path);
assert_eq!(
table_count, 0,
"dry-run must not create user tables; found {table_count}"
);
let conn = Connection::open(&db_path).expect("open dry-run db");
let history_exists: bool = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='refinery_schema_history'",
[],
|r| r.get::<_, i64>(0),
)
.unwrap_or(0)
> 0;
assert!(
!history_exists,
"dry-run must not create refinery_schema_history"
);
}
}
#[test]
fn dry_run_on_fresh_db_reports_no_pending_after_init() {
let tmp = TempDir::new().expect("tempdir");
let db_path = tmp.path().join("init_then_dryrun.sqlite");
let init_status = Command::new(env!("CARGO"))
.arg("run")
.arg("--quiet")
.arg("--bin")
.arg("sqlite-graphrag")
.arg("--")
.arg("init")
.arg("--db")
.arg(&db_path)
.arg("--json")
.status()
.expect("spawn init");
assert!(init_status.success(), "init must succeed for this test");
let (status, stdout) = run_dry_run(&db_path);
assert!(status == 0, "dry-run must succeed; got status {status}");
assert!(
stdout.contains("ok_no_pending") || stdout.contains("\"pending_count\": 0"),
"after init, dry-run must report ok_no_pending or pending_count=0; got: {stdout}"
);
}