1
1
"""Unit tests for xrpc_sync.py."""
2
+ from datetime import timedelta
2
3
from io import BytesIO
3
4
from threading import Semaphore , Thread
5
+ import time
4
6
from unittest import skip
5
7
from unittest .mock import patch
6
8
18
20
from .. import server
19
21
from ..storage import Action , Storage , SUBSCRIBE_REPOS_NSID
20
22
from .. import util
21
- from ..util import dag_cbor_cid , int_to_tid , next_tid
23
+ from ..util import dag_cbor_cid , int_to_tid , next_tid , tid_to_int
22
24
from .. import xrpc_sync
23
25
24
26
from . import testutil
@@ -522,7 +524,7 @@ def test_get_blocks_repo_tombstoned(self):
522
524
# with self.assertRaises(ValueError):
523
525
# sync.loadCheckout(syncStorage, checkoutCar, repoDid, keypair.did())
524
526
525
- # based atproto/packages/pds/tests/sync/list.test.ts
527
+ # based on atproto/packages/pds/tests/sync/list.test.ts
526
528
# def test_paginates_listed_hosted_repos(self):
527
529
# full = xrpc_sync.list_repos({})
528
530
# pt1 = xrpc_sync.list_repos({}, limit=2)
@@ -562,7 +564,8 @@ def subscribe(self, received, delivered=None, limit=None, cursor=None):
562
564
return
563
565
564
566
def assertCommitMessage (self , commit_msg , record = None , write = None ,
565
- repo = None , cur = None , prev = None , seq = None ):
567
+ repo = None , cur = None , prev = None , seq = None ,
568
+ check_commit = True ):
566
569
if not repo :
567
570
repo = self .repo
568
571
if not cur :
@@ -597,7 +600,7 @@ def assertCommitMessage(self, commit_msg, record=None, write=None,
597
600
record_cid = dag_cbor_cid (record )
598
601
mst_entry = {
599
602
'e' : [{
600
- 'k' : f'co.ll/{ int_to_tid ( util . _tid_ts_last ) } ' .encode (),
603
+ 'k' : f'co.ll/{ write . rkey } ' .encode (),
601
604
'v' : record_cid ,
602
605
'p' : 0 ,
603
606
't' : None ,
@@ -613,20 +616,21 @@ def assertCommitMessage(self, commit_msg, record=None, write=None,
613
616
'l' : None ,
614
617
}
615
618
616
- commit_record = {
617
- 'version' : 3 ,
618
- 'did' : repo .did ,
619
- 'data' : dag_cbor_cid (mst_entry ),
620
- 'rev' : int_to_tid (seq , clock_id = 0 ),
621
- 'prev' : prev ,
622
- }
623
-
624
619
msg_records = [b .decoded for b in msg_blocks ]
625
620
# TODO: if I util.sign(commit_record), the sig doesn't match. why?
626
621
for msg_record in msg_records :
627
622
msg_record .pop ('sig' , None )
628
623
629
- self .assertIn (commit_record , msg_records )
624
+ if check_commit :
625
+ commit_record = {
626
+ 'version' : 3 ,
627
+ 'did' : repo .did ,
628
+ 'data' : dag_cbor_cid (mst_entry ),
629
+ 'rev' : int_to_tid (seq , clock_id = 0 ),
630
+ 'prev' : prev ,
631
+ }
632
+ self .assertIn (commit_record , msg_records )
633
+
630
634
if record :
631
635
self .assertIn (record , msg_records )
632
636
@@ -766,7 +770,7 @@ def test_include_preexisting_record_block(self, *_):
766
770
767
771
subscriber .join ()
768
772
769
- def test_tombstone (self , * _ ):
773
+ def test_tombstoned (self , * _ ):
770
774
# second repo: bob
771
775
bob_repo = Repo .create (server .storage , 'did:bob' ,
772
776
handle = 'bo.bb' , signing_key = self .key )
@@ -826,6 +830,56 @@ def test_tombstone(self, *_):
826
830
'time' : testutil .NOW .isoformat (),
827
831
}, payload )
828
832
833
+ @patch ('arroba.xrpc_sync.NEW_EVENTS_TIMEOUT' , timedelta (seconds = 2 ))
834
+ def test_subscribe_repos_skipped_seq (self , * _ ):
835
+ # https://github.com/snarfed/arroba/issues/34
836
+ received = []
837
+ delivered = Semaphore (value = 0 )
838
+ subscriber = Thread (target = self .subscribe ,
839
+ args = [received , delivered , 2 ])
840
+ subscriber .start ()
841
+
842
+ # prepare two writes with seqs 4 and 5
843
+ write_4 = Write (Action .CREATE , 'co.ll' , next_tid (), {'a' : 'b' })
844
+ commit_4 = Repo .format_commit (repo = self .repo , writes = [write_4 ])
845
+ self .assertEqual (4 , tid_to_int (commit_4 .commit .decoded ['rev' ]))
846
+
847
+ write_5 = Write (Action .CREATE , 'co.ll' , next_tid (), {'x' : 'y' })
848
+ commit_5 = Repo .format_commit (repo = self .repo , writes = [write_5 ])
849
+ self .assertEqual (5 , tid_to_int (commit_5 .commit .decoded ['rev' ]))
850
+
851
+ prev = self .repo .head .cid
852
+
853
+ with self .assertLogs () as logs :
854
+ # first write, skip seq 4, write with seq 5 instead
855
+ self .repo .apply_commit (commit_5 )
856
+ head_5 = self .repo .head .cid
857
+
858
+ # there's a small chance that this could be flaky, if >.2s elapses
859
+ # between starting the subscriber above and receiving the second
860
+ # write below
861
+ time .sleep (.1 )
862
+
863
+ # shouldn't receive the event yet
864
+ self .assertEqual (0 , len (received ))
865
+
866
+ # second write, use seq 4 that we skipped above
867
+ self .repo .apply_commit (commit_4 )
868
+
869
+ delivered .acquire ()
870
+ delivered .acquire ()
871
+
872
+ self .assertIn ('WARNING:arroba.xrpc_sync:Waiting for seq 4' , logs .output )
873
+
874
+ # should receive both commits
875
+ self .assertEqual (2 , len (received ))
876
+ self .assertCommitMessage (received [0 ], {'a' : 'b' }, write = write_4 ,
877
+ cur = self .repo .head .cid , prev = prev , seq = 4 )
878
+ self .assertCommitMessage (received [1 ], {'x' : 'y' }, write = write_5 ,
879
+ cur = head_5 , prev = prev , seq = 5 , check_commit = False )
880
+
881
+ subscriber .join ()
882
+
829
883
830
884
class DatastoreXrpcSyncTest (XrpcSyncTest , testutil .DatastoreTest ):
831
885
STORAGE_CLS = DatastoreStorage
0 commit comments