buttplug 2.1.5

Buttplug Intimate Hardware Control Library
Documentation
mod util;
extern crate buttplug;

use buttplug::{
  client::{ButtplugClient, ButtplugClientError, ButtplugClientEvent, VibrateCommand},
  connector::{
    ButtplugConnector,
    ButtplugConnectorError,
    ButtplugConnectorResultFuture,
    ButtplugInProcessClientConnector,
  },
  core::{
    errors::{ButtplugDeviceError, ButtplugError},
    messages::{ButtplugCurrentSpecClientMessage, ButtplugCurrentSpecServerMessage},
  },
  device::{Endpoint, DeviceImplCommand, DeviceWriteCmd},
  server::ButtplugServerOptions,
  test::check_test_recv_value,
  util::async_manager,
};
use futures::{future::BoxFuture, StreamExt};
use futures_timer::Delay;
use std::time::Duration;
use tokio::sync::mpsc::Sender;
use util::DelayDeviceCommunicationManager;

#[derive(Default)]
struct ButtplugFailingConnector {}

impl ButtplugConnector<ButtplugCurrentSpecClientMessage, ButtplugCurrentSpecServerMessage>
  for ButtplugFailingConnector
{
  fn connect(
    &mut self,
    _: Sender<ButtplugCurrentSpecServerMessage>,
  ) -> BoxFuture<'static, Result<(), ButtplugConnectorError>> {
    ButtplugConnectorError::ConnectorNotConnected.into()
  }

  fn disconnect(&self) -> ButtplugConnectorResultFuture {
    ButtplugConnectorError::ConnectorNotConnected.into()
  }

  fn send(&self, _msg: ButtplugCurrentSpecClientMessage) -> ButtplugConnectorResultFuture {
    panic!("Should never be called")
  }
}

#[cfg(feature = "server")]
#[test]
fn test_failing_connection() {
  async_manager::block_on(async {
    let client = ButtplugClient::new("Test Client");
    assert!(client
      .connect(ButtplugFailingConnector::default())
      .await
      .is_err());
  });
}

#[cfg(feature = "server")]
#[test]
fn test_disconnect_status() {
  async_manager::block_on(async {
    let client = ButtplugClient::new("Test Client");
    client
      .connect(ButtplugInProcessClientConnector::default())
      .await
      .unwrap();
    assert!(client.disconnect().await.is_ok());
    assert!(!client.connected());
  });
}

#[cfg(feature = "server")]
#[test]
fn test_double_disconnect() {
  async_manager::block_on(async {
    let client = ButtplugClient::new("Test Client");
    client
      .connect(ButtplugInProcessClientConnector::default())
      .await
      .unwrap();
    assert!(client.disconnect().await.is_ok());
    assert!(client.disconnect().await.is_err());
  });
}

#[cfg(feature = "server")]
#[test]
fn test_connect_init() {
  async_manager::block_on(async {
    let client = ButtplugClient::new("Test Client");
    client
      .connect(ButtplugInProcessClientConnector::default())
      .await
      .unwrap();
    assert_eq!(client.server_name(), Some("Buttplug Server".to_owned()));
  });
}

#[cfg(feature = "server")]
#[test]
fn test_client_connected_status() {
  async_manager::block_on(async {
    let client = ButtplugClient::new("Test Client");
    assert!(!client.connected());
    client
      .connect(ButtplugInProcessClientConnector::default())
      .await
      .unwrap();
    assert!(client.connected());
    client.disconnect().await.unwrap();
    assert!(!client.connected());
  });
}

#[cfg(feature = "server")]
#[test]
fn test_start_scanning() {
  async_manager::block_on(async {
    let connector = ButtplugInProcessClientConnector::default();
    let test_mgr_helper = connector.server_ref().add_test_comm_manager().unwrap();
    test_mgr_helper.add_ble_device("Massage Demo").await;
    let client = ButtplugClient::new("Test Client");
    client.connect(connector).await.unwrap();
    assert!(client.start_scanning().await.is_ok());
  });
}

#[cfg(feature = "server")]
#[test]
#[ignore]
fn test_stop_scanning_when_not_scanning() {
  async_manager::block_on(async {
    let connector = ButtplugInProcessClientConnector::default();
    connector
      .server_ref()
      .add_comm_manager::<DelayDeviceCommunicationManager>()
      .unwrap();
    let client = ButtplugClient::new("Test Client");
    client.connect(connector).await.unwrap();
    let should_be_err = client.stop_scanning().await;
    if let Err(ButtplugClientError::ButtplugError(bp_err)) = should_be_err {
      assert!(matches!(
        bp_err,
        ButtplugError::ButtplugDeviceError(ButtplugDeviceError::DeviceScanningAlreadyStopped)
      ));
    } else {
      panic!("Should've thrown error!");
    }
    assert!(client.stop_scanning().await.is_err());
  });
}

