@@ -21,7 +21,7 @@ import {
2121 QSSOperationResult ,
2222} from './qss.types'
2323import { createLogger } from '../common/logger'
24- import { Community , Identity } from '@quiet/types'
24+ import { Community , Identity , SocketEvents } from '@quiet/types'
2525import { getReduxStoreFactory , prepareStore , Store } from '@quiet/state-manager'
2626import { FactoryGirl } from 'factory-girl'
2727import { DateTime } from 'luxon'
@@ -640,6 +640,7 @@ describe('QSSService', () => {
640640 expect ( initStatusOrig . qssSetup ) . toBeTruthy ( )
641641 const syncSeq = 41
642642 await localDbService . setLastSyncSeq ( sigchainService . team . id , 40 )
643+ const emitSpy = jest . spyOn ( qssService [ 'socketService' ] . serverIoProvider . io , 'emit' )
643644
644645 mockedJoinStatus = jest . spyOn ( qssService , 'joinStatus' ) . mockReturnValue ( JoinStatus . JOINED )
645646 mockedSendMessage = jest
@@ -708,6 +709,10 @@ describe('QSSService', () => {
708709 expect ( result ) . toBe ( true )
709710 expect ( mockedSendMessage ) . toHaveBeenCalledTimes ( 1 )
710711 expect ( await localDbService . getLastSyncSeq ( sigchainService . team . id ) ) . toBe ( syncSeq )
712+ expect ( emitSpy ) . toHaveBeenCalledWith ( SocketEvents . NSE_SYNC_SEQ_UPDATED , {
713+ teamId : sigchainService . team . id ,
714+ lastSyncSeq : syncSeq ,
715+ } )
711716
712717 const pendingMessages = await localDbService . getPendingQssLogSyncMessages ( )
713718 expect ( pendingMessages ) . toEqual ( { } )
@@ -754,6 +759,131 @@ describe('QSSService', () => {
754759 } )
755760 } )
756761
762+ it ( `reconciles by pull when a fanout arrives before a sync-seq baseline is established` , async ( ) => {
763+ await initCommunity ( { qssEnabled : true , qssSetup : true } )
764+ const teamId = sigchainService . activeChain . team ! . id
765+ const pullSpy = jest . spyOn ( qssService as any , '_pullLatestLogEntriesForTeam' ) . mockResolvedValue ( undefined )
766+
767+ jest . spyOn ( orbitDbService , 'handleFanoutMessage' ) . mockResolvedValue ( true )
768+
769+ qssClient . emit ( WebsocketEvents . LOG_ENTRY_SYNC , {
770+ ts : DateTime . utc ( ) . toMillis ( ) ,
771+ status : CommunityOperationStatus . SUCCESS ,
772+ payload : {
773+ teamId,
774+ hash : 'fanout-baseline-hash' ,
775+ hashedDbId : 'fanout-baseline-db-id' ,
776+ encEntry : {
777+ encrypted : {
778+ contents : new Uint8Array ( ) ,
779+ scope : {
780+ type : EncryptionScopeType . ROLE ,
781+ name : RoleName . MEMBER ,
782+ generation : 1 ,
783+ } ,
784+ } ,
785+ signature : {
786+ signature : 'fanout-baseline-sig' as Base58 ,
787+ author : { type : 'USER' , name : 'fanout-user' } as any ,
788+ } ,
789+ ts : DateTime . utc ( ) . toMillis ( ) ,
790+ userId : sigchainService . user . userId ,
791+ teamId,
792+ } ,
793+ syncSeq : 1 ,
794+ } ,
795+ } satisfies LogEntrySyncMessage )
796+
797+ await waitForExpect ( ( ) => {
798+ expect ( pullSpy ) . toHaveBeenCalledWith ( teamId )
799+ } )
800+ expect ( await localDbService . getLastSyncSeq ( teamId ) ) . toBeNull ( )
801+ } )
802+
803+ it ( `reconciles by pull when a sync-seq gap is detected from fanout` , async ( ) => {
804+ await initCommunity ( { qssEnabled : true , qssSetup : true } )
805+ const teamId = sigchainService . activeChain . team ! . id
806+ await localDbService . setLastSyncSeq ( teamId , 5 )
807+ const pullSpy = jest . spyOn ( qssService as any , '_pullLatestLogEntriesForTeam' ) . mockResolvedValue ( undefined )
808+
809+ jest . spyOn ( orbitDbService , 'handleFanoutMessage' ) . mockResolvedValue ( true )
810+
811+ qssClient . emit ( WebsocketEvents . LOG_ENTRY_SYNC , {
812+ ts : DateTime . utc ( ) . toMillis ( ) ,
813+ status : CommunityOperationStatus . SUCCESS ,
814+ payload : {
815+ teamId,
816+ hash : 'fanout-gap-hash' ,
817+ hashedDbId : 'fanout-gap-db-id' ,
818+ encEntry : {
819+ encrypted : {
820+ contents : new Uint8Array ( ) ,
821+ scope : {
822+ type : EncryptionScopeType . ROLE ,
823+ name : RoleName . MEMBER ,
824+ generation : 1 ,
825+ } ,
826+ } ,
827+ signature : {
828+ signature : 'fanout-gap-sig' as Base58 ,
829+ author : { type : 'USER' , name : 'fanout-user' } as any ,
830+ } ,
831+ ts : DateTime . utc ( ) . toMillis ( ) ,
832+ userId : sigchainService . user . userId ,
833+ teamId,
834+ } ,
835+ syncSeq : 7 ,
836+ } ,
837+ } satisfies LogEntrySyncMessage )
838+
839+ await waitForExpect ( ( ) => {
840+ expect ( pullSpy ) . toHaveBeenCalledWith ( teamId )
841+ } )
842+ expect ( await localDbService . getLastSyncSeq ( teamId ) ) . toBe ( 5 )
843+ } )
844+
845+ it ( `reconciles by pull when fanout ingest fails even with a contiguous sync seq` , async ( ) => {
846+ await initCommunity ( { qssEnabled : true , qssSetup : true } )
847+ const teamId = sigchainService . activeChain . team ! . id
848+ await localDbService . setLastSyncSeq ( teamId , 5 )
849+ const pullSpy = jest . spyOn ( qssService as any , '_pullLatestLogEntriesForTeam' ) . mockResolvedValue ( undefined )
850+
851+ jest . spyOn ( orbitDbService , 'handleFanoutMessage' ) . mockResolvedValue ( false )
852+
853+ qssClient . emit ( WebsocketEvents . LOG_ENTRY_SYNC , {
854+ ts : DateTime . utc ( ) . toMillis ( ) ,
855+ status : CommunityOperationStatus . SUCCESS ,
856+ payload : {
857+ teamId,
858+ hash : 'fanout-failure-hash' ,
859+ hashedDbId : 'fanout-failure-db-id' ,
860+ encEntry : {
861+ encrypted : {
862+ contents : new Uint8Array ( ) ,
863+ scope : {
864+ type : EncryptionScopeType . ROLE ,
865+ name : RoleName . MEMBER ,
866+ generation : 1 ,
867+ } ,
868+ } ,
869+ signature : {
870+ signature : 'fanout-failure-sig' as Base58 ,
871+ author : { type : 'USER' , name : 'fanout-user' } as any ,
872+ } ,
873+ ts : DateTime . utc ( ) . toMillis ( ) ,
874+ userId : sigchainService . user . userId ,
875+ teamId,
876+ } ,
877+ syncSeq : 6 ,
878+ } ,
879+ } satisfies LogEntrySyncMessage )
880+
881+ await waitForExpect ( ( ) => {
882+ expect ( pullSpy ) . toHaveBeenCalledWith ( teamId )
883+ } )
884+ expect ( await localDbService . getLastSyncSeq ( teamId ) ) . toBe ( 5 )
885+ } )
886+
757887 it ( `fails to send log sync to QSS and writes pending message to local DB` , async ( ) => {
758888 await initCommunity ( { qssEnabled : true , qssSetup : true } )
759889 const initStatusOrig = await qssService . getQssInitStatus ( )
0 commit comments