@@ -111,6 +111,8 @@ pub trait StateStoreIntegrationTests {
111111 async fn test_thread_subscriptions ( & self ) -> TestResult ;
112112 /// Test thread subscription bumpstamp semantics.
113113 async fn test_thread_subscriptions_bumpstamps ( & self ) -> TestResult ;
114+ /// Test thread subscriptions bulk upsert, including bumpstamp semantics.
115+ async fn test_thread_subscriptions_bulk_upsert ( & self ) -> TestResult ;
114116}
115117
116118impl StateStoreIntegrationTests for DynStateStore {
@@ -2004,6 +2006,183 @@ impl StateStoreIntegrationTests for DynStateStore {
20042006
20052007 Ok ( ( ) )
20062008 }
2009+
2010+ async fn test_thread_subscriptions_bulk_upsert ( & self ) -> TestResult {
2011+ let threads = [
2012+ event_id ! ( "$t1" ) ,
2013+ event_id ! ( "$t2" ) ,
2014+ event_id ! ( "$t3" ) ,
2015+ event_id ! ( "$t4" ) ,
2016+ event_id ! ( "$t5" ) ,
2017+ event_id ! ( "$t6" ) ,
2018+ ] ;
2019+ // Helper for building the input for `upsert_thread_subscriptions()`,
2020+ // which is of the type: Vec<(&RoomId, &EventId, StoredThreadSubscription)>
2021+ let build_subscription_updates = |subs : & [ StoredThreadSubscription ] | {
2022+ threads
2023+ . iter ( )
2024+ . zip ( subs)
2025+ . map ( |( & event_id, & sub) | ( room_id ( ) , event_id, sub) )
2026+ . collect :: < Vec < _ > > ( )
2027+ } ;
2028+
2029+ // Test bump_stamp logic
2030+ let initial_subscriptions = build_subscription_updates ( & [
2031+ StoredThreadSubscription {
2032+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2033+ bump_stamp : None ,
2034+ } ,
2035+ StoredThreadSubscription {
2036+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2037+ bump_stamp : Some ( 14 ) ,
2038+ } ,
2039+ StoredThreadSubscription {
2040+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2041+ bump_stamp : None ,
2042+ } ,
2043+ StoredThreadSubscription {
2044+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2045+ bump_stamp : Some ( 210 ) ,
2046+ } ,
2047+ StoredThreadSubscription {
2048+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2049+ bump_stamp : Some ( 5 ) ,
2050+ } ,
2051+ StoredThreadSubscription {
2052+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2053+ bump_stamp : Some ( 100 ) ,
2054+ } ,
2055+ ] ) ;
2056+
2057+ let update_subscriptions = build_subscription_updates ( & [
2058+ StoredThreadSubscription {
2059+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2060+ bump_stamp : None ,
2061+ } ,
2062+ StoredThreadSubscription {
2063+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2064+ bump_stamp : None ,
2065+ } ,
2066+ StoredThreadSubscription {
2067+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2068+ bump_stamp : Some ( 1101 ) ,
2069+ } ,
2070+ StoredThreadSubscription {
2071+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2072+ bump_stamp : Some ( 222 ) ,
2073+ } ,
2074+ StoredThreadSubscription {
2075+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2076+ bump_stamp : Some ( 1 ) ,
2077+ } ,
2078+ StoredThreadSubscription {
2079+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2080+ bump_stamp : Some ( 100 ) ,
2081+ } ,
2082+ ] ) ;
2083+
2084+ let expected_subscriptions = build_subscription_updates ( & [
2085+ // Status should be updated, because prev and new bump_stamp are both None
2086+ StoredThreadSubscription {
2087+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2088+ bump_stamp : None ,
2089+ } ,
2090+ // Status should be updated, but keep initial bump_stamp (new is None)
2091+ StoredThreadSubscription {
2092+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2093+ bump_stamp : Some ( 14 ) ,
2094+ } ,
2095+ // Status should be updated and also bump_stamp should be updated (initial was None)
2096+ StoredThreadSubscription {
2097+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2098+ bump_stamp : Some ( 1101 ) ,
2099+ } ,
2100+ // Status should be updated and also bump_stamp should be updated (initial was lower)
2101+ StoredThreadSubscription {
2102+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2103+ bump_stamp : Some ( 222 ) ,
2104+ } ,
2105+ // Status shouldn't change, as new bump_stamp is lower
2106+ StoredThreadSubscription {
2107+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2108+ bump_stamp : Some ( 5 ) ,
2109+ } ,
2110+ // Status shouldn't change, as bump_stamp is equal to the previous one
2111+ StoredThreadSubscription {
2112+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2113+ bump_stamp : Some ( 100 ) ,
2114+ } ,
2115+ ] ) ;
2116+
2117+ // Set the initial subscriptions
2118+ self . upsert_thread_subscriptions ( initial_subscriptions. clone ( ) ) . await ?;
2119+
2120+ // Assert the subscriptions have been added
2121+ for ( room_id, thread_id, expected_sub) in & initial_subscriptions {
2122+ let stored_subscription = self . load_thread_subscription ( room_id, thread_id) . await ?;
2123+ assert_eq ! ( stored_subscription, Some ( * expected_sub) ) ;
2124+ }
2125+
2126+ // Update subscriptions
2127+ self . upsert_thread_subscriptions ( update_subscriptions) . await ?;
2128+
2129+ // Assert the expected subscriptions and bump_stamps
2130+ for ( room_id, thread_id, expected_sub) in & expected_subscriptions {
2131+ let stored_subscription = self . load_thread_subscription ( room_id, thread_id) . await ?;
2132+ assert_eq ! ( stored_subscription, Some ( * expected_sub) ) ;
2133+ }
2134+
2135+ // Test just state changes, but first remove previous subscriptions
2136+ for ( room_id, thread_id, _) in & expected_subscriptions {
2137+ self . remove_thread_subscription ( room_id, thread_id) . await ?;
2138+ }
2139+
2140+ let initial_subscriptions = build_subscription_updates ( & [
2141+ StoredThreadSubscription {
2142+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2143+ bump_stamp : Some ( 1 ) ,
2144+ } ,
2145+ StoredThreadSubscription {
2146+ status : ThreadSubscriptionStatus :: Subscribed { automatic : false } ,
2147+ bump_stamp : Some ( 1 ) ,
2148+ } ,
2149+ StoredThreadSubscription {
2150+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2151+ bump_stamp : Some ( 1 ) ,
2152+ } ,
2153+ ] ) ;
2154+
2155+ self . upsert_thread_subscriptions ( initial_subscriptions. clone ( ) ) . await ?;
2156+
2157+ for ( room_id, thread_id, expected_sub) in & initial_subscriptions {
2158+ let stored_subscription = self . load_thread_subscription ( room_id, thread_id) . await ?;
2159+ assert_eq ! ( stored_subscription, Some ( * expected_sub) ) ;
2160+ }
2161+
2162+ let update_subscriptions = build_subscription_updates ( & [
2163+ StoredThreadSubscription {
2164+ status : ThreadSubscriptionStatus :: Subscribed { automatic : true } ,
2165+ bump_stamp : Some ( 2 ) ,
2166+ } ,
2167+ StoredThreadSubscription {
2168+ status : ThreadSubscriptionStatus :: Unsubscribed ,
2169+ bump_stamp : Some ( 2 ) ,
2170+ } ,
2171+ StoredThreadSubscription {
2172+ status : ThreadSubscriptionStatus :: Subscribed { automatic : false } ,
2173+ bump_stamp : Some ( 2 ) ,
2174+ } ,
2175+ ] ) ;
2176+
2177+ self . upsert_thread_subscriptions ( update_subscriptions. clone ( ) ) . await ?;
2178+
2179+ for ( room_id, thread_id, expected_sub) in & update_subscriptions {
2180+ let stored_subscription = self . load_thread_subscription ( room_id, thread_id) . await ?;
2181+ assert_eq ! ( stored_subscription, Some ( * expected_sub) ) ;
2182+ }
2183+
2184+ Ok ( ( ) )
2185+ }
20072186}
20082187
20092188/// Macro building to allow your StateStore implementation to run the entire
@@ -2196,6 +2375,12 @@ macro_rules! statestore_integration_tests {
21962375 let store = get_store( ) . await ?. into_state_store( ) ;
21972376 store. test_thread_subscriptions_bumpstamps( ) . await
21982377 }
2378+
2379+ #[ async_test]
2380+ async fn test_thread_subscriptions_bulk_upsert( ) -> TestResult {
2381+ let store = get_store( ) . await ?. into_state_store( ) ;
2382+ store. test_thread_subscriptions_bulk_upsert( ) . await
2383+ }
21992384 }
22002385 } ;
22012386}
0 commit comments