@@ -121,8 +121,14 @@ void _syncTests<T>({
121121 }
122122
123123 List <Object ?> pushCheckpoint (
124- {int lastOpId = 1 , List <Object > buckets = const []}) {
125- return syncLine (checkpoint (lastOpId: lastOpId, buckets: buckets));
124+ {int lastOpId = 1 ,
125+ List <Object > buckets = const [],
126+ String ? writeCheckpoint}) {
127+ return syncLine (checkpoint (
128+ lastOpId: lastOpId,
129+ buckets: buckets,
130+ writeCheckpoint: writeCheckpoint,
131+ ));
126132 }
127133
128134 List <Object ?> pushCheckpointComplete ({int ? priority, String lastOpId = '1' }) {
@@ -676,6 +682,123 @@ void _syncTests<T>({
676682 });
677683 });
678684
685+ group ('applies pending changes' , () {
686+ test ('write checkpoint before upload complete' , () {
687+ // local write while offline
688+ db.execute ("insert into items (id, col) values ('local', 'data');" );
689+ invokeControl ('start' , null );
690+
691+ // Start upload process. Assume data has been uploaded and a write
692+ // checkpoint has been requested, but not received yet.
693+ db.execute ('DELETE FROM ps_crud' );
694+ pushCheckpoint (buckets: priorityBuckets, writeCheckpoint: '1' );
695+ pushSyncData ('prio1' , '1' , 'row-0' , 'PUT' , {'col' : 'hi' });
696+ expect (pushCheckpointComplete (), [
697+ containsPair ('LogLine' , {
698+ 'severity' : 'INFO' ,
699+ 'line' : contains ('Will retry at completed upload' )
700+ })
701+ ]);
702+
703+ // Now complete the upload process.
704+ db.execute (r"UPDATE ps_buckets SET target_op = 1 WHERE name = '$local'" );
705+ invokeControl ('completed_upload' , null );
706+
707+ // This should apply the pending write checkpoint.
708+ expect (fetchRows (), [
709+ {'id' : 'row-0' , 'col' : 'hi' }
710+ ]);
711+ });
712+
713+ test ('write checkpoint with synced data' , () {
714+ // local write while offline
715+ db.execute ("insert into items (id, col) values ('local', 'data');" );
716+ invokeControl ('start' , null );
717+
718+ // Complete upload process
719+ db.execute ('DELETE FROM ps_crud' );
720+ db.execute (r"UPDATE ps_buckets SET target_op = 1 WHERE name = '$local'" );
721+ expect (invokeControl ('completed_upload' , null ), isEmpty);
722+
723+ // Sync afterwards containing data and write checkpoint.
724+ pushCheckpoint (buckets: priorityBuckets, writeCheckpoint: '1' );
725+ pushSyncData ('prio1' , '1' , 'row-0' , 'PUT' , {'col' : 'hi' });
726+ pushCheckpointComplete ();
727+ expect (fetchRows (), [
728+ {'id' : 'row-0' , 'col' : 'hi' }
729+ ]);
730+ });
731+
732+ test ('write checkpoint after synced data' , () {
733+ // local write while offline
734+ db.execute ("insert into items (id, col) values ('local', 'data');" );
735+ invokeControl ('start' , null );
736+
737+ // Upload changes, assume that triggered a checkpoint.
738+ db.execute ('DELETE FROM ps_crud' );
739+ pushCheckpoint (buckets: priorityBuckets);
740+ pushSyncData ('prio1' , '1' , 'row-0' , 'PUT' , {'col' : 'hi' });
741+ expect (pushCheckpointComplete (), [
742+ containsPair ('LogLine' , {
743+ 'severity' : 'INFO' ,
744+ 'line' : contains ('Will retry at completed upload' )
745+ })
746+ ]);
747+
748+ // Now the upload is complete and requests a write checkpoint
749+ db.execute (r"UPDATE ps_buckets SET target_op = 1 WHERE name = '$local'" );
750+ expect (invokeControl ('completed_upload' , null ), isEmpty);
751+
752+ // Which triggers a new iteration
753+ pushCheckpoint (buckets: priorityBuckets, writeCheckpoint: '1' );
754+ expect (
755+ pushCheckpointComplete (),
756+ contains (containsPair ('LogLine' , {
757+ 'severity' : 'DEBUG' ,
758+ 'line' : contains ('Validated and applied checkpoint' )
759+ })));
760+
761+ expect (fetchRows (), [
762+ {'id' : 'row-0' , 'col' : 'hi' }
763+ ]);
764+ });
765+
766+ test ('second local write' , () {
767+ // first local write while offline
768+ db.execute ("insert into items (id, col) values ('local', 'data');" );
769+ invokeControl ('start' , null );
770+
771+ // Upload changes, assume that triggered a checkpoint.
772+ db.execute ('DELETE FROM ps_crud' );
773+ pushCheckpoint (buckets: priorityBuckets, writeCheckpoint: '1' );
774+ pushSyncData ('prio1' , '1' , 'row-0' , 'PUT' , {'col' : 'hi' });
775+ expect (pushCheckpointComplete (), [
776+ containsPair ('LogLine' , {
777+ 'severity' : 'INFO' ,
778+ 'line' : contains ('Will retry at completed upload' )
779+ })
780+ ]);
781+
782+ // Second local write during sync
783+ db.execute ("insert into items (id, col) values ('local2', 'data2');" );
784+
785+ // Now the upload is complete and requests a write checkpoint
786+ db.execute (r"UPDATE ps_buckets SET target_op = 1 WHERE name = '$local'" );
787+ expect (invokeControl ('completed_upload' , null ), [
788+ containsPair ('LogLine' , {
789+ 'severity' : 'WARNING' ,
790+ 'line' :
791+ 'Could not apply pending checkpoint even after completed upload'
792+ })
793+ ]);
794+
795+ expect (fetchRows (), [
796+ {'id' : 'local' , 'col' : 'data' },
797+ {'id' : 'local2' , 'col' : 'data2' },
798+ ]);
799+ });
800+ });
801+
679802 group ('errors' , () {
680803 syncTest ('diff without prior checkpoint' , (_) {
681804 invokeControl ('start' , null );
0 commit comments