#![allow(clippy::unwrap_used)]
use std::process::Command;
fn cli() -> Command {
Command::new(env!("CARGO_BIN_EXE_fraiseql-cli"))
}
fn confiture_available() -> bool {
Command::new("confiture")
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|s| s.success())
}
#[test]
fn migrate_help_exits_zero() {
let out = cli().args(["migrate", "--help"]).output().unwrap();
assert!(out.status.success(), "migrate --help must exit 0");
let text = String::from_utf8_lossy(&out.stdout);
assert!(
text.contains("up") || text.contains("down") || text.contains("status"),
"migrate --help must describe sub-commands; got: {text}"
);
}
#[test]
fn migrate_up_help_exits_zero() {
let out = cli().args(["migrate", "up", "--help"]).output().unwrap();
assert!(out.status.success(), "migrate up --help must exit 0");
}
#[test]
fn migrate_create_help_exits_zero() {
let out = cli().args(["migrate", "create", "--help"]).output().unwrap();
assert!(out.status.success(), "migrate create --help must exit 0");
}
#[test]
fn migrate_up_without_confiture_exits_nonzero() {
if confiture_available() {
return; }
let out = cli()
.args(["migrate", "up", "--database", "postgres://localhost/test"])
.output()
.unwrap();
assert!(!out.status.success(), "migrate up without confiture must exit non-zero");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("confiture") || stderr.contains("install"),
"error message must mention confiture or install; got: {stderr}"
);
}
#[test]
fn migrate_status_no_database_url_exits_nonzero() {
if confiture_available() {
return; }
let tmp = tempfile::tempdir().unwrap();
let out = cli()
.current_dir(tmp.path())
.args(["migrate", "status"])
.env_remove("DATABASE_URL")
.output()
.unwrap();
assert!(
!out.status.success(),
"migrate status with no URL and no confiture must exit non-zero"
);
}
#[test]
fn migrate_create_without_confiture_exits_nonzero() {
if confiture_available() {
return; }
let tmp = tempfile::tempdir().unwrap();
let out = cli()
.args([
"migrate",
"create",
"add_posts_table",
"--dir",
tmp.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(!out.status.success(), "migrate create without confiture must exit non-zero");
}
#[test]
fn migrate_generate_help_exits_zero() {
let out = cli().args(["migrate", "generate", "--help"]).output().unwrap();
assert!(out.status.success(), "migrate generate --help must exit 0");
let text = String::from_utf8_lossy(&out.stdout);
assert!(
text.contains("NAME") || text.contains("name") || text.contains("migration"),
"generate help must describe NAME argument; got: {text}"
);
}
#[test]
fn migrate_validate_help_exits_zero() {
let out = cli().args(["migrate", "validate", "--help"]).output().unwrap();
assert!(out.status.success(), "migrate validate --help must exit 0");
}
#[test]
fn migrate_preflight_help_exits_zero() {
let out = cli().args(["migrate", "preflight", "--help"]).output().unwrap();
assert!(out.status.success(), "migrate preflight --help must exit 0");
}
#[test]
fn migrate_generate_without_confiture_exits_nonzero() {
if confiture_available() {
return; }
let tmp = tempfile::tempdir().unwrap();
let out = cli()
.args([
"migrate",
"generate",
"add_posts_table",
"--dir",
tmp.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(!out.status.success(), "migrate generate without confiture must exit non-zero");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("confiture") || stderr.contains("install"),
"error must mention confiture; got: {stderr}"
);
}
#[test]
fn migrate_validate_without_confiture_exits_nonzero() {
if confiture_available() {
return;
}
let out = cli().args(["migrate", "validate"]).output().unwrap();
assert!(!out.status.success(), "migrate validate without confiture must exit non-zero");
}
#[test]
fn migrate_preflight_without_confiture_exits_nonzero() {
if confiture_available() {
return;
}
let out = cli().args(["migrate", "preflight"]).output().unwrap();
assert!(!out.status.success(), "migrate preflight without confiture must exit non-zero");
}
#[test]
fn compile_emit_ddl_writes_files() {
let tmp = tempfile::tempdir().unwrap();
let ddl_dir = tmp.path().join("ddl");
let schema_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/minimal_schema.json");
let out_path = tmp.path().join("schema.compiled.json");
let out = cli()
.args([
"compile",
schema_path.to_str().unwrap(),
"-o",
out_path.to_str().unwrap(),
"--emit-ddl",
ddl_dir.to_str().unwrap(),
])
.output()
.unwrap();
assert!(
out.status.success(),
"compile --emit-ddl must exit 0; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(ddl_dir.exists(), "DDL directory must be created");
let user_sql = ddl_dir.join("user.sql");
assert!(
user_sql.exists(),
"user.sql must be emitted; files: {:?}",
std::fs::read_dir(&ddl_dir).unwrap().collect::<Vec<_>>()
);
let content = std::fs::read_to_string(&user_sql).unwrap();
assert!(
content.contains("CREATE TABLE"),
"DDL must contain CREATE TABLE; got: {content}"
);
assert!(content.contains("tb_user"), "table name must be tb_user; got: {content}");
}
#[test]
fn compile_emit_ddl_column_types() {
let tmp = tempfile::tempdir().unwrap();
let ddl_dir = tmp.path().join("ddl");
let schema_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/minimal_schema.json");
let out_path = tmp.path().join("schema.compiled.json");
let out = cli()
.args([
"compile",
schema_path.to_str().unwrap(),
"-o",
out_path.to_str().unwrap(),
"--emit-ddl",
ddl_dir.to_str().unwrap(),
])
.output()
.unwrap();
assert!(out.status.success());
let content = std::fs::read_to_string(ddl_dir.join("user.sql")).unwrap();
assert!(content.contains("INTEGER"), "Int field must map to INTEGER; got: {content}");
assert!(content.contains("TEXT"), "String field must map to TEXT; got: {content}");
}
#[test]
fn migrate_reads_database_url_from_toml() {
if confiture_available() {
return; }
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("fraiseql.toml"),
"[database]\nurl = \"postgres://localhost/toml_test\"\n",
)
.unwrap();
let out = cli().current_dir(tmp.path()).args(["migrate", "up"]).output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.contains("No database URL"),
"URL should have been resolved from fraiseql.toml; stderr: {stderr}"
);
}