#![cfg(feature = "mem-repo")]
use assert_cmd::Command;
use tempfile::TempDir;
fn memstead() -> Command {
Command::cargo_bin("memstead").expect("memstead binary must be built by cargo")
}
fn parse_envelope(stdout_bytes: &[u8]) -> serde_json::Value {
let body = std::str::from_utf8(stdout_bytes).expect("stdout must be UTF-8");
serde_json::from_str(body.trim()).unwrap_or_else(|e| {
panic!("--json output must parse as JSON: {e}\n--- stdout ---\n{body}\n--- end ---")
})
}
fn quarantined_workspace(tmp: &TempDir) -> std::path::PathBuf {
let ws = tmp.path().join("ws");
memstead()
.args(["mem-repo", "init", ws.to_str().unwrap(), "--no-gitignore"])
.assert()
.success();
std::fs::create_dir_all(ws.join("plenum")).unwrap();
let mounts = ws.join(".memstead").join("state").join("mounts.json");
std::fs::create_dir_all(mounts.parent().unwrap()).unwrap();
std::fs::write(
&mounts,
r#"{
"format": "memstead-mounts-3",
"mounts": [
{ "mem": "plenum", "schema": "ghost@1.0.0", "storage": { "type": "folder", "path": "plenum" }, "capability": "write", "lifecycle": "eager", "cross_linkable": true }
]
}"#,
)
.unwrap();
let health = health_json(&ws);
assert_eq!(
health["quarantined"][0]["reason_code"], "SCHEMA_NOT_FOUND",
"fixture must quarantine on the bad pin: {health}"
);
ws
}
fn health_json(ws: &std::path::Path) -> serde_json::Value {
let out = memstead()
.current_dir(ws)
.args(["--json", "health"])
.assert()
.success()
.get_output()
.stdout
.clone();
parse_envelope(&out)
}
fn example_package_dir() -> String {
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../memstead-schema/examples/minimal")
.display()
.to_string()
}
#[test]
fn set_schema_repairs_quarantined_mem() {
let tmp = TempDir::new().unwrap();
let ws = quarantined_workspace(&tmp);
let out = memstead()
.current_dir(&ws)
.args(["--json", "mem", "set-schema", "plenum", "default@1.0.0"])
.assert()
.success()
.get_output()
.stdout
.clone();
let env = parse_envelope(&out);
assert_eq!(env["schema_pin"], "default@1.0.0", "got: {env}");
let health = health_json(&ws);
assert!(
health["quarantined"].is_null(),
"roster must be empty after repair: {health}"
);
memstead()
.current_dir(&ws)
.args(["--json", "status"])
.assert()
.success();
}
#[test]
fn install_then_set_schema_recovers_end_to_end() {
let tmp = TempDir::new().unwrap();
let ws = quarantined_workspace(&tmp);
let out = memstead()
.current_dir(&ws)
.args(["--json", "schema", "install", &example_package_dir()])
.assert()
.success()
.get_output()
.stdout
.clone();
let env = parse_envelope(&out);
assert_eq!(env["ok"], true, "got: {env}");
assert_eq!(env["schema"], "recipe@0.1.0");
memstead()
.current_dir(&ws)
.args(["--json", "mem", "set-schema", "plenum", "recipe@0.1.0"])
.assert()
.success();
let health = health_json(&ws);
assert!(
health["quarantined"].is_null(),
"mem returns to service on the installed ref: {health}"
);
memstead()
.current_dir(&ws)
.args(["--json", "status"])
.assert()
.success();
}
#[test]
fn repair_refuses_typed_on_corrupt_store_bad_package_and_bad_ref() {
let tmp = TempDir::new().unwrap();
let ws = quarantined_workspace(&tmp);
let out = memstead()
.current_dir(&ws)
.args(["--json", "mem", "set-schema", "plenum", "ghost2@1.0.0"])
.assert()
.failure()
.get_output()
.stdout
.clone();
let env = parse_envelope(&out);
assert_eq!(env["code"], "SCHEMA_NOT_FOUND", "got: {env}");
let health = health_json(&ws);
assert!(
health["quarantined"][0]["reason_message"]
.as_str()
.unwrap()
.contains("ghost@1.0.0"),
"original broken pin must be untouched: {health}"
);
let bad_pkg = tmp.path().join("badpkg");
std::fs::create_dir_all(&bad_pkg).unwrap();
std::fs::write(bad_pkg.join("schema.yaml"), "not: [valid").unwrap();
let out = memstead()
.current_dir(&ws)
.args(["--json", "schema", "install", bad_pkg.to_str().unwrap()])
.assert()
.failure()
.get_output()
.stdout
.clone();
let env = parse_envelope(&out);
assert_eq!(env["code"], "SCHEMA_VALIDATION_FAILED", "got: {env}");
let tmp2 = TempDir::new().unwrap();
let ws2 = tmp2.path().join("ws");
memstead()
.args(["mem-repo", "init", ws2.to_str().unwrap(), "--no-gitignore"])
.assert()
.success();
let mounts = ws2.join(".memstead").join("state").join("mounts.json");
std::fs::create_dir_all(mounts.parent().unwrap()).unwrap();
std::fs::write(&mounts, "this is not json {").unwrap();
let pkg = example_package_dir();
for args in [
vec!["--json", "mem", "set-schema", "plenum", "default@1.0.0"],
vec!["--json", "schema", "install", pkg.as_str()],
] {
let out = memstead()
.current_dir(&ws2)
.args(&args)
.assert()
.failure()
.get_output()
.stdout
.clone();
let env = parse_envelope(&out);
assert_eq!(
env["code"], "WORKSPACE_STORE_PARSE",
"corrupt store must refuse typed for {args:?}: {env}"
);
}
}
#[test]
fn projection_migrate_defers_cursor_seeding_when_boot_fails() {
let tmp = TempDir::new().unwrap();
let ws = tmp.path().join("ws");
memstead()
.args(["mem-repo", "init", ws.to_str().unwrap(), "--no-gitignore"])
.assert()
.success();
let mounts = ws.join(".memstead").join("state").join("mounts.json");
std::fs::create_dir_all(mounts.parent().unwrap()).unwrap();
std::fs::write(&mounts, "this is not json {").unwrap();
let cursor_path = ws.join(".memstead").join("reconcile-cursors.json");
std::fs::write(&cursor_path, r#"{"plenum:/abs/somewhere": "abc123"}"#).unwrap();
let out = memstead()
.current_dir(&ws)
.args(["--json", "projection", "migrate"])
.assert()
.success()
.get_output()
.stdout
.clone();
let env = parse_envelope(&out);
let notice = env["cursors_deferred"]
.as_str()
.expect("deferral notice must be present");
assert!(
notice.contains("RECONCILE_CURSORS_DEFERRED"),
"typed notice: {notice}"
);
assert!(
notice.contains("projection migrate"),
"notice names the follow-up: {notice}"
);
assert!(
cursor_path.exists(),
"deferral keeps the cursor file — silent loss is the refused shape"
);
let tmp2 = TempDir::new().unwrap();
let ws2 = tmp2.path().join("ws");
memstead()
.args(["mem-repo", "init", ws2.to_str().unwrap(), "--no-gitignore"])
.assert()
.success();
let cursor_path2 = ws2.join(".memstead").join("reconcile-cursors.json");
std::fs::write(&cursor_path2, r#"{"m:/abs/x": "abc123"}"#).unwrap();
let out = memstead()
.current_dir(&ws2)
.args(["--json", "projection", "migrate"])
.assert()
.success()
.get_output()
.stdout
.clone();
let env = parse_envelope(&out);
assert!(env["cursors_deferred"].is_null(), "no deferral: {env}");
assert!(
!cursor_path2.exists(),
"bootable workspace consumes and retires the cursor file as today"
);
}
#[test]
fn type_names_the_quarantine_instead_of_printing_a_default_over_it() {
let tmp = TempDir::new().unwrap();
let ws = quarantined_workspace(&tmp);
let out = memstead()
.current_dir(&ws)
.args(["--json", "type"])
.assert()
.success()
.get_output()
.stdout
.clone();
let body: serde_json::Value = serde_json::from_slice(&out).expect("--json is JSON");
assert_eq!(
body["fallback"]["code"], "ALL_MEMS_QUARANTINED",
"the condition must be machine-readable; got: {body}"
);
let detail = body["fallback"]["detail"].as_str().unwrap_or_default();
assert!(
detail.contains("plenum") && detail.contains("SCHEMA_NOT_FOUND"),
"the quarantined mem and the engine's typed reason must be named; got: {detail}"
);
assert!(
detail.contains("memstead schema install"),
"the engine's own repair command must be surfaced, not restated; got: {detail}"
);
let md = body["markdown"].as_str().unwrap_or_default();
assert!(
md.starts_with("**No mem is serving in this workspace**"),
"the reader must meet the condition before the catalogue; got: {md}"
);
}
#[test]
fn type_refusal_over_a_quarantine_states_the_condition_too() {
let tmp = TempDir::new().unwrap();
let ws = quarantined_workspace(&tmp);
let out = memstead()
.current_dir(&ws)
.args(["--json", "type", "no-such-type"])
.assert()
.failure()
.get_output()
.stdout
.clone();
let body: serde_json::Value = serde_json::from_slice(&out).expect("--json refusal is JSON");
assert_eq!(body["code"], "UNKNOWN_ENTITY_TYPE");
assert_eq!(
body["details"]["fallback"]["code"], "ALL_MEMS_QUARANTINED",
"the refusal must carry the condition machine-readably; got: {body}"
);
let message = body["message"].as_str().unwrap_or_default();
assert!(
message.contains("plenum") && message.contains("not this workspace's own"),
"the human message must name the quarantine too; got: {message}"
);
let healthy = tmp.path().join("healthy");
memstead()
.args([
"init",
healthy.to_str().unwrap(),
"--name",
"healthy-mem",
"--schema",
"default@1.0.0",
])
.assert()
.success();
let out = memstead()
.current_dir(&healthy)
.args(["--json", "type", "no-such-type"])
.assert()
.failure()
.get_output()
.stdout
.clone();
let body: serde_json::Value = serde_json::from_slice(&out).unwrap();
assert!(
body["details"]["fallback"].is_null(),
"a healthy refusal carries no condition; got: {body}"
);
}
#[test]
fn type_outside_a_workspace_stays_a_silent_cold_start_probe() {
let tmp = TempDir::new().unwrap();
let out = memstead()
.current_dir(tmp.path())
.args(["--json", "type"])
.assert()
.success()
.get_output()
.stdout
.clone();
let body: serde_json::Value = serde_json::from_slice(&out).unwrap();
assert!(
body["fallback"].is_null(),
"the cold-start probe must carry no fallback notice; got: {body}"
);
assert!(
!body["markdown"]
.as_str()
.unwrap_or_default()
.contains("built-in default, not"),
"no condition line belongs on the healthy cold-start path"
);
}
#[test]
fn type_on_a_healthy_workspace_reports_its_own_schema_silently() {
let tmp = TempDir::new().unwrap();
let ws = tmp.path().join("healthy");
memstead()
.args([
"init",
ws.to_str().unwrap(),
"--name",
"healthy-mem",
"--schema",
"default@1.0.0",
])
.assert()
.success();
let out = memstead()
.current_dir(&ws)
.args(["--json", "type"])
.assert()
.success()
.get_output()
.stdout
.clone();
let body: serde_json::Value = serde_json::from_slice(&out).unwrap();
assert!(
body["fallback"].is_null(),
"a healthy workspace carries no fallback notice; got: {body}"
);
assert_eq!(body["schema"], "default@1.0.0");
}
#[test]
fn type_mem_names_the_quarantine_instead_of_unknown_mem() {
let tmp = TempDir::new().unwrap();
let ws = quarantined_workspace(&tmp);
let out = memstead()
.current_dir(&ws)
.args(["--json", "type", "--mem", "plenum"])
.assert()
.failure()
.get_output()
.stdout
.clone();
let body = parse_envelope(&out);
assert_eq!(
body["code"], "MEM_QUARANTINED",
"naming a quarantined mem is not an unknown-mem condition; got: {body}"
);
let message = body["message"].as_str().unwrap_or_default();
assert!(
message.contains("SCHEMA_NOT_FOUND") && message.contains("memstead schema install"),
"the refusal must carry the engine's reason and repair; got: {message}"
);
}
#[test]
fn status_carries_the_quarantine_roster_in_both_forms() {
let tmp = TempDir::new().unwrap();
let ws = quarantined_workspace(&tmp);
let out = memstead()
.current_dir(&ws)
.args(["--json", "status"])
.assert()
.success()
.get_output()
.stdout
.clone();
let body = parse_envelope(&out);
let roster = body["quarantined"].as_array().expect("quarantined roster");
assert_eq!(roster.len(), 1, "got: {body}");
assert_eq!(roster[0]["mem"], "plenum");
assert_eq!(roster[0]["reason_code"], "SCHEMA_NOT_FOUND");
let md_bytes = memstead()
.current_dir(&ws)
.arg("status")
.assert()
.success()
.get_output()
.stdout
.clone();
let md = String::from_utf8(md_bytes).expect("stdout is UTF-8");
assert!(
md.contains("Quarantined mems (1)")
&& md.contains("plenum")
&& md.contains("SCHEMA_NOT_FOUND"),
"markdown status must render the quarantine roster; got:\n{md}"
);
}