tauri-plugin-serialplugin 3.0.5

Tauri plugin for serial port access (desktop and Android).
#[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);
    }
}