use std::path::{Path, PathBuf};
use assert_cmd::Command;
use tempfile::TempDir;
fn jammi_cmd(artifact_dir: &Path) -> Command {
let mut cmd = Command::cargo_bin("jammi-cli").expect("jammi-cli binary built");
cmd.env("JAMMI_ARTIFACT_DIR", artifact_dir)
.env_remove("JAMMI_CONFIG");
cmd
}
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.expect("workspace root")
.join("tests/fixtures/cp9")
.join(name)
}
fn write_schema(tmp: &TempDir, body: &str) -> PathBuf {
let p = tmp.path().join("schema.json");
std::fs::write(&p, body).unwrap();
p
}
#[test]
fn cli_mutable_create_list_drop_happy_path() {
let dir = TempDir::new().expect("tempdir");
let schema = fixture("feature_schema.json");
assert!(
schema.exists(),
"expected fixture {schema:?} to exist (run from workspace)"
);
jammi_cmd(dir.path())
.args([
"mutable",
"create",
"--name",
"feature_store_dimensions",
"--schema",
schema.to_str().unwrap(),
"--primary-key",
"feature_id,effective_from",
"--index",
"name=idx_active,columns=feature_id+effective_to,unique=false",
])
.assert()
.success()
.stdout(predicates::str::contains("registered"))
.stdout(predicates::str::contains("idx_active"));
let out = jammi_cmd(dir.path())
.args(["mutable", "list"])
.output()
.expect("run mutable list");
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("feature_store_dimensions"),
"list missing table:\n{stdout}"
);
assert!(
stdout.contains("feature_id,effective_from"),
"list missing primary key:\n{stdout}"
);
jammi_cmd(dir.path())
.args(["mutable", "drop", "feature_store_dimensions"])
.assert()
.success()
.stdout(predicates::str::contains("dropped"));
let out = jammi_cmd(dir.path())
.args(["mutable", "list"])
.output()
.expect("run mutable list after drop");
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
!stdout.contains("feature_store_dimensions"),
"table still present after drop:\n{stdout}"
);
}
#[test]
fn cli_mutable_create_rejects_reserved_tenant_id_column() {
let dir = TempDir::new().expect("tempdir");
let schema_dir = TempDir::new().expect("schema tempdir");
let schema = write_schema(
&schema_dir,
r#"[
{"name":"feature_id","type":"Int64","nullable":false},
{"name":"tenant_id","type":"Utf8","nullable":false}
]"#,
);
let out = jammi_cmd(dir.path())
.args([
"mutable",
"create",
"--name",
"tries_to_smuggle_tenant",
"--schema",
schema.to_str().unwrap(),
"--primary-key",
"feature_id",
])
.output()
.expect("run create");
assert!(!out.status.success(), "must reject reserved column name");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("reserved") && stderr.contains("tenant_id"),
"stderr must explain reserved column:\n{stderr}"
);
}
#[test]
fn cli_mutable_create_rejects_missing_primary_key() {
let dir = TempDir::new().expect("tempdir");
let schema = fixture("feature_schema.json");
let out = jammi_cmd(dir.path())
.args([
"mutable",
"create",
"--name",
"missing_pk",
"--schema",
schema.to_str().unwrap(),
"--primary-key",
"",
])
.output()
.expect("run create");
assert!(!out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("at least one column"),
"stderr must require pk column:\n{stderr}"
);
}
#[test]
fn cli_mutable_drop_rejects_unknown_table() {
let dir = TempDir::new().expect("tempdir");
let out = jammi_cmd(dir.path())
.args(["mutable", "drop", "never_registered"])
.output()
.expect("run drop");
assert!(!out.status.success(), "drop of unknown table must fail");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("never_registered") || stderr.to_lowercase().contains("not found"),
"stderr must explain missing table:\n{stderr}"
);
}
#[test]
fn cli_mutable_create_rejects_unknown_schema_type() {
let dir = TempDir::new().expect("tempdir");
let schema_dir = TempDir::new().expect("schema tempdir");
let schema = write_schema(
&schema_dir,
r#"[{"name":"x","type":"Decimal","nullable":false}]"#,
);
let out = jammi_cmd(dir.path())
.args([
"mutable",
"create",
"--name",
"bad_schema",
"--schema",
schema.to_str().unwrap(),
"--primary-key",
"x",
])
.output()
.expect("run create");
assert!(!out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("Decimal"),
"stderr must mention unsupported type:\n{stderr}"
);
}