#[cfg(feature = "server")]
#[test]
fn test_start_scanning_when_already_scanning() {
  async_manager::block_on(async {
    let connector = ButtplugInProcessClientConnector::default();
    connector
      .server_ref()
      .add_comm_manager::<DelayDeviceCommunicationManager>()
      .unwrap();
    let client = ButtplugClient::new("Test Client");
    client.connect(connector).await.unwrap();
    assert!(client.start_scanning().await.is_ok());
    assert!(client.start_scanning().await.is_err());
  });
}

#[cfg(feature = "server")]
#[test]
fn test_client_scanning_finished() {
  async_manager::block_on(async {
    let connector = ButtplugInProcessClientConnector::default();
    connector
      .server_ref()
      .add_comm_manager::<DelayDeviceCommunicationManager>()
      .unwrap();
    let client = ButtplugClient::new("Test Client");
    let mut recv = client.event_stream();
    client.connect(connector).await.unwrap();

    assert!(client.start_scanning().await.is_ok());
    assert!(client.stop_scanning().await.is_ok());
    assert!(matches!(
      recv.next().await.unwrap(),
      ButtplugClientEvent::ScanningFinished
    ));
  });
}

#[cfg(feature = "server")]
#[test]
fn test_client_ping() {
  async_manager::block_on(async {
    let mut options = ButtplugServerOptions::default();
    options.max_ping_time = 200;
    let connector = ButtplugInProcessClientConnector::new_with_options(&options).unwrap();
    let client = ButtplugClient::new("Test Client");
    client.connect(connector).await.unwrap();
    assert!(client.ping().await.is_ok());
    Delay::new(Duration::from_millis(800)).await;
    // TODO Watch for ping events
    assert!(client.ping().await.is_err());
  });
}

// Tests both the stop all devices functionality, as well as both ends of the
// command range for is_in_command_range message validation.
#[cfg(feature = "server")]
#[test]
fn test_stop_all_devices_and_device_command_range() {
  async_manager::block_on(async {
    let connector = ButtplugInProcessClientConnector::default();
    let test_mgr_helper = connector.server_ref().add_test_comm_manager().unwrap();
    let test_device = test_mgr_helper.add_ble_device("Massage Demo").await;
    let client = ButtplugClient::new("Test Client");
    let mut event_stream = client.event_stream();
    client.connect(connector).await.unwrap();
    assert!(client.start_scanning().await.is_ok());
    while let Some(event) = event_stream.next().await {
      if let ButtplugClientEvent::DeviceAdded(dev) = event {
        assert!(dev.vibrate(VibrateCommand::Speed(0.5)).await.is_ok());
        // Unlike protocol unit tests, here the endpoint doesn't exist until
        // after device creation, so create the test receiver later.
        let command_receiver = test_device.get_endpoint_receiver(&Endpoint::Tx).unwrap();
        check_test_recv_value(
          &command_receiver,
          DeviceImplCommand::Write(DeviceWriteCmd::new(Endpoint::Tx, vec![0xF1, 64], false)),
        );
        check_test_recv_value(
          &command_receiver,
          DeviceImplCommand::Write(DeviceWriteCmd::new(Endpoint::Tx, vec![0xF2, 64], false)),
        );
        assert!(dev.vibrate(VibrateCommand::Speed(1.0)).await.is_ok());
        check_test_recv_value(
          &command_receiver,
          DeviceImplCommand::Write(DeviceWriteCmd::new(Endpoint::Tx, vec![0xF1, 127], false)),
        );
        check_test_recv_value(
          &command_receiver,
          DeviceImplCommand::Write(DeviceWriteCmd::new(Endpoint::Tx, vec![0xF2, 127], false)),
        );
        assert!(client.stop_all_devices().await.is_ok());
        check_test_recv_value(
          &command_receiver,
          DeviceImplCommand::Write(DeviceWriteCmd::new(Endpoint::Tx, vec![0xF1, 0], false)),
        );
        check_test_recv_value(
          &command_receiver,
          DeviceImplCommand::Write(DeviceWriteCmd::new(Endpoint::Tx, vec![0xF2, 0], false)),
        );
        break;
      }
    }
    assert!(client.stop_all_devices().await.is_ok());
  });
}

// TODO Test calling connect twice
// TODO Test calling disconnect twice w/o connection
// TODO Test invalid return on RequestServerInfo
// TODO Test invalid return on DeviceList
// TODO Test receiving unmatched Ok (should emit error)
// TODO Test receiving unmatched DeviceRemoved
// TODO Test receiving Error when expecting Ok (i.e. StartScanning returns an error)
// TODO Test receiving wrong message expecting Ok (i.e. StartScanning returns DeviceList)