use smix_simctl::registry::{DeviceKind, RegisteredSim, SimRegistry};
use std::path::{Path, PathBuf};
fn sim(udid: &str, opt_in: bool) -> RegisteredSim {
RegisteredSim {
device_name: format!("dev {udid}"),
kind: DeviceKind::Simulator,
destructive_opt_in: opt_in,
udid: udid.into(),
runtime: "com.apple.CoreSimulator.SimRuntime.iOS-26-5".into(),
device_type: String::new(),
avd_name: None,
locale: None,
runner_port: None,
}
}
fn book(root: &Path, name: &str, rows: &[(&str, RegisteredSim)]) -> PathBuf {
let dir = root.join(name).join(".smix");
std::fs::create_dir_all(&dir).unwrap();
for (alias, s) in rows {
SimRegistry::register(&dir, alias, s.clone()).unwrap();
}
dir
}
fn tmp(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"smix-migrate-{tag}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn every_device_in_every_book_survives() {
let root = tmp("union");
let a = book(&root, "a", &[("dev", sim("UDID-1", false))]);
let b = book(&root, "b", &[("phone", sim("UDID-2", false))]);
let c = book(&root, "c", &[("dev", sim("UDID-1", false))]);
let machine = root.join("machine");
let report = SimRegistry::migrate(&machine, &[a, b, c]).unwrap();
let got = SimRegistry::load(&machine).unwrap();
let udids: std::collections::BTreeSet<String> =
got.sims().values().map(|s| s.udid.clone()).collect();
assert_eq!(
udids,
["UDID-1".to_string(), "UDID-2".to_string()].into(),
"report was {report:?}"
);
}
#[test]
fn a_device_known_by_two_names_answers_to_both() {
let root = tmp("aliases");
let a = book(&root, "a", &[("dev", sim("UDID-1", false))]);
let b = book(&root, "b", &[("sim-01", sim("UDID-1", false))]);
let machine = root.join("machine");
SimRegistry::migrate(&machine, &[a, b]).unwrap();
let got = SimRegistry::load(&machine).unwrap();
assert_eq!(got.resolve("dev").unwrap(), "UDID-1");
assert_eq!(got.resolve("sim-01").unwrap(), "UDID-1");
}
#[test]
fn conflicting_consent_lands_on_the_stricter_answer() {
let root = tmp("consent");
let a = book(&root, "a", &[("phone", sim("UDID-9", true))]);
let b = book(&root, "b", &[("phone", sim("UDID-9", false))]);
let machine = root.join("machine");
SimRegistry::migrate(&machine, &[a, b]).unwrap();
let got = SimRegistry::load(&machine).unwrap();
assert!(
!got.lookup("phone").unwrap().destructive_opt_in,
"consent widened during a migration"
);
}
#[test]
fn a_source_that_will_not_open_is_named_not_fatal() {
let root = tmp("corrupt");
let good = book(&root, "good", &[("dev", sim("UDID-1", false))]);
let missing = root.join("nope").join(".smix");
let machine = root.join("machine");
let report = SimRegistry::migrate(&machine, &[good, missing.clone()]).unwrap();
let got = SimRegistry::load(&machine).unwrap();
assert!(got.lookup("dev").is_some(), "the readable book was dropped");
assert!(
report.empty.iter().any(|p| p == &missing),
"a source that gave nothing was not reported: {report:?}"
);
}
#[test]
fn the_source_book_is_left_where_it_was() {
let root = tmp("nondestructive");
let a = book(&root, "a", &[("dev", sim("UDID-1", false))]);
let machine = root.join("machine");
SimRegistry::migrate(&machine, std::slice::from_ref(&a)).unwrap();
let still = SimRegistry::load(&a).unwrap();
assert_eq!(still.resolve("dev").unwrap(), "UDID-1");
}
#[test]
fn a_second_run_is_a_no_op() {
let root = tmp("idempotent");
let a = book(&root, "a", &[("dev", sim("UDID-1", false))]);
let machine = root.join("machine");
SimRegistry::migrate(&machine, std::slice::from_ref(&a)).unwrap();
let first: Vec<String> = SimRegistry::load(&machine)
.unwrap()
.sims()
.keys()
.cloned()
.collect();
let second_report = SimRegistry::migrate(&machine, &[a]).unwrap();
let second: Vec<String> = SimRegistry::load(&machine)
.unwrap()
.sims()
.keys()
.cloned()
.collect();
assert_eq!(first, second, "a second migration changed the aliases");
assert!(
second_report.added.is_empty(),
"a second migration claimed to add something: {second_report:?}"
);
}