mod common;
use std::cell::Cell;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::rc::Rc;
use tiny_pubsub::PubSub;
#[test]
fn token_format_is_uid_zero_based() {
let bus: PubSub<String> = PubSub::new();
assert_eq!(bus.subscribe("a", |_, _| {}).as_str(), "uid_0");
assert_eq!(bus.subscribe("a", |_, _| {}).as_str(), "uid_1");
}
#[test]
fn token_counter_survives_clear_all() {
let bus: PubSub<String> = PubSub::new();
assert_eq!(bus.subscribe("a", |_, _| {}).as_str(), "uid_0");
assert_eq!(bus.subscribe("a", |_, _| {}).as_str(), "uid_1");
bus.clear_all_subscriptions();
assert_eq!(bus.subscribe("a", |_, _| {}).as_str(), "uid_2");
}
#[test]
fn emptied_topic_key_persists_and_is_observable() {
let bus: PubSub<String> = PubSub::new();
let tok = bus.subscribe("a", |_, _| {});
bus.unsubscribe(&tok);
assert!(!bus.publish_sync("a", String::new()));
assert_eq!(bus.get_subscriptions("a"), vec!["a".to_string()]);
assert!(bus.unsubscribe_topic("a"));
let bus2: PubSub<String> = PubSub::new();
let tok2 = bus2.subscribe("a", |_, _| {});
bus2.unsubscribe(&tok2);
assert_eq!(bus2.count_subscriptions("a"), 0);
}
#[test]
fn count_breaks_on_first_prefix_match_even_when_empty() {
let bus: PubSub<String> = PubSub::new();
let tok = bus.subscribe("t", |_, _| {});
bus.subscribe("t.a", |_, _| {});
bus.subscribe("t.a", |_, _| {});
bus.unsubscribe(&tok);
assert_eq!(bus.count_subscriptions("t"), 0);
}
#[test]
fn empty_prefix_matches_every_topic() {
let bus: PubSub<String> = PubSub::new();
bus.subscribe("a", |_, _| {});
bus.subscribe("b", |_, _| {});
bus.subscribe("b", |_, _| {});
let mut all = bus.get_subscriptions("");
all.sort();
assert_eq!(all, vec!["a".to_string(), "b".to_string()]);
assert_eq!(bus.count_subscriptions(""), 1);
}
#[test]
fn immediate_exceptions_read_per_publish() {
let bus: PubSub<String> = PubSub::new();
let ran = Rc::new(Cell::new(0u32));
bus.subscribe("t", |_, _| panic!("boom"));
let r = ran.clone();
bus.subscribe("t", move |_, _| r.set(r.get() + 1));
let r1 = catch_unwind(AssertUnwindSafe(|| {
let _ = bus.publish_sync("t", String::new());
}));
assert!(r1.is_err());
assert_eq!(ran.get(), 1);
bus.set_immediate_exceptions(true);
let r2 = catch_unwind(AssertUnwindSafe(|| {
let _ = bus.publish_sync("t", String::new());
}));
assert!(r2.is_err());
assert_eq!(ran.get(), 1);
bus.set_immediate_exceptions(false);
let r3 = catch_unwind(AssertUnwindSafe(|| {
let _ = bus.publish_sync("t", String::new());
}));
assert!(r3.is_err());
assert_eq!(ran.get(), 2);
}
#[test]
fn deferred_job_captures_immediate_flag_at_publish() {
let bus: PubSub<String> = PubSub::new();
let ran = Rc::new(Cell::new(0u32));
bus.subscribe("t", |_, _| panic!("boom"));
let r = ran.clone();
bus.subscribe("t", move |_, _| r.set(r.get() + 1));
let _ = bus.publish("t", String::new());
bus.set_immediate_exceptions(true);
let drained = catch_unwind(AssertUnwindSafe(|| bus.process_deferred()));
assert!(drained.is_err());
assert_eq!(ran.get(), 1);
}
#[test]
fn deferred_delivery_is_fifo_with_depth() {
let bus: PubSub<u32> = PubSub::new();
let seen = Rc::new(std::cell::RefCell::new(Vec::<u32>::new()));
let s = seen.clone();
bus.subscribe("t", move |_, n| s.borrow_mut().push(*n));
let _ = bus.publish("t", 1);
let _ = bus.publish("t", 2);
let _ = bus.publish("t", 3);
assert_eq!(bus.pending(), 3);
bus.process_deferred();
assert_eq!(*seen.borrow(), vec![1, 2, 3]);
assert_eq!(bus.pending(), 0);
}
#[test]
fn ancestor_match_returns_true() {
let bus: PubSub<String> = PubSub::new();
bus.subscribe("a", |_, _| {});
assert!(bus.publish_sync("a.b.c", String::new()));
assert!(bus.publish("a.b.c", String::new()));
}
#[test]
fn no_match_returns_false_among_unrelated_topics() {
let bus: PubSub<String> = PubSub::new();
bus.subscribe("x", |_, _| {});
bus.subscribe("y.z", |_, _| {});
assert!(!bus.publish_sync("q.r", String::new()));
assert!(!bus.publish("q.r", String::new()));
}
#[test]
fn deferred_panic_does_not_abort_other_jobs() {
let bus: PubSub<()> = PubSub::new();
let ran = Rc::new(Cell::new(false));
bus.subscribe("x", |_, _| panic!("boom"));
let r = ran.clone();
bus.subscribe("y", move |_, _| r.set(true));
let _ = bus.publish("x", ());
let _ = bus.publish("y", ());
let drained = catch_unwind(AssertUnwindSafe(|| bus.process_deferred()));
assert!(drained.is_err());
assert!(ran.get());
assert_eq!(bus.pending(), 0);
}
#[test]
fn deferred_republish_runs_on_next_tick() {
let bus: Rc<PubSub<u32>> = Rc::new(PubSub::new());
let depth = Rc::new(Cell::new(0u32));
let bus_inner = bus.clone();
let depth_inner = depth.clone();
bus.subscribe("t", move |_, n| {
depth_inner.set(*n);
if *n < 3 {
let _ = bus_inner.publish("t", n + 1);
}
});
let _ = bus.publish("t", 1);
bus.process_deferred();
assert_eq!(depth.get(), 1);
assert_eq!(bus.pending(), 1);
bus.process_deferred();
assert_eq!(depth.get(), 2);
assert_eq!(bus.pending(), 1);
bus.process_deferred();
assert_eq!(depth.get(), 3);
assert_eq!(bus.pending(), 0);
}