#[path = "common/config.rs"]
mod common_address;
use std::time::{Duration, Instant};
use common_address::test_address;
use tekhsi_rs::data::Acquisition;
use tekhsi_rs::errors::{SubscriptionError, SubscriptionUpdateError, TekHsiError};
use tekhsi_rs::{SubscribeOptions, TekHsiClient};
use tokio::time::timeout;
fn acquisition_matches_symbols(acquisition: &Acquisition, symbols: &[String]) -> bool {
if acquisition.data.len() != symbols.len() {
return false;
}
symbols
.iter()
.all(|symbol| acquisition.get_by_symbol(symbol.as_str()).is_some())
}
#[tokio::test]
async fn subscribe_rejects_empty_symbols() -> Result<(), Box<dyn std::error::Error>> {
let client = TekHsiClient::connect(&test_address()).await?;
let err = client
.subscribe(Vec::new(), SubscribeOptions::default())
.expect_err("expected empty symbols to fail");
assert!(matches!(
err,
TekHsiError::Subscription(SubscriptionError::EmptySymbols)
));
client.disconnect().await?;
Ok(())
}
#[tokio::test]
async fn subscribe_rejects_double_subscription() -> Result<(), Box<dyn std::error::Error>> {
let client = TekHsiClient::connect(&test_address()).await?;
let symbols = client.list_available_symbols().await?;
assert!(!symbols.is_empty(), "test server should return symbols");
let receiver = client.subscribe(symbols.clone(), SubscribeOptions::default())?;
let err = client
.subscribe(symbols, SubscribeOptions::default())
.expect_err("expected double subscribe to fail");
assert!(matches!(
err,
TekHsiError::Subscription(SubscriptionError::AlreadyActive)
));
drop(receiver);
client.disconnect().await?;
Ok(())
}
#[tokio::test]
async fn update_symbols_rejects_empty_symbols() -> Result<(), Box<dyn std::error::Error>> {
let client = TekHsiClient::connect(&test_address()).await?;
let err = client
.update_symbols(Vec::new())
.expect_err("expected empty symbols to fail");
assert!(matches!(
err,
TekHsiError::SubscriptionUpdate(SubscriptionUpdateError::EmptySymbols)
));
client.disconnect().await?;
Ok(())
}
#[tokio::test]
async fn update_symbols_requires_active_subscription() -> Result<(), Box<dyn std::error::Error>> {
let client = TekHsiClient::connect(&test_address()).await?;
let err = client
.update_symbols(vec!["ch1".to_string()])
.expect_err("expected update to fail without subscription");
assert!(matches!(
err,
TekHsiError::SubscriptionUpdate(SubscriptionUpdateError::NotActive)
));
client.disconnect().await?;
Ok(())
}
#[tokio::test]
async fn update_symbols_applies_to_active_subscription() -> Result<(), Box<dyn std::error::Error>> {
let client = TekHsiClient::connect(&test_address()).await?;
let symbols = client.list_available_symbols().await?;
assert!(!symbols.is_empty(), "test server should return symbols");
let mut receiver = client.subscribe(symbols.clone(), SubscribeOptions::default())?;
let _ = timeout(Duration::from_secs(1), receiver.recv())
.await?
.map_err(|err| format!("acquisition receive failed: {err}"))?;
let target_symbols = vec![symbols[0].clone()];
client.update_symbols(target_symbols.clone())?;
let deadline = Instant::now() + Duration::from_millis(500);
loop {
let now = Instant::now();
if now >= deadline {
return Err("timed out waiting for updated symbols".into());
}
let remaining = deadline - now;
let acquisition = timeout(remaining, receiver.recv())
.await?
.map_err(|err| format!("acquisition receive failed: {err}"))?;
if acquisition_matches_symbols(&acquisition, &target_symbols) {
break;
}
}
drop(receiver);
client.disconnect().await?;
Ok(())
}
#[tokio::test]
async fn acquisition_propagates_timing() -> Result<(), Box<dyn std::error::Error>> {
let client = TekHsiClient::connect(&test_address()).await?;
let symbols = client.list_available_symbols().await?;
assert!(!symbols.is_empty(), "test server should return symbols");
let mut receiver = client.subscribe(symbols, SubscribeOptions::default())?;
for _ in 0..5 {
let acquisition = timeout(Duration::from_secs(5), receiver.recv())
.await?
.map_err(|err| format!("acquisition receive failed: {err}"))?;
if acquisition.wait_time == Duration::ZERO {
return Err("expected non-zero wait_time".into());
}
if acquisition.download_time == Duration::ZERO {
return Err("expected non-zero download_time".into());
}
let total = acquisition.wait_time + acquisition.download_time;
if total > Duration::from_secs(5) {
return Err(format!("timing exceeded 5 seconds: {:?}", total).into());
}
}
client.disconnect().await?;
Ok(())
}