//! Fleet layout and node-config tests. The bus daemon is a fake `tines-busd` //! seeded onto PATH (see `common`; the real binary isn't on PATH for unit //! tests): it creates the socket file named by argv[1], so the fleet's //! spawn protocol is what's under test — the real daemon is covered by //! tines-bus's own suite and Task 4's e2e. mod common; use std::os::unix::fs::PermissionsExt; use std::time::{Duration, Instant}; use tines_gui::fleet::{Fleet, Mode}; fn fleet_name(tag: &str) -> String { format!( "test-{tag}-{}", std::thread::current().name().unwrap_or("t") ) } #[test] fn fleet_dir_layout() { common::env_with_fake_busd(); let fleet = Fleet::create(&fleet_name("layout")).expect("fleet creates"); let dir = tines_gui::config::fleet_dir(&fleet_name("layout")); let meta = std::fs::metadata(&dir).expect("fleet dir exists"); assert!(meta.is_dir()); assert_eq!(meta.permissions().mode() & 0o777, 0o700); // The fake daemon creates the bus socket the fleet asked it to bind. let socket = fleet.bus_socket(); assert_eq!(socket, dir.join("bus.sock")); let deadline = Instant::now() + Duration::from_secs(5); while !socket.exists() { assert!( Instant::now() < deadline, "bus.sock never appeared: {}", socket.display() ); std::thread::sleep(Duration::from_millis(50)); } } #[test] fn add_node_bus_config_mounts_data() { common::env_with_fake_busd(); let fleet_name = fleet_name("bus"); let fleet = Fleet::create(&fleet_name).expect("fleet creates"); let spec = fleet .add_node("alpha", Some(&"11".repeat(16)), Mode::Sim, None) .expect("node added"); let json = std::fs::read_to_string(spec.dir.join("config.json")).unwrap(); let parsed: tines::config::Config = serde_json::from_str(&json).expect("config parses as the service config"); assert_eq!(parsed.transport, Some(tines::config::TransportKind::Bus)); assert_eq!(parsed.bus_socket, Some("/data/bus.sock".to_string())); assert_eq!(parsed.socket_path, "/data/alpha/sock".to_string()); assert_eq!(parsed.log_path, "/data/alpha/log".to_string()); assert_eq!(parsed.node_id, "11".repeat(16)); assert_eq!(parsed.validate(), Ok(())); } #[test] fn add_node_bt_config_has_peer() { common::env_with_fake_busd(); let fleet_name = fleet_name("bt"); let fleet = Fleet::create(&fleet_name).expect("fleet creates"); let peer = "22".repeat(16); let spec = fleet .add_node("bravo", None, Mode::Bt, Some(&peer)) .expect("node added"); let json = std::fs::read_to_string(spec.dir.join("config.json")).unwrap(); let parsed: tines::config::Config = serde_json::from_str(&json).expect("config parses as the service config"); assert_eq!(parsed.transport, Some(tines::config::TransportKind::Bt)); assert_eq!(parsed.peer, Some(peer)); assert_eq!(parsed.bus_socket, None); assert_eq!(parsed.validate(), Ok(())); } #[test] fn generated_id_roundtrips() { common::env_with_fake_busd(); let fleet_name = fleet_name("gen"); let fleet = Fleet::create(&fleet_name).expect("fleet creates"); let spec = fleet .add_node("charlie", None, Mode::Sim, None) .expect("node added"); let json = std::fs::read_to_string(spec.dir.join("config.json")).unwrap(); let parsed: tines::config::Config = serde_json::from_str(&json).expect("config parses as the service config"); assert_eq!(parsed.node_id.len(), 32); assert!( parsed .node_id .chars() .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()), "generated id must be lowercase hex" ); }