use super::store::Store;
use super::tests::cli;
use super::*;
use crate::tui::app::connection::{Connection, Key, Screen};
const MSTP_FILE: &str = r#"version = 1
transport = "mstp"
[mstp]
port = "/dev/ttyUSB0"
mac = 3
baud = 76800
max_master = 32
max_info_frames = 2
timeout_ms = 6000
min_interval_ms = 0
[safety]
writes = "session"
dangerous = false
"#;
fn mstp_profile() -> Profile {
let mut p = Profile::mstp_default();
let m = p.mstp_mut();
m.port = "/dev/ttyUSB0".into();
m.mac = 3;
m.baud = 76_800;
m.max_master = 32;
m.max_info_frames = 2;
p
}
#[test]
fn an_mstp_profile_saves_its_table_and_loads_it_back() {
let root = tempfile::tempdir().unwrap();
let store = Store::new(root.path().into());
store.save("trunk", &mstp_profile(), true).unwrap();
let path = root.path().join("profiles/trunk.toml");
assert_eq!(std::fs::read_to_string(&path).unwrap(), MSTP_FILE);
assert_eq!(store.load("trunk").unwrap(), mstp_profile());
let short = "version = 1\ntransport = \"mstp\"\n\n[mstp]\nport = \"COM3\"\nmac = 0\n\n\
[safety]\nwrites = \"never\"\ndangerous = false\n";
std::fs::write(&path, short).unwrap();
let m = store.load("trunk").unwrap().mstp.unwrap();
assert_eq!(
(m.baud, m.max_master, m.max_info_frames, m.timeout_ms),
(38_400, 127, 1, 6000)
);
for text in [
MSTP_FILE.replace("mac = 3\n", ""),
MSTP_FILE.replace("port = \"/dev/ttyUSB0\"\n", ""),
MSTP_FILE.replace("[mstp]", "[bip]"),
MSTP_FILE.replace("transport = \"mstp\"", "transport = \"bip\""),
MSTP_FILE.replace("[mstp]\n", "[mstp]\nparity = \"none\"\n"),
] {
std::fs::write(&path, &text).unwrap();
assert!(store.load("trunk").is_err(), "{text}");
}
}
#[test]
fn mstp_settings_are_checked_before_any_port_opens() {
assert_eq!(mstp_profile().validate(), Ok(()));
for (change, expected) in [
(
(|m: &mut Mstp| m.baud = 4800) as fn(&mut Mstp),
"baud must be one of",
),
(
|m| m.mac = 128,
"mac a master station's MAC must be 0 to 127",
),
(|m| m.mac = 40, "mac 40 is above max_master 32"),
(
|m| m.max_info_frames = 0,
"max_info_frames must be 1 to 255",
),
(|m| m.port.clear(), "serial port is required"),
(|m| m.min_interval_ms = 3_600_001, "min_interval_ms"),
] {
let mut p = mstp_profile();
change(p.mstp_mut());
let error = p.validate().unwrap_err();
assert!(error.contains(expected), "{error}");
assert!(p.transport_args().is_err());
}
let args = mstp_profile().transport_args().unwrap();
assert_eq!(args.mstp, Some(mstp_profile().mstp.unwrap().args()));
assert_eq!(args.timeout_ms, 6000);
assert!(!args.sc && args.foreign_device.is_none());
assert_eq!(Transport::Mstp.kind().label(), "MSTP");
}
#[test]
fn mstp_flags_override_an_mstp_profile_only() {
let overrides = Overrides::from_cli(&cli(&[
"bacnet",
"--mstp",
"/dev/ttyUSB1",
"--mstp-mac",
"5",
"--timeout",
"9000",
"tui",
"--profile",
"trunk",
]))
.unwrap();
let resolved = overrides.resolve(&mstp_profile()).unwrap();
let m = resolved.mstp.unwrap();
assert_eq!(
(m.port.as_str(), m.mac, m.baud, m.timeout_ms),
("/dev/ttyUSB1", 5, 76_800, 9000)
);
let error = overrides.resolve(&Profile::default()).unwrap_err();
assert!(error.contains("MS/TP flags"), "{error}");
let bip_flags =
Overrides::from_cli(&cli(&["bacnet", "-p", "47809", "tui", "--profile", "x"])).unwrap();
let error = bip_flags.resolve(&mstp_profile()).unwrap_err();
assert!(error.contains("don't apply to an MS/TP profile"), "{error}");
assert!(!startup::enabled(&cli(&[
"bacnet",
"--mstp",
"/dev/ttyUSB0",
"--mstp-mac",
"3",
"tui"
])));
}
#[test]
fn the_editor_creates_an_mstp_profile() {
let mut connection = Connection::new(Overrides::default());
assert_eq!(connection.key(Key::NewMstp), None);
assert_eq!(connection.screen, Screen::Edit);
assert_eq!(connection.transport, Transport::Mstp);
assert_eq!(connection.labels().len(), connection.fields.len());
assert_eq!(connection.fields[2], "", "no default MAC");
for ch in "trunk".chars() {
connection.key(Key::Char(ch));
}
connection.key(Key::Next);
for ch in "/dev/ttyUSB0".chars() {
connection.key(Key::Char(ch));
}
assert_eq!(connection.key(Key::Submit), None);
assert!(connection
.error
.as_ref()
.unwrap()
.contains("MAC must be 0 to 127"));
connection.key(Key::Next);
connection.key(Key::Char('3'));
let Some(crate::tui::message::Command::Profile(service::Request::Save { profile, .. })) =
connection.key(Key::Submit)
else {
panic!("expected a save: {:?}", connection.error);
};
let m = profile.mstp.unwrap();
assert_eq!(
(m.port.as_str(), m.mac, m.baud),
("/dev/ttyUSB0", 3, 38_400)
);
}