#[cfg(test)]
mod tests {
use crate::api::SerialPort;
use crate::commands::watch;
use crate::events::{SerialEvent, WatchOptions};
use serde::Deserialize;
use std::sync::{Arc, Mutex};
use tauri::ipc::Channel;
use tauri::ipc::InvokeResponseBody;
use tauri::test::MockRuntime;
use tauri::Manager;
#[derive(Deserialize)]
struct WatchArgs {
path: String,
options: WatchOptions,
}
#[derive(Deserialize)]
struct UnwatchArgs {
#[serde(rename = "channelId")]
channel_id: u32,
}
#[derive(Deserialize)]
struct AvailablePortsArgs {
#[serde(rename = "singlePortPerDevice", default)]
single_port_per_device: bool,
}
#[test]
fn watch_args_deserialize_from_js_payload() {
let json = serde_json::json!({
"path": "/dev/ttyUSB0",
"options": {
"timeout": 500,
"size": 2048,
"serialDataFlushIntervalMs": 250
}
});
let args: WatchArgs = serde_json::from_value(json).unwrap();
assert_eq!(args.path, "/dev/ttyUSB0");
assert_eq!(args.options.timeout, Some(500));
assert_eq!(args.options.size, Some(2048));
assert_eq!(args.options.serial_data_flush_interval_ms, Some(250));
}
#[test]
fn unwatch_args_deserialize_channel_id() {
let json = serde_json::json!({ "channelId": 42 });
let args: UnwatchArgs = serde_json::from_value(json).unwrap();
assert_eq!(args.channel_id, 42);
}
#[test]
fn available_ports_args_default_single_port_false() {
let json = serde_json::json!({});
let args: AvailablePortsArgs = serde_json::from_value(json).unwrap();
assert!(!args.single_port_per_device);
}
#[test]
fn available_ports_args_single_port_true() {
let json = serde_json::json!({ "singlePortPerDevice": true });
let args: AvailablePortsArgs = serde_json::from_value(json).unwrap();
assert!(args.single_port_per_device);
}
#[test]
fn watch_command_requires_open_port_on_desktop() {
let app = tauri::test::mock_app();
let serial_port = SerialPort::new(app.handle().clone());
app.manage(serial_port);
let events: Arc<Mutex<Vec<SerialEvent>>> = Arc::new(Mutex::new(Vec::new()));
let events_clone = events.clone();
let channel = Channel::<SerialEvent>::new(move |body| {
if let InvokeResponseBody::Json(json) = body {
if let Ok(event) = serde_json::from_str::<SerialEvent>(&json) {
events_clone.lock().unwrap().push(event);
}
}
Ok(())
});
let result = watch(
app.handle().clone(),
app.state::<SerialPort<MockRuntime>>(),
"NONEXISTENT".to_string(),
WatchOptions::default(),
channel,
);
assert!(result.is_err());
}
#[test]
fn exchange_options_deserialize_result_format_and_at_intermediate() {
use crate::events::{ExchangeCompletionMode, ExchangeOptions};
let json = serde_json::json!({
"timeoutMs": 5000,
"resultFormat": "numeric",
"completionMode": "atIntermediate",
"command": "AT+CMGS=1",
"solicitedPrefixes": ["+CMGS:"]
});
let opts: ExchangeOptions = serde_json::from_value(json).unwrap();
let resolved = opts.resolve();
assert_eq!(
resolved.result_format,
crate::events::AtResultFormat::Numeric
);
assert_eq!(
resolved.completion_mode,
ExchangeCompletionMode::AtIntermediate
);
assert_eq!(resolved.command.as_deref(), Some("AT+CMGS=1"));
}
#[test]
fn exchange_match_intermediate_serde_roundtrip() {
use crate::at::parse::ExchangeMatch;
let m = ExchangeMatch::Intermediate {
line: ">".to_string(),
};
let json = serde_json::to_value(&m).unwrap();
assert_eq!(json["intermediate"]["line"], ">");
let back: ExchangeMatch = serde_json::from_value(json).unwrap();
assert!(matches!(back, ExchangeMatch::Intermediate { line } if line == ">"));
}
#[test]
fn exchange_match_numeric_ok_serde_roundtrip() {
use crate::at::parse::ExchangeMatch;
let m = ExchangeMatch::Ok;
let json = serde_json::to_value(&m).unwrap();
assert_eq!(json, "ok");
let back: ExchangeMatch = serde_json::from_value(json).unwrap();
assert!(matches!(back, ExchangeMatch::Ok));
}
#[test]
fn enable_mux_options_deserialize_camel_case() {
use crate::commands::EnableMuxOptions;
let json = serde_json::json!({
"command": "AT+CMUX=0,0,5,31",
"timeoutMs": 3000
});
let opts: EnableMuxOptions = serde_json::from_value(json).unwrap();
assert_eq!(opts.command.as_deref(), Some("AT+CMUX=0,0,5,31"));
assert_eq!(opts.timeout_ms, Some(3000));
}
#[test]
fn at_session_config_deserialize_camel_case() {
use crate::at::session::AtSessionConfig;
use crate::events::{AtResultFormat, RxPrepareMode};
let json = serde_json::json!({
"defaultTimeoutMs": 4000,
"defaultIdleMs": 50,
"defaultRxPrepare": "drain",
"stopOnError": false,
"appendCr": true,
"resultFormat": "verbose"
});
let cfg: AtSessionConfig = serde_json::from_value(json).unwrap();
assert_eq!(cfg.default_timeout_ms, Some(4000));
assert_eq!(cfg.default_idle_ms, Some(50));
assert_eq!(cfg.default_rx_prepare, Some(RxPrepareMode::Drain));
assert_eq!(cfg.stop_on_error, Some(false));
assert_eq!(cfg.append_cr, Some(true));
assert_eq!(cfg.result_format, Some(AtResultFormat::Verbose));
}
#[test]
fn at_command_options_deserialize_camel_case() {
use crate::at::session::AtCommandOptions;
use crate::events::ExchangeCompletionMode;
let json = serde_json::json!({
"timeoutMs": 1500,
"completionMode": "atIntermediate",
"solicitedPrefixes": ["+CMGS:"],
"appendCr": false
});
let opts: AtCommandOptions = serde_json::from_value(json).unwrap();
assert_eq!(opts.timeout_ms, Some(1500));
assert_eq!(
opts.completion_mode,
Some(ExchangeCompletionMode::AtIntermediate)
);
assert_eq!(
opts.solicited_prefixes.as_deref(),
Some(&["+CMGS:".to_string()][..])
);
assert_eq!(opts.append_cr, Some(false));
}
#[test]
fn at_phase_and_sms_pdu_options_deserialize_camel_case() {
use crate::at::session::{AtPhase, AtPhaseWrite, SendSmsPduOptions};
use crate::events::{AtResultFormat, ExchangeCompletionMode};
let phase_json = serde_json::json!({
"write": "AT+CMGS=12",
"completionMode": "atIntermediate",
"timeoutMs": 2000,
"command": "AT+CMGS"
});
let phase: AtPhase = serde_json::from_value(phase_json).unwrap();
assert!(matches!(phase.write, AtPhaseWrite::Text(ref s) if s == "AT+CMGS=12"));
assert_eq!(
phase.completion_mode,
Some(ExchangeCompletionMode::AtIntermediate)
);
assert_eq!(phase.timeout_ms, Some(2000));
let sms_json = serde_json::json!({
"timeoutMs": 9000,
"resultFormat": "numeric"
});
let sms: SendSmsPduOptions = serde_json::from_value(sms_json).unwrap();
assert_eq!(sms.timeout_ms, Some(9000));
assert_eq!(sms.result_format, Some(AtResultFormat::Numeric));
}
#[test]
fn watch_ports_options_deserialize_camel_case() {
use crate::events::WatchPortsOptions;
let json = serde_json::json!({
"singlePortPerDevice": true,
"pollIntervalMs": 1500
});
let opts: WatchPortsOptions = serde_json::from_value(json).unwrap();
assert_eq!(opts.single_port_per_device, Some(true));
assert_eq!(opts.poll_interval_ms, Some(1500));
}
#[test]
fn clear_buffer_args_deserialize_camel_case() {
use crate::state::ClearBuffer;
#[derive(Deserialize)]
struct ClearBufferArgs {
path: String,
#[serde(rename = "bufferType")]
buffer_type: ClearBuffer,
}
let json = serde_json::json!({
"path": "/dev/ttyUSB0",
"bufferType": "Input"
});
let args: ClearBufferArgs = serde_json::from_value(json).unwrap();
assert_eq!(args.path, "/dev/ttyUSB0");
assert_eq!(args.buffer_type, ClearBuffer::Input);
}
}