use std::sync::Arc;
use sz_orm_mqtt::topics::topic_matches;
use sz_orm_mqtt::{MqttConfig, MqttPlugin, QoS};
async fn make_plugin() -> MqttPlugin {
let mut plugin = MqttPlugin::new(MqttConfig::default());
plugin.connect().await.unwrap();
plugin
}
#[tokio::test]
async fn stress_mqtt_100k_messages() {
let plugin = make_plugin().await;
let total: u64 = 100_000;
for i in 0..total {
let payload = format!("msg-{}", i);
plugin
.publish("bulk/topic", payload.into_bytes(), QoS::AtLeastOnce)
.await
.unwrap();
}
assert_eq!(plugin.message_count().await, total as usize);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn stress_mqtt_concurrent_publish() {
let plugin = Arc::new(make_plugin().await);
let per_task: u64 = 10_000;
let task_count: u64 = 8;
let total = per_task * task_count;
let mut handles = Vec::new();
for task_id in 0..task_count {
let p = plugin.clone();
handles.push(tokio::spawn(async move {
let topic = format!("task/{}/data", task_id);
for i in 0..per_task {
let payload = format!("t{}-m{}", task_id, i);
p.publish(&topic, payload.into_bytes(), QoS::AtMostOnce)
.await
.unwrap();
}
}));
}
for h in handles {
h.await.unwrap();
}
assert_eq!(plugin.message_count().await, total as usize);
}
#[tokio::test]
async fn stress_mqtt_1000_subscribers() {
let plugin = make_plugin().await;
let n: usize = 1000;
for i in 0..n {
plugin
.subscribe(&format!("topic-{}", i), QoS::AtLeastOnce)
.await
.unwrap();
}
assert_eq!(plugin.subscription_count().await, n);
for i in 0..n / 2 {
plugin.unsubscribe(&format!("topic-{}", i)).await.unwrap();
}
assert_eq!(plugin.subscription_count().await, n / 2);
}
#[tokio::test]
async fn stress_mqtt_subscribe_same_topic_idempotent() {
let plugin = make_plugin().await;
for _ in 0..1000 {
plugin
.subscribe("same/topic", QoS::AtLeastOnce)
.await
.unwrap();
}
assert_eq!(
plugin.subscription_count().await,
1,
"duplicate subscribe must be idempotent"
);
}
#[tokio::test]
async fn stress_mqtt_retained_uniqueness() {
let plugin = make_plugin().await;
let n: u64 = 10_000;
for i in 0..n {
let topic = format!("config/{}", i % 100); plugin
.publish_retain(&topic, format!("v{}", i).into_bytes(), QoS::AtLeastOnce)
.await
.unwrap();
}
assert_eq!(plugin.retained_count().await, 100);
}
#[tokio::test]
async fn stress_mqtt_wildcard_matching() {
let plugin = make_plugin().await;
for i in 0..1000u64 {
let topic = format!("home/room{}/temp", i);
plugin
.publish(&topic, b"23.5".to_vec(), QoS::AtMostOnce)
.await
.unwrap();
}
let matched = plugin.messages_matching("home/+/temp").await;
assert_eq!(matched.len(), 1000);
let matched_all = plugin.messages_matching("home/#").await;
assert_eq!(matched_all.len(), 1000);
let matched_none = plugin.messages_matching("office/#").await;
assert_eq!(matched_none.len(), 0);
}
#[tokio::test]
async fn stress_mqtt_large_payload() {
let plugin = make_plugin().await;
let payload = vec![0xCDu8; 1_000_000];
for _ in 0..100 {
plugin
.publish("large/topic", payload.clone(), QoS::AtMostOnce)
.await
.unwrap();
}
assert_eq!(plugin.message_count().await, 100);
}
#[test]
fn stress_mqtt_topic_matches_function() {
let topics: Vec<String> = (0..10_000)
.map(|i| format!("home/room{}/temp", i))
.collect();
let mut match_count = 0usize;
for t in &topics {
if topic_matches(t, "home/+/temp") {
match_count += 1;
}
}
assert_eq!(match_count, 10_000);
for t in &topics {
assert!(!topic_matches(t, "office/#"));
}
}
#[tokio::test]
async fn stress_mqtt_not_connected_consistency() {
let plugin = MqttPlugin::new(MqttConfig::default());
for i in 0..100 {
let result = plugin
.publish(&format!("t/{}", i), vec![], QoS::AtMostOnce)
.await;
assert!(
result.is_err(),
"publish must fail when disconnected at iter {}",
i
);
}
for i in 0..100 {
let result = plugin.subscribe(&format!("t/{}", i), QoS::AtMostOnce).await;
assert!(
result.is_err(),
"subscribe must fail when disconnected at iter {}",
i
);
}
}
#[tokio::test]
async fn stress_mqtt_disconnect_reconnect_cycle() {
let mut plugin = MqttPlugin::new(MqttConfig::default());
for cycle in 0..10 {
plugin.connect().await.unwrap();
for i in 0..100 {
plugin
.publish(
&format!("cycle/{}/{}", cycle, i),
b"data".to_vec(),
QoS::AtMostOnce,
)
.await
.unwrap();
}
plugin.disconnect().await.unwrap();
let result = plugin.publish("fail/topic", vec![], QoS::AtMostOnce).await;
assert!(result.is_err());
}
plugin.connect().await.unwrap();
assert_eq!(plugin.message_count().await, 1000);
}