use std::path::Path;
use std::process::{Command, Output};
fn run(args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_roomrs"))
.args(args)
.output()
.expect("바이너리 실행")
}
fn stderr(out: &Output) -> String {
String::from_utf8_lossy(&out.stderr).into_owned()
}
fn snapshot_v1() -> &'static str {
r#"{
"version": 1,
"tables": [{
"name": "users",
"columns": [
{ "name": "id", "sql_type": "INTEGER", "not_null": true, "pk": true }
],
"ddl": ["CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY)"]
}]
}"#
}
fn snapshot_v2() -> &'static str {
r#"{
"version": 2,
"tables": [{
"name": "users",
"columns": [
{ "name": "id", "sql_type": "INTEGER", "not_null": true, "pk": true },
{ "name": "name", "sql_type": "TEXT", "not_null": false, "pk": false }
],
"ddl": ["CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY, \"name\" TEXT)"]
}]
}"#
}
fn write_fixture(dir: &Path, name: &str, json: &str) -> String {
let p = dir.join(name);
std::fs::write(&p, json).expect("픽스처 기록");
p.to_str().expect("utf-8 경로").to_string()
}
#[test]
fn diff_prints_sql_to_stdout() {
let dir = tempfile::tempdir().unwrap();
let old = write_fixture(dir.path(), "v1.json", snapshot_v1());
let new = write_fixture(dir.path(), "v2.json", snapshot_v2());
let out = run(&["migrate", "diff", &old, &new]);
assert!(out.status.success(), "exit 0 기대: {out:?}");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("ALTER TABLE \"users\" ADD COLUMN \"name\" TEXT"),
"ADD COLUMN 초안 포함: {stdout}"
);
assert!(
stdout.contains("PRAGMA user_version = 2"),
"user_version 갱신 포함: {stdout}"
);
}
#[test]
fn diff_writes_out_file() {
let dir = tempfile::tempdir().unwrap();
let old = write_fixture(dir.path(), "v1.json", snapshot_v1());
let new = write_fixture(dir.path(), "v2.json", snapshot_v2());
let out_path = dir.path().join("draft.sql");
let out = run(&["migrate", "diff", &old, &new, out_path.to_str().unwrap()]);
assert!(out.status.success(), "exit 0 기대: {out:?}");
assert!(stderr(&out).contains("초안 저장"), "저장 안내 메시지");
let sql = std::fs::read_to_string(&out_path).expect("초안 파일 존재");
assert!(sql.contains("ALTER TABLE"), "초안 SQL 내용: {sql}");
}
#[test]
fn diff_refuses_to_overwrite_existing_out_file() {
let dir = tempfile::tempdir().unwrap();
let old = write_fixture(dir.path(), "v1.json", snapshot_v1());
let new = write_fixture(dir.path(), "v2.json", snapshot_v2());
let out_path = dir.path().join("draft.sql");
std::fs::write(&out_path, "-- 수동 검토 중 초안").unwrap();
let out = run(&["migrate", "diff", &old, &new, out_path.to_str().unwrap()]);
assert_eq!(out.status.code(), Some(1), "exit 1 기대: {out:?}");
assert!(
stderr(&out).contains("이미 존재"),
"한국어 덮어쓰기 거부 메시지: {}",
stderr(&out)
);
assert_eq!(
std::fs::read_to_string(&out_path).unwrap(),
"-- 수동 검토 중 초안",
"기존 파일 무손상"
);
}
#[test]
fn check_identical_snapshots_exit_zero() {
let dir = tempfile::tempdir().unwrap();
let a = write_fixture(dir.path(), "a.json", snapshot_v1());
let b = write_fixture(dir.path(), "b.json", snapshot_v1());
let out = run(&["migrate", "check", &a, &b]);
assert!(out.status.success(), "exit 0 기대: {out:?}");
assert!(stderr(&out).contains("일치"), "일치 메시지");
}
#[test]
fn check_different_snapshots_exit_one() {
let dir = tempfile::tempdir().unwrap();
let a = write_fixture(dir.path(), "a.json", snapshot_v1());
let b = write_fixture(dir.path(), "b.json", snapshot_v2());
let out = run(&["migrate", "check", &a, &b]);
assert_eq!(out.status.code(), Some(1), "exit 1 기대: {out:?}");
assert!(stderr(&out).contains("불일치"), "불일치 메시지");
}
#[test]
fn missing_file_exit_one_with_korean_message() {
let dir = tempfile::tempdir().unwrap();
let a = write_fixture(dir.path(), "a.json", snapshot_v1());
let missing = dir.path().join("없음.json");
let out = run(&["migrate", "check", &a, missing.to_str().unwrap()]);
assert_eq!(out.status.code(), Some(1), "exit 1 기대: {out:?}");
assert!(
stderr(&out).contains("스냅샷을 읽을 수 없습니다"),
"한국어 에러 메시지: {}",
stderr(&out)
);
}
#[test]
fn bad_usage_exit_two() {
let out = run(&["migrate", "unknown"]);
assert_eq!(out.status.code(), Some(2), "exit 2 기대: {out:?}");
assert!(stderr(&out).contains("사용법"), "사용법 안내");
let out = run(&[]);
assert_eq!(out.status.code(), Some(2), "exit 2 기대: {out:?}");
}
#[test]
fn check_dir_happy_path() {
let dir = tempfile::tempdir().unwrap();
write_fixture(dir.path(), "app.1.json", snapshot_v1());
write_fixture(dir.path(), "app.2.json", snapshot_v2());
write_fixture(dir.path(), "other.1.json", snapshot_v1());
let out = run(&["migrate", "check-dir", dir.path().to_str().unwrap(), "app"]);
assert!(out.status.success(), "exit 0 기대: {out:?}");
let err = stderr(&out);
assert!(err.contains("스냅샷 2개 확인"), "요약 메시지: {err}");
assert!(err.contains("v1..v2"), "버전 범위: {err}");
}
#[test]
fn check_dir_reports_destructive_as_warning() {
let dir = tempfile::tempdir().unwrap();
write_fixture(
dir.path(),
"app.1.json",
&snapshot_v2().replace("\"version\": 2", "\"version\": 1"),
);
write_fixture(
dir.path(),
"app.2.json",
&snapshot_v1().replace("\"version\": 1", "\"version\": 2"),
);
let out = run(&["migrate", "check-dir", dir.path().to_str().unwrap(), "app"]);
assert!(out.status.success(), "경고는 실패 아님: {out:?}");
let err = stderr(&out);
assert!(err.contains("파괴적 변경"), "파괴적 경고: {err}");
assert!(err.contains("스냅샷 2개 확인"), "{err}");
}
#[test]
fn check_dir_version_gap_warns_exit_zero() {
let dir = tempfile::tempdir().unwrap();
write_fixture(dir.path(), "app.1.json", snapshot_v1());
write_fixture(
dir.path(),
"app.3.json",
&snapshot_v2().replace("\"version\": 2", "\"version\": 3"),
);
let out = run(&["migrate", "check-dir", dir.path().to_str().unwrap(), "app"]);
assert!(out.status.success(), "경고는 실패 아님: {out:?}");
let err = stderr(&out);
assert!(err.contains("버전 갭: v1 다음 v3"), "갭 경고: {err}");
assert!(err.contains("중간 스냅샷 누락"), "갭 사유: {err}");
assert!(err.contains("스냅샷 2개 확인"), "요약 유지: {err}");
}
#[test]
fn check_dir_version_gap_strict_exit_one() {
let dir = tempfile::tempdir().unwrap();
write_fixture(dir.path(), "app.1.json", snapshot_v1());
write_fixture(
dir.path(),
"app.3.json",
&snapshot_v2().replace("\"version\": 2", "\"version\": 3"),
);
let out = run(&[
"migrate",
"check-dir",
dir.path().to_str().unwrap(),
"app",
"--strict",
]);
assert_eq!(out.status.code(), Some(1), "--strict = exit 1: {out:?}");
let err = stderr(&out);
assert!(err.contains("버전 갭: v1 다음 v3"), "갭 경고: {err}");
assert!(err.contains("--strict"), "승격 안내: {err}");
}
#[test]
fn check_dir_destructive_strict_exit_one() {
let dir = tempfile::tempdir().unwrap();
write_fixture(
dir.path(),
"app.1.json",
&snapshot_v2().replace("\"version\": 2", "\"version\": 1"),
);
write_fixture(
dir.path(),
"app.2.json",
&snapshot_v1().replace("\"version\": 1", "\"version\": 2"),
);
let out = run(&[
"migrate",
"check-dir",
dir.path().to_str().unwrap(),
"app",
"--strict",
]);
assert_eq!(out.status.code(), Some(1), "--strict = exit 1: {out:?}");
assert!(stderr(&out).contains("파괴적 변경"), "{}", stderr(&out));
}
#[test]
fn check_dir_clean_strict_exit_zero() {
let dir = tempfile::tempdir().unwrap();
write_fixture(dir.path(), "app.1.json", snapshot_v1());
write_fixture(dir.path(), "app.2.json", snapshot_v2());
let out = run(&[
"migrate",
"check-dir",
dir.path().to_str().unwrap(),
"app",
"--strict",
]);
assert!(out.status.success(), "경고 없음 = exit 0: {out:?}");
assert!(stderr(&out).contains("스냅샷 2개 확인"), "{}", stderr(&out));
}
#[test]
fn check_dir_empty_exit_one() {
let dir = tempfile::tempdir().unwrap();
let out = run(&["migrate", "check-dir", dir.path().to_str().unwrap(), "app"]);
assert_eq!(out.status.code(), Some(1), "exit 1 기대: {out:?}");
assert!(
stderr(&out).contains("스냅샷이 없습니다"),
"{}",
stderr(&out)
);
}
#[test]
fn check_dir_corrupt_file_exit_one() {
let dir = tempfile::tempdir().unwrap();
write_fixture(dir.path(), "app.1.json", "{ 이건 JSON 아님");
let out = run(&["migrate", "check-dir", dir.path().to_str().unwrap(), "app"]);
assert_eq!(out.status.code(), Some(1), "exit 1 기대: {out:?}");
assert!(
stderr(&out).contains("스냅샷을 읽을 수 없습니다"),
"{}",
stderr(&out)
);
}
#[test]
fn check_dir_version_mismatch_exit_one() {
let dir = tempfile::tempdir().unwrap();
write_fixture(dir.path(), "app.3.json", snapshot_v1());
let out = run(&["migrate", "check-dir", dir.path().to_str().unwrap(), "app"]);
assert_eq!(out.status.code(), Some(1), "exit 1 기대: {out:?}");
assert!(stderr(&out).contains("파일명 버전"), "{}", stderr(&out));
}
#[test]
fn invalid_json_exit_one() {
let dir = tempfile::tempdir().unwrap();
let bad = write_fixture(dir.path(), "bad.json", "{ 이건 JSON 아님");
let ok = write_fixture(dir.path(), "ok.json", snapshot_v1());
let out = run(&["migrate", "check", &bad, &ok]);
assert_eq!(out.status.code(), Some(1), "exit 1 기대: {out:?}");
assert!(
stderr(&out).contains("스냅샷을 읽을 수 없습니다"),
"한국어 에러 메시지: {}",
stderr(&out)
);
}