[fp-rs] Added FP Advertisement unit tests in Rust.

This commit is contained in:
Lucas Silva Shepard
2023-08-15 16:44:48 -07:00
parent 169c04c430
commit fb95e26bf5
4 changed files with 125 additions and 18 deletions
@@ -33,7 +33,7 @@ type DecibelMilliwatts = i16;
impl BleAdvertisement {
/// Construct a new `BleAdvertisement` instance.
pub(crate) fn new(
pub fn new(
address: BleAddress,
rssi: Option<DecibelMilliwatts>,
tx_power: Option<DecibelMilliwatts>,
+2 -2
View File
@@ -17,8 +17,8 @@ mod common;
use api::{BleAdapter, BleDevice, ClassicDevice};
pub use common::{
BleAddress, BleAdvertisement, BleDataTypeId, BluetoothError,
ClassicAddress, PairingResult, ServiceData,
BleAddress, BleAddressKind, BleAdvertisement, BleDataTypeId,
BluetoothError, ClassicAddress, PairingResult, ServiceData,
};
cfg_if::cfg_if! {
+113 -12
View File
@@ -14,10 +14,7 @@
use bluetooth::{BleAddress, BleAdvertisement, ServiceData};
use crate::{
decoder::FpDecoder,
fetcher::{DeviceInfo, FpFetcher, FpFetcherFs},
};
use crate::{decoder::FpDecoder, fetcher::FpFetcher};
/// Represents a FP device model ID.
pub(crate) type ModelId = String;
@@ -30,7 +27,7 @@ pub(crate) struct FpPairingAdvertisement {
/// Estimated distance in meters of device from BLE adapter.
distance: f64,
model_id: ModelId,
name: String,
device_name: String,
image_url: String,
}
@@ -39,6 +36,7 @@ impl FpPairingAdvertisement {
pub(crate) fn new(
adv: BleAdvertisement,
service_data: &ServiceData<u16>,
fetcher: &Box<dyn FpFetcher>,
) -> Result<Self, anyhow::Error> {
let rssi = adv.rssi().ok_or(anyhow::anyhow!(
"Windows advertisements should contain RSSI information."
@@ -78,16 +76,13 @@ impl FpPairingAdvertisement {
let model_id = format!("{}", u32::from_be_bytes(model_id.try_into().unwrap()));
// Retrieve device info of the device corresponding to this model ID.
let fetcher = FpFetcherFs::new(String::from("./local"));
let device_info = fetcher
.get_device_info_from_model_id(&model_id)
.expect("Failed to create device info from model ID.");
let device_info = fetcher.get_device_info_from_model_id(&model_id)?;
Ok(FpPairingAdvertisement {
inner: adv,
distance,
model_id,
name: device_info.name().to_string(),
device_name: device_info.name().to_string(),
image_url: device_info.image_url().to_string(),
})
}
@@ -109,8 +104,8 @@ impl FpPairingAdvertisement {
&self.model_id
}
pub(crate) fn name(&self) -> &String {
&self.name
pub(crate) fn device_name(&self) -> &String {
&self.device_name
}
pub(crate) fn image_url(&self) -> &String {
@@ -149,3 +144,109 @@ pub(crate) fn distance_from_rssi_and_tx_power(rssi: i16, tx_power: i16) -> f64 {
(f64::from(tx_power - rssi - RSSI_DROPOFF_AT_1_M)) / f64::from(10 * PATH_LOSS_EXPONENT),
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fetcher::{mock::FpFetcherMock, DeviceInfo};
use bluetooth::BleAddressKind;
#[test]
fn test_new_fp_pairing_advertisement() {
let addr = BleAddress::new(0x112233, BleAddressKind::Public);
let ble_adv = BleAdvertisement::new(addr, Some(-60), Some(10));
let raw_data = vec![3, 2, 1];
let expected_model_id = "197121"; // (3 << 16) + (2 << 8) + 1.
let service_data = ServiceData::new(0x123 as u16, raw_data);
let image_url = String::from("image_url");
let device_name = String::from("name");
let device_info = Ok(DeviceInfo::new(image_url.clone(), device_name.clone()));
let fetcher: Box<dyn FpFetcher> = Box::new(FpFetcherMock::new(device_info));
let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher);
assert!(fp_adv.is_ok());
let fp_adv = fp_adv.unwrap();
assert_eq!(fp_adv.address(), addr);
assert_eq!(fp_adv.image_url(), &image_url);
assert_eq!(fp_adv.device_name(), &device_name);
assert_eq!(fp_adv.model_id(), &expected_model_id);
}
#[test]
fn test_new_fp_pairing_advertisement_bad_rssi() {
let addr = BleAddress::new(0x112233, BleAddressKind::Public);
let ble_adv = BleAdvertisement::new(addr, None, Some(10));
let raw_data = vec![3, 2, 1];
let service_data = ServiceData::new(0x123 as u16, raw_data);
let device_info = Ok(DeviceInfo::new(
String::from("image_url"),
String::from("name"),
));
let fetcher: Box<dyn FpFetcher> = Box::new(FpFetcherMock::new(device_info));
let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher);
assert!(fp_adv.is_err());
}
#[test]
fn test_new_fp_pairing_advertisement_bad_tx_power() {
let addr = BleAddress::new(0x112233, BleAddressKind::Public);
let ble_adv = BleAdvertisement::new(addr, Some(-60), None);
let raw_data = vec![3, 2, 1];
let service_data = ServiceData::new(0x123 as u16, raw_data);
let device_info = Ok(DeviceInfo::new(
String::from("image_url"),
String::from("name"),
));
let fetcher: Box<dyn FpFetcher> = Box::new(FpFetcherMock::new(device_info));
let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher);
assert!(fp_adv.is_err());
}
#[test]
fn test_new_fp_pairing_advertisement_bad_service_data() {
let addr = BleAddress::new(0x112233, BleAddressKind::Public);
let ble_adv = BleAdvertisement::new(addr, Some(-60), Some(10));
let raw_data = vec![4, 3, 2, 1];
let service_data = ServiceData::new(0x123 as u16, raw_data);
let device_info = Ok(DeviceInfo::new(
String::from("image_url"),
String::from("name"),
));
let fetcher: Box<dyn FpFetcher> = Box::new(FpFetcherMock::new(device_info));
let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher);
assert!(fp_adv.is_err());
}
#[test]
fn test_new_fp_pairing_advertisement_bad_fetcher() {
let addr = BleAddress::new(0x112233, BleAddressKind::Public);
let ble_adv = BleAdvertisement::new(addr, Some(-60), Some(10));
let raw_data = vec![3, 2, 1];
let service_data = ServiceData::new(0x123 as u16, raw_data);
let fetcher: Box<dyn FpFetcher> = Box::new(FpFetcherMock::new(Err(anyhow::anyhow!(
"mock intentional error"
))));
let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher);
assert!(fp_adv.is_err());
}
}
+9 -3
View File
@@ -9,7 +9,10 @@ use futures::executor;
use tracing::{info, warn};
use ttl_cache::TtlCache;
use crate::advertisement::{FpPairingAdvertisement, ModelId};
use crate::{
advertisement::{FpPairingAdvertisement, ModelId},
fetcher::{FpFetcher, FpFetcherFs},
};
// Sends a device name to Flutter via `StreamSink` FFI layer.
static DEVICE_STREAM: RwLock<Option<StreamSink<Option<[String; 2]>>>> = RwLock::new(None);
@@ -29,7 +32,7 @@ async fn update_best_device(best_adv: FpPairingAdvertisement) {
match DEVICE_STREAM.read().unwrap().as_ref() {
Some(stream) => {
stream.add(Some([
best_adv.name().to_string(),
best_adv.device_name().to_string(),
best_adv.image_url().to_string(),
]));
}
@@ -47,6 +50,7 @@ async fn update_best_device(best_adv: FpPairingAdvertisement) {
fn new_best_fp_advertisement(
advertisement: BleAdvertisement,
service_data: &ServiceData<u16>,
fetcher: &Box<dyn FpFetcher>,
latest_advertisement_map: &mut HashMap<String, FpPairingAdvertisement>,
) -> Option<FpPairingAdvertisement> {
// Analyze service data sections.
@@ -57,7 +61,7 @@ fn new_best_fp_advertisement(
return None;
}
let fp_adv = match FpPairingAdvertisement::new(advertisement, service_data) {
let fp_adv = match FpPairingAdvertisement::new(advertisement, service_data, fetcher) {
Ok(fp_adv) => fp_adv,
Err(err) => {
// If error during construction (e.g. non-discoverable
@@ -130,6 +134,7 @@ pub fn init() {
let mut latest_advertisement_map = HashMap::new();
let datatype_selector = vec![BleDataTypeId::ServiceData16BitUuid];
let fetcher: Box<dyn FpFetcher> = Box::new(FpFetcherFs::new(String::from("./local")));
loop {
// Retrieve the next received advertisement.
@@ -142,6 +147,7 @@ pub fn init() {
if let Some(best_adv) = new_best_fp_advertisement(
advertisement.clone(),
service_data,
&fetcher,
&mut latest_advertisement_map,
) {
update_best_device(best_adv).await;