tauri-plugin-serialplugin 3.0.5

Tauri plugin for serial port access (desktop and Android).
//! Command inventory: handler ↔ guest-js ↔ ACL permission files stay in sync.

#[cfg(test)]
mod tests {
    use std::collections::BTreeSet;
    use std::fs;
    use std::path::PathBuf;

    /// Keep in sync with `generate_handler!` in `src/lib.rs`.
    const HANDLER_COMMANDS: &[&str] = &[
        "available_ports",
        "managed_ports",
        "cancel_read",
        "close",
        "close_all",
        "force_close",
        "open",
        "capabilities",
        "watch",
        "unwatch",
        "watch_ports",
        "unwatch_ports",
        "read",
        "read_binary",
        "write",
        "write_binary",
        "set_baud_rate",
        "set_data_bits",
        "set_flow_control",
        "set_parity",
        "set_stop_bits",
        "set_timeout",
        "write_request_to_send",
        "write_data_terminal_ready",
        "read_clear_to_send",
        "read_data_set_ready",
        "read_ring_indicator",
        "read_carrier_detect",
        "bytes_to_read",
        "bytes_to_write",
        "clear_buffer",
        "set_break",
        "clear_break",
        "set_log_level",
        "get_log_level",
        "exchange",
        "exchange_binary",
        "cancel_exchange",
        "at",
        "at_phases",
        "send_sms_pdu",
        "configure_at_session",
        "enable_mux",
        "open_mux_channel",
        "disable_mux",
    ];

    fn repo_root() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
    }

    fn permission_command_names() -> BTreeSet<String> {
        let dir = repo_root().join("permissions/autogenerated/commands");
        fs::read_dir(&dir)
            .unwrap_or_else(|e| panic!("read {}: {e}", dir.display()))
            .filter_map(|e| e.ok())
            .filter_map(|e| {
                let name = e.file_name().into_string().ok()?;
                name.strip_suffix(".toml").map(str::to_string)
            })
            .collect()
    }

    fn guest_js_invoke_commands() -> BTreeSet<String> {
        let js = fs::read_to_string(repo_root().join("guest-js/serial-port.ts"))
            .expect("guest-js/serial-port.ts");
        let needle = "plugin:serialplugin|";
        let mut out = BTreeSet::new();
        let mut rest = js.as_str();
        while let Some(i) = rest.find(needle) {
            let after = &rest[i + needle.len()..];
            let end = after
                .find(|c: char| !c.is_ascii_alphanumeric() && c != '_')
                .unwrap_or(after.len());
            out.insert(after[..end].to_string());
            rest = &after[end..];
        }
        out
    }

    #[test]
    fn handler_commands_match_permission_files() {
        let perms = permission_command_names();
        let handler: BTreeSet<_> = HANDLER_COMMANDS.iter().map(|s| (*s).to_string()).collect();
        assert_eq!(
            handler, perms,
            "mismatch generate_handler! ↔ permissions/autogenerated/commands"
        );
    }

    #[test]
    fn guest_js_invokes_exactly_handler_commands() {
        let js = guest_js_invoke_commands();
        let handler: BTreeSet<_> = HANDLER_COMMANDS.iter().map(|s| (*s).to_string()).collect();
        assert_eq!(js, handler, "mismatch guest-js invoke ↔ generate_handler!");
    }

    #[test]
    fn default_permission_set_covers_all_allow_commands() {
        let default = fs::read_to_string(repo_root().join("permissions/default.toml"))
            .expect("permissions/default.toml");
        for cmd in HANDLER_COMMANDS {
            let allow = format!("allow-{}", cmd.replace('_', "-"));
            let present = default.lines().any(|line| {
                let trimmed = line.trim().trim_end_matches(',');
                trimmed == format!("\"{allow}\"")
            });
            assert!(present, "default.toml missing {allow} for command {cmd}");
        }
    }
}