mod common;
use common::{copy_fixture, run_ok, workdir};
fn builder_factory_service() -> tempfile::TempDir {
let repo = copy_fixture("builder-factory-service");
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
repo
}
fn exported_ir(repo: &tempfile::TempDir) -> serde_json::Value {
let out = run_ok(&workdir(repo.path()), &["export", "system-ir.json"]);
serde_json::from_str(&out).unwrap()
}
#[test]
fn builder_and_factory_registrations_surface() {
let repo = builder_factory_service();
let ir = exported_ir(&repo);
let entities = ir["entities"].as_array().unwrap();
let contracts: Vec<&serde_json::Value> = entities
.iter()
.filter(|e| e["kind"] == "contract")
.collect();
assert!(
contracts
.iter()
.any(|e| e["name"] == "object" && e["attributes"]["kind"] == "factory"),
"z.object factory contract missing: {contracts:?}"
);
assert!(
contracts
.iter()
.any(|e| e["name"] == "string" && e["attributes"]["kind"] == "factory"),
"z.string factory contract missing"
);
assert!(
contracts
.iter()
.any(|e| e["name"] == "create" && e["attributes"]["kind"] == "factory"),
"axios.create factory contract missing"
);
assert!(
contracts.iter().all(|e| e["attributes"]["kind"].is_string()),
"contracts must carry a registration kind"
);
let registers: Vec<(&str, &str)> = ir["relationships"]
.as_array()
.unwrap()
.iter()
.filter(|r| r["predicate"] == "registers")
.filter_map(|r| Some((r["subject"].as_str()?, r["object"].as_str()?)))
.collect();
let register_names: Vec<String> = registers
.iter()
.map(|(s, o)| format!("{s} -> {o}"))
.collect();
for owner in [
"Session", "create_session", "Builder", "ImmutableList", "Command", "New", "createApp", "createClient", "z", ] {
assert!(
register_names.iter().any(|r| {
r.split(" -> ")
.next()
.map(|s| s.contains(owner))
.unwrap_or(false)
}),
"{owner} registration missing: {register_names:?}"
);
}
let verify = run_ok(&workdir(repo.path()), &["check-invariants"]);
assert!(
verify.contains("ok") || verify.is_empty(),
"check-invariants must pass with builder/factory contracts: {verify}"
);
}
#[test]
fn module_globals_and_statics_are_mutable_state() {
let repo = builder_factory_service();
let ir = exported_ir(&repo);
let fields: Vec<&serde_json::Value> = ir["entities"]
.as_array()
.unwrap()
.iter()
.filter(|e| e["kind"] == "field")
.collect();
let mutable_field_names: Vec<String> = fields
.iter()
.filter(|e| e["attributes"]["mutable"].as_bool() == Some(true))
.filter_map(|e| e["name"].as_str().map(str::to_string))
.collect();
assert!(
mutable_field_names
.iter()
.any(|n| n == "requests_style.DEFAULT_TIMEOUT"),
"python module global missing: {mutable_field_names:?}"
);
assert!(
mutable_field_names
.iter()
.any(|n| n == "zerolog_style.DefaultLogger"),
"go package var missing: {mutable_field_names:?}"
);
assert!(
mutable_field_names
.iter()
.any(|n| n == "ImmutableList.DEFAULT_CAPACITY"),
"java static field missing: {mutable_field_names:?}"
);
assert!(
!mutable_field_names
.iter()
.any(|n| n == "ImmutableList.PACKAGE"),
"final static must not be mutable: {mutable_field_names:?}"
);
assert!(
mutable_field_names
.iter()
.any(|n| n == "axios_style.defaultTimeout"),
"ts module let missing: {mutable_field_names:?}"
);
let module_syms: Vec<&str> = ir["entities"]
.as_array()
.unwrap()
.iter()
.filter(|e| {
e["kind"] == "symbol" && e["attributes"]["kind"].as_str() == Some("module")
})
.filter_map(|e| e["name"].as_str())
.collect();
for want in ["requests_style", "zerolog_style", "axios_style"] {
assert!(
module_syms.contains(&want),
"module symbol {want} missing: {module_syms:?}"
);
}
}