notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 70f393af645893dab14475dd623939d063f7f572
parent 353cc2680135669c7bbcd65cc4751fb7f220c529
Author: kernelkind <kernelkind@gmail.com>
Date:   Thu, 12 Feb 2026 22:20:27 -0500

test(outbox): add multi-relay eose cleanup regression

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/enostr/tests/outbox_integration.rs | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 93 insertions(+), 0 deletions(-)

diff --git a/crates/enostr/tests/outbox_integration.rs b/crates/enostr/tests/outbox_integration.rs @@ -553,6 +553,52 @@ async fn oneshot_subscription_removed_after_eose() { ); } +/// Oneshot subscriptions across multiple relays should fully clean up after all EOSEs. +#[tokio::test] +async fn oneshot_multi_relay_fully_removed_after_eose() { + let (_relay1, url1) = create_test_relay().await; + let (_relay2, url2) = create_test_relay().await; + + let mut pool = OutboxPool::default(); + + let mut urls = HashSet::new(); + urls.insert(url1.clone()); + urls.insert(url2.clone()); + let url_pkgs = RelayUrlPkgs::new(urls); + + let id = { + let mut handler = pool.start_session(MockWakeup::default()); + handler.oneshot(trivial_filter(), url_pkgs); + let session = handler.export(); + let id = *session + .tasks + .keys() + .next() + .expect("oneshot should create a task"); + OutboxSessionHandler::import(&mut pool, session, MockWakeup::default()); + id + }; + + let got_all_eose = pump_pool_until(&mut pool, 100, Duration::from_millis(10), |pool| { + pool.all_have_eose(&id) + }) + .await; + assert!(got_all_eose, "oneshot should receive EOSE from all relays"); + + { + let _ = pool.start_session(MockWakeup::default()); + } + + assert!( + pool.filters(&id).is_none(), + "oneshot metadata should be removed after EOSE processing" + ); + assert!( + pool.status(&id).is_empty(), + "oneshot should be fully unsubscribed on all relays after EOSE processing" + ); +} + // ==================== Since Optimization After EOSE ==================== fn filter_has_since(filter: &Filter) -> bool { @@ -605,3 +651,50 @@ async fn eose_applies_since_to_filters() { "filters should have since after EOSE" ); } + +/// Since optimization should wait until every relay for the subscription reaches EOSE. +#[tokio::test] +async fn since_optimization_waits_for_all_relays_eose() { + let (_relay, live_url) = create_test_relay().await; + let dead_url = NormRelayUrl::new("wss://127.0.0.1:1").expect("valid dead relay url"); + + let mut pool = OutboxPool::default(); + + let mut urls = HashSet::new(); + urls.insert(live_url); + urls.insert(dead_url); + let mut url_pkgs = RelayUrlPkgs::new(urls); + url_pkgs.use_transparent = true; + + let id = { + let mut session = pool.start_session(MockWakeup::default()); + session.subscribe( + vec![Filter::new().kinds(vec![1]).limit(10).build()], + url_pkgs, + ) + }; + + let initial_filters = pool.filters(&id).expect("subscription exists"); + assert!( + !filter_has_since(&initial_filters[0]), + "filters should not have since before any EOSE" + ); + + let got_any_eose = default_pool_pump(&mut pool, |pool| pool.has_eose(&id)).await; + assert!(got_any_eose, "live relay should produce EOSE"); + assert!( + !pool.all_have_eose(&id), + "all relays should not have EOSE when one relay is unreachable" + ); + + // Trigger EOSE queue processing. + { + let _ = pool.start_session(MockWakeup::default()); + } + + let filters = pool.filters(&id).expect("subscription still exists"); + assert!( + !filter_has_since(&filters[0]), + "since should not be optimized until every relay reaches EOSE" + ); +}