sharedb 7.0.0 → 7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agent.js +197 -99
- package/lib/backend.js +40 -9
- package/lib/client/presence/doc-presence.js +6 -0
- package/lib/client/presence/presence.js +4 -1
- package/lib/pubsub/index.js +21 -9
- package/lib/query-emitter.js +6 -6
- package/lib/submit-request.js +8 -1
- package/lib/util.js +10 -0
- package/package.json +1 -1
- package/test/agent.js +415 -0
- package/test/client/presence/doc-presence.js +861 -18
- package/test/client/presence/presence.js +582 -1
- package/test/client/query-subscribe.js +25 -0
- package/test/middleware.js +150 -0
- package/test/pubsub-memory.js +75 -0
- package/test/pubsub.js +32 -0
|
@@ -585,20 +585,595 @@ describe('DocPresence', function() {
|
|
|
585
585
|
connection1.send(message);
|
|
586
586
|
});
|
|
587
587
|
|
|
588
|
+
it('rejects a presence message with a non-integer version', function(done) {
|
|
589
|
+
var localPresence1 = presence1.create('presence-1');
|
|
590
|
+
localPresence1.on('error', function(error) {
|
|
591
|
+
expect(error.code).to.eql('ERR_MESSAGE_BADLY_FORMED');
|
|
592
|
+
done();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
var message = localPresence1._message();
|
|
596
|
+
message.v = 1.5;
|
|
597
|
+
message.t = presenceTestType.type.uri;
|
|
598
|
+
connection1.send(message);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('rejects a presence message without a version', function(done) {
|
|
602
|
+
var localPresence1 = presence1.create('presence-1');
|
|
603
|
+
localPresence1.on('error', function(error) {
|
|
604
|
+
expect(error.code).to.eql('ERR_MESSAGE_BADLY_FORMED');
|
|
605
|
+
done();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
var message = localPresence1._message();
|
|
609
|
+
message.t = presenceTestType.type.uri;
|
|
610
|
+
connection1.send(message);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
[
|
|
614
|
+
{description: 'without doc coordinates', c: undefined, d: undefined},
|
|
615
|
+
{description: 'for another doc', c: 'books', d: 'subtle-knife'}
|
|
616
|
+
].forEach(function(test) {
|
|
617
|
+
it('ignores remote presence on its channel ' + test.description, function(done) {
|
|
618
|
+
sinon.spy(doc2, 'fetch');
|
|
619
|
+
|
|
620
|
+
[doc2.version, doc2.version + 1].forEach(function(version, index) {
|
|
621
|
+
var message = presence1.create('presence-' + index)._message();
|
|
622
|
+
message.c = test.c;
|
|
623
|
+
message.d = test.d;
|
|
624
|
+
message.p = {index: 1};
|
|
625
|
+
message.v = version;
|
|
626
|
+
message.t = presenceTestType.type.uri;
|
|
627
|
+
connection2.handleMessage(message);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
doc2.whenNothingPending(function() {
|
|
631
|
+
expect(presence2.remotePresences).to.be.empty;
|
|
632
|
+
expect(presence2._remotePresenceInstances).to.be.empty;
|
|
633
|
+
expect(doc2.fetch).not.to.have.been.called;
|
|
634
|
+
done();
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('rejects presence whose version is ahead of the doc', function(done) {
|
|
640
|
+
var localPresence1 = presence1.create('presence-1');
|
|
641
|
+
presence2.on('receive', function() {
|
|
642
|
+
done(new Error('Should not broadcast presence from the future'));
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
async.series([
|
|
646
|
+
presence2.subscribe.bind(presence2),
|
|
647
|
+
function(next) {
|
|
648
|
+
localPresence1.on('error', function(error) {
|
|
649
|
+
expect(error.code).to.equal('ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT');
|
|
650
|
+
expect(connection1.agent.presenceRequests[presence1.channel]['presence-1'].p).to.be.null;
|
|
651
|
+
next();
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
var message = localPresence1._message();
|
|
655
|
+
message.p = {index: 1};
|
|
656
|
+
message.v = doc1.version + 1;
|
|
657
|
+
message.t = presenceTestType.type.uri;
|
|
658
|
+
connection1.send(message);
|
|
659
|
+
}
|
|
660
|
+
], done);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it('rejects presence ahead of a tracked doc', function(done) {
|
|
664
|
+
var localPresence1 = presence1.create('presence-1');
|
|
665
|
+
|
|
666
|
+
async.series([
|
|
667
|
+
localPresence1.submit.bind(localPresence1, {index: 1}),
|
|
668
|
+
function(next) {
|
|
669
|
+
localPresence1.on('error', function(error) {
|
|
670
|
+
expect(error.code).to.equal('ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT');
|
|
671
|
+
next();
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
var message = localPresence1._message();
|
|
675
|
+
message.p = {index: 1};
|
|
676
|
+
message.v = doc1.version + 1;
|
|
677
|
+
message.t = presenceTestType.type.uri;
|
|
678
|
+
connection1.send(message);
|
|
679
|
+
}
|
|
680
|
+
], done);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('rejects presence ahead of a tracked doc after ops to a doc on the same channel', function(done) {
|
|
684
|
+
var notesDoc1 = connection1.get('notes', 'private.alice');
|
|
685
|
+
var diaryDoc1 = connection1.get('notes.private', 'alice');
|
|
686
|
+
var diaryDoc2 = connection2.get('notes.private', 'alice');
|
|
687
|
+
var localNotesPresence1 = connection1.getDocPresence('notes', 'private.alice').create('notes-presence');
|
|
688
|
+
|
|
689
|
+
async.series([
|
|
690
|
+
notesDoc1.create.bind(notesDoc1, 'Hello', presenceTestType.type.name),
|
|
691
|
+
localNotesPresence1.submit.bind(localNotesPresence1, {index: 1}),
|
|
692
|
+
diaryDoc2.create.bind(diaryDoc2, 'Dear diary', presenceTestType.type.name),
|
|
693
|
+
diaryDoc1.subscribe.bind(diaryDoc1),
|
|
694
|
+
function(next) {
|
|
695
|
+
diaryDoc1.once('op', function() {
|
|
696
|
+
next();
|
|
697
|
+
});
|
|
698
|
+
diaryDoc2.submitOp({index: 0, value: 'Oh, '}, errorHandler(done));
|
|
699
|
+
},
|
|
700
|
+
function(next) {
|
|
701
|
+
localNotesPresence1.on('error', function(error) {
|
|
702
|
+
expect(error.code).to.equal('ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT');
|
|
703
|
+
next();
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
expect(diaryDoc2.version).to.be.above(notesDoc1.version);
|
|
707
|
+
var message = localNotesPresence1._message();
|
|
708
|
+
message.p = {index: 1};
|
|
709
|
+
message.v = diaryDoc2.version;
|
|
710
|
+
message.t = presenceTestType.type.uri;
|
|
711
|
+
connection1.send(message);
|
|
712
|
+
}
|
|
713
|
+
], done);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it('does not read the snapshot for presence after its own client\'s op', function(done) {
|
|
717
|
+
var localPresence1 = presence1.create('presence-1');
|
|
718
|
+
|
|
719
|
+
async.series([
|
|
720
|
+
presence2.subscribe.bind(presence2),
|
|
721
|
+
function(next) {
|
|
722
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
723
|
+
presence2.once('receive', function() {
|
|
724
|
+
next();
|
|
725
|
+
});
|
|
726
|
+
},
|
|
727
|
+
function(next) {
|
|
728
|
+
doc2.once('op', function() {
|
|
729
|
+
next();
|
|
730
|
+
});
|
|
731
|
+
doc1.submitOp({index: 0, value: 'The '}, errorHandler(done));
|
|
732
|
+
},
|
|
733
|
+
function(next) {
|
|
734
|
+
sinon.spy(backend.db, 'getSnapshot');
|
|
735
|
+
localPresence1.submit({index: 6}, errorHandler(done));
|
|
736
|
+
presence2.once('receive', function(id, presence) {
|
|
737
|
+
expect(presence).to.eql({index: 6});
|
|
738
|
+
expect(backend.db.getSnapshot).not.to.have.been.called;
|
|
739
|
+
next();
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
], done);
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it('does not read the snapshot for presence at the tracked doc version', function(done) {
|
|
746
|
+
var localPresence1 = presence1.create('presence-1');
|
|
747
|
+
|
|
748
|
+
async.series([
|
|
749
|
+
presence2.subscribe.bind(presence2),
|
|
750
|
+
function(next) {
|
|
751
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
752
|
+
presence2.once('receive', function() {
|
|
753
|
+
next();
|
|
754
|
+
});
|
|
755
|
+
},
|
|
756
|
+
function(next) {
|
|
757
|
+
sinon.spy(backend.db, 'getSnapshot');
|
|
758
|
+
localPresence1.submit({index: 2}, errorHandler(done));
|
|
759
|
+
presence2.once('receive', function(id, presence) {
|
|
760
|
+
expect(presence).to.eql({index: 2});
|
|
761
|
+
expect(backend.db.getSnapshot).not.to.have.been.called;
|
|
762
|
+
next();
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
], done);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it('clamps a leave ahead of the doc to the doc version', function(done) {
|
|
769
|
+
var localPresence1 = presence1.create('presence-1');
|
|
770
|
+
localPresence1.on('error', done);
|
|
771
|
+
sinon.spy(doc2, 'fetch');
|
|
772
|
+
|
|
773
|
+
async.series([
|
|
774
|
+
presence2.subscribe.bind(presence2),
|
|
775
|
+
function(next) {
|
|
776
|
+
presence2.once('receive', function(id, presence) {
|
|
777
|
+
expect(id).to.equal('presence-1');
|
|
778
|
+
expect(presence).to.be.null;
|
|
779
|
+
expect(connection1.agent.presenceRequests[presence1.channel]['presence-1'].v).to.equal(doc1.version);
|
|
780
|
+
expect(doc2.fetch).not.to.have.been.called;
|
|
781
|
+
next();
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
var leave = localPresence1._message();
|
|
785
|
+
leave.p = null;
|
|
786
|
+
leave.v = doc1.version + 1;
|
|
787
|
+
leave.t = presenceTestType.type.uri;
|
|
788
|
+
connection1.send(leave);
|
|
789
|
+
}
|
|
790
|
+
], done);
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it('broadcasts null presence on disconnect after rejecting presence ahead of the doc', function(done) {
|
|
794
|
+
var localPresence1 = presence1.create('presence-1');
|
|
795
|
+
|
|
796
|
+
async.series([
|
|
797
|
+
presence1.subscribe.bind(presence1),
|
|
798
|
+
presence2.subscribe.bind(presence2),
|
|
799
|
+
function(next) {
|
|
800
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
801
|
+
presence2.once('receive', function() {
|
|
802
|
+
next();
|
|
803
|
+
});
|
|
804
|
+
},
|
|
805
|
+
function(next) {
|
|
806
|
+
localPresence1.once('error', function(error) {
|
|
807
|
+
expect(error.code).to.equal('ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT');
|
|
808
|
+
next();
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
var message = localPresence1._message();
|
|
812
|
+
message.v = doc1.version + 1;
|
|
813
|
+
message.t = presenceTestType.type.uri;
|
|
814
|
+
connection1.send(message);
|
|
815
|
+
},
|
|
816
|
+
function(next) {
|
|
817
|
+
presence2.once('receive', function(id, presence) {
|
|
818
|
+
expect(id).to.equal('presence-1');
|
|
819
|
+
expect(presence).to.be.null;
|
|
820
|
+
next();
|
|
821
|
+
});
|
|
822
|
+
connection1.close();
|
|
823
|
+
}
|
|
824
|
+
], done);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it('accepts up-to-date presence when the agent\'s tracked doc version lags behind', function(done) {
|
|
828
|
+
var localPresence1 = presence1.create('presence-1');
|
|
829
|
+
|
|
830
|
+
async.series([
|
|
831
|
+
presence2.subscribe.bind(presence2),
|
|
832
|
+
function(next) {
|
|
833
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
834
|
+
presence2.once('receive', function() {
|
|
835
|
+
next();
|
|
836
|
+
});
|
|
837
|
+
},
|
|
838
|
+
function(next) {
|
|
839
|
+
doc1.submitOp({index: 5, value: 'ern'}, errorHandler(done));
|
|
840
|
+
doc2.once('op', function() {
|
|
841
|
+
next();
|
|
842
|
+
});
|
|
843
|
+
},
|
|
844
|
+
function(next) {
|
|
845
|
+
connection1.agent.latestDocVersions.books['northern-lights'] = 1;
|
|
846
|
+
localPresence1.submit({index: 12}, errorHandler(done));
|
|
847
|
+
presence2.once('receive', function(id, presence) {
|
|
848
|
+
expect(doc2.version).to.eql(2);
|
|
849
|
+
expect(presence).to.eql({index: 12});
|
|
850
|
+
next();
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
], done);
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
it('transforms presence ahead of the agent\'s lagging tracked doc version by later ops', function(done) {
|
|
857
|
+
var localPresence1 = presence1.create('presence-1');
|
|
858
|
+
|
|
859
|
+
async.series([
|
|
860
|
+
presence2.subscribe.bind(presence2),
|
|
861
|
+
function(next) {
|
|
862
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
863
|
+
presence2.once('receive', function() {
|
|
864
|
+
next();
|
|
865
|
+
});
|
|
866
|
+
},
|
|
867
|
+
function(next) {
|
|
868
|
+
doc1.submitOp({index: 5, value: 'ern'}, errorHandler(done));
|
|
869
|
+
doc2.once('op', function() {
|
|
870
|
+
next();
|
|
871
|
+
});
|
|
872
|
+
},
|
|
873
|
+
function(next) {
|
|
874
|
+
doc2.submitOp({index: 0, value: 'The '}, errorHandler(done));
|
|
875
|
+
doc1.once('op', function() {
|
|
876
|
+
next();
|
|
877
|
+
});
|
|
878
|
+
},
|
|
879
|
+
function(next) {
|
|
880
|
+
connection1.agent.latestDocVersions.books['northern-lights'] = 1;
|
|
881
|
+
presence2.once('receive', function(id, presence) {
|
|
882
|
+
expect(presence).to.eql({index: 16});
|
|
883
|
+
next();
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
var message = localPresence1._message();
|
|
887
|
+
message.p = {index: 12};
|
|
888
|
+
message.v = 2;
|
|
889
|
+
message.t = presenceTestType.type.uri;
|
|
890
|
+
connection1.send(message);
|
|
891
|
+
}
|
|
892
|
+
], done);
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
it('rejects presence ahead of the doc when transformed without a tracked version', function(done) {
|
|
896
|
+
var presence = {
|
|
897
|
+
c: 'books',
|
|
898
|
+
d: 'northern-lights',
|
|
899
|
+
p: {index: 1},
|
|
900
|
+
v: doc1.version + 1,
|
|
901
|
+
t: presenceTestType.type.uri
|
|
902
|
+
};
|
|
903
|
+
backend.transformPresenceToLatestVersion(connection1.agent, presence, function(error) {
|
|
904
|
+
expect(error.code).to.equal('ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT');
|
|
905
|
+
done();
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
|
|
588
909
|
it('rejects a presence message without an ID', function(done) {
|
|
589
910
|
var localPresence1 = presence1.create('presence-1');
|
|
590
|
-
// Have to catch the error on the Presence instance, because obviously
|
|
591
|
-
// we won't find the LocalPresence without the ID
|
|
592
|
-
presence1.on('error', function(error) {
|
|
593
|
-
expect(error.code).to.eql('ERR_MESSAGE_BADLY_FORMED');
|
|
594
|
-
done();
|
|
911
|
+
// Have to catch the error on the Presence instance, because obviously
|
|
912
|
+
// we won't find the LocalPresence without the ID
|
|
913
|
+
presence1.on('error', function(error) {
|
|
914
|
+
expect(error.code).to.eql('ERR_MESSAGE_BADLY_FORMED');
|
|
915
|
+
done();
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
var message = localPresence1._message();
|
|
919
|
+
message.id = null;
|
|
920
|
+
message.v = 1;
|
|
921
|
+
message.t = presenceTestType.type.uri;
|
|
922
|
+
connection1.send(message);
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it('rejects a presence message for a different doc than its channel', function(done) {
|
|
926
|
+
var localPresence1 = presence1.create('presence-1');
|
|
927
|
+
localPresence1.on('error', function(error) {
|
|
928
|
+
expect(error.code).to.eql('ERR_MESSAGE_BADLY_FORMED');
|
|
929
|
+
expect(error.message).to.equal('Presence collection and doc ID do not match channel');
|
|
930
|
+
done();
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
var message = localPresence1._message();
|
|
934
|
+
message.d = 'subtle-knife';
|
|
935
|
+
message.v = 1;
|
|
936
|
+
message.t = presenceTestType.type.uri;
|
|
937
|
+
connection1.send(message);
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
it('rejects a presence message with a doc ID but no collection', function(done) {
|
|
941
|
+
var localPresence1 = presence1.create('presence-1');
|
|
942
|
+
localPresence1.on('error', function(error) {
|
|
943
|
+
expect(error.code).to.eql('ERR_MESSAGE_BADLY_FORMED');
|
|
944
|
+
done();
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
var message = localPresence1._message();
|
|
948
|
+
message.c = null;
|
|
949
|
+
message.v = 1;
|
|
950
|
+
message.t = presenceTestType.type.uri;
|
|
951
|
+
connection1.send(message);
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
it('rejects a presence message with a collection but no doc ID', function(done) {
|
|
955
|
+
var localPresence1 = presence1.create('presence-1');
|
|
956
|
+
localPresence1.on('error', function(error) {
|
|
957
|
+
expect(error.code).to.eql('ERR_MESSAGE_BADLY_FORMED');
|
|
958
|
+
done();
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
var message = localPresence1._message();
|
|
962
|
+
message.d = null;
|
|
963
|
+
message.v = 1;
|
|
964
|
+
message.t = presenceTestType.type.uri;
|
|
965
|
+
connection1.send(message);
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
it('rejects a presence message with an empty collection', function(done) {
|
|
969
|
+
var localPresence1 = connection1.getDocPresence('', 'northern-lights').create('presence-1');
|
|
970
|
+
localPresence1.on('error', function(error) {
|
|
971
|
+
expect(error.code).to.eql('ERR_MESSAGE_BADLY_FORMED');
|
|
972
|
+
done();
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
var message = localPresence1._message();
|
|
976
|
+
message.v = 1;
|
|
977
|
+
message.t = presenceTestType.type.uri;
|
|
978
|
+
connection1.send(message);
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
it('rejects a presence message with an empty doc ID', function(done) {
|
|
982
|
+
var localPresence1 = connection1.getDocPresence('books', '').create('presence-1');
|
|
983
|
+
localPresence1.on('error', function(error) {
|
|
984
|
+
expect(error.code).to.eql('ERR_MESSAGE_BADLY_FORMED');
|
|
985
|
+
done();
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
var message = localPresence1._message();
|
|
989
|
+
message.v = 1;
|
|
990
|
+
message.t = presenceTestType.type.uri;
|
|
991
|
+
connection1.send(message);
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
it('rejects presence on a doc the client cannot read', function(done) {
|
|
995
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.readSnapshots, function(context, next) {
|
|
996
|
+
next(new Error('Forbidden'));
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1000
|
+
localPresence1.submit({index: 1}, function(error) {
|
|
1001
|
+
expect(error.message).to.equal('Forbidden');
|
|
1002
|
+
done();
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it('clears presence when a client that has lost read access disconnects', function(done) {
|
|
1007
|
+
var agent1 = connection1.agent;
|
|
1008
|
+
var canRead = true;
|
|
1009
|
+
[backend.MIDDLEWARE_ACTIONS.readSnapshots, backend.MIDDLEWARE_ACTIONS.op].forEach(function(action) {
|
|
1010
|
+
backend.use(action, function(context, next) {
|
|
1011
|
+
if (canRead || context.agent !== agent1) return next();
|
|
1012
|
+
next(new Error('Forbidden'));
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1016
|
+
|
|
1017
|
+
async.series([
|
|
1018
|
+
presence2.subscribe.bind(presence2),
|
|
1019
|
+
function(next) {
|
|
1020
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
1021
|
+
presence2.once('receive', function() {
|
|
1022
|
+
next();
|
|
1023
|
+
});
|
|
1024
|
+
},
|
|
1025
|
+
function(next) {
|
|
1026
|
+
doc2.submitOp({index: 5, value: 'ern'}, errorHandler(done));
|
|
1027
|
+
doc1.once('op', function() {
|
|
1028
|
+
next();
|
|
1029
|
+
});
|
|
1030
|
+
},
|
|
1031
|
+
function(next) {
|
|
1032
|
+
canRead = false;
|
|
1033
|
+
presence2.once('receive', function(id, presence) {
|
|
1034
|
+
expect(id).to.equal('presence-1');
|
|
1035
|
+
expect(presence).to.be.null;
|
|
1036
|
+
next();
|
|
1037
|
+
});
|
|
1038
|
+
connection1.close();
|
|
1039
|
+
}
|
|
1040
|
+
], done);
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
it('ignores untyped presence on its channel', function(done) {
|
|
1044
|
+
var connection3 = backend.connect();
|
|
1045
|
+
var untypedPresence3 = connection3.getPresence('books.northern-lights');
|
|
1046
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1047
|
+
|
|
1048
|
+
async.series([
|
|
1049
|
+
presence2.subscribe.bind(presence2),
|
|
1050
|
+
untypedPresence3.subscribe.bind(untypedPresence3),
|
|
1051
|
+
function(next) {
|
|
1052
|
+
connection2.on('receive', function(request) {
|
|
1053
|
+
if (request.data.src !== connection3.id) return;
|
|
1054
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
1055
|
+
});
|
|
1056
|
+
presence2.once('receive', function(id, presence) {
|
|
1057
|
+
expect(id).to.equal('presence-1');
|
|
1058
|
+
expect(presence).to.eql({index: 1});
|
|
1059
|
+
next();
|
|
1060
|
+
});
|
|
1061
|
+
connection3.send({
|
|
1062
|
+
a: 'p', ch: 'books.northern-lights', id: 'presence-1', p: {index: 9}, pv: 0, v: doc1.version
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
], done);
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
it('does not let untyped presence with the same ID ack its local presence', function(done) {
|
|
1069
|
+
var connection3 = backend.connect();
|
|
1070
|
+
var untypedPresence3 = connection3.getPresence('books.northern-lights');
|
|
1071
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1072
|
+
var hasReceivedOwnPresence = false;
|
|
1073
|
+
|
|
1074
|
+
async.series([
|
|
1075
|
+
presence1.subscribe.bind(presence1),
|
|
1076
|
+
untypedPresence3.subscribe.bind(untypedPresence3),
|
|
1077
|
+
function(next) {
|
|
1078
|
+
connection1.on('receive', function(request) {
|
|
1079
|
+
if (request.data.src === connection1.id) hasReceivedOwnPresence = true;
|
|
1080
|
+
if (request.data.src !== connection3.id) return;
|
|
1081
|
+
localPresence1.submit({index: 1}, function(error) {
|
|
1082
|
+
if (error) return done(error);
|
|
1083
|
+
expect(hasReceivedOwnPresence).to.be.true;
|
|
1084
|
+
next();
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
1087
|
+
connection3.send({
|
|
1088
|
+
a: 'p', ch: 'books.northern-lights', id: 'presence-1', p: {index: 9}, pv: 0, v: doc1.version
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
], done);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
it('does not let typed presence from another client with the same ID ack its local presence', function(done) {
|
|
1095
|
+
var connection3 = backend.connect();
|
|
1096
|
+
var presence3 = connection3.getDocPresence('books', 'northern-lights');
|
|
1097
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1098
|
+
var hasReceivedOwnPresence = false;
|
|
1099
|
+
|
|
1100
|
+
async.series([
|
|
1101
|
+
presence1.subscribe.bind(presence1),
|
|
1102
|
+
presence3.subscribe.bind(presence3),
|
|
1103
|
+
function(next) {
|
|
1104
|
+
connection1.on('receive', function(request) {
|
|
1105
|
+
if (request.data.src === connection1.id) hasReceivedOwnPresence = true;
|
|
1106
|
+
if (request.data.src !== connection3.id) return;
|
|
1107
|
+
localPresence1.submit({index: 1}, function(error) {
|
|
1108
|
+
if (error) return done(error);
|
|
1109
|
+
expect(hasReceivedOwnPresence).to.be.true;
|
|
1110
|
+
next();
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
connection3.send({
|
|
1114
|
+
a: 'p',
|
|
1115
|
+
ch: 'books.northern-lights',
|
|
1116
|
+
c: 'books',
|
|
1117
|
+
d: 'northern-lights',
|
|
1118
|
+
t: presenceTestType.type.uri,
|
|
1119
|
+
id: 'presence-1',
|
|
1120
|
+
p: {index: 9},
|
|
1121
|
+
pv: 0,
|
|
1122
|
+
v: doc1.version
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
], done);
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
it('emits a forwarded error for a presence with the same ID on the presence, not its local presence', function(done) {
|
|
1129
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.sendPresence, function(context, next) {
|
|
1130
|
+
next(new Error('uh-oh!'));
|
|
595
1131
|
});
|
|
596
1132
|
|
|
597
|
-
var
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1133
|
+
var connection3 = backend.connect();
|
|
1134
|
+
var doc3 = connection3.get('books', 'northern-lights');
|
|
1135
|
+
var localPresence3 = connection3.getDocPresence('books', 'northern-lights').create('presence-1');
|
|
1136
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1137
|
+
localPresence1.on('error', done);
|
|
1138
|
+
|
|
1139
|
+
async.series([
|
|
1140
|
+
doc3.subscribe.bind(doc3),
|
|
1141
|
+
presence1.subscribe.bind(presence1),
|
|
1142
|
+
function(next) {
|
|
1143
|
+
presence1.once('error', function(error) {
|
|
1144
|
+
expect(error.message).to.equal('uh-oh!');
|
|
1145
|
+
next();
|
|
1146
|
+
});
|
|
1147
|
+
localPresence3.submit({index: 1}, errorHandler(done));
|
|
1148
|
+
}
|
|
1149
|
+
], done);
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
it('ignores presence for a doc whose collection and id join to the same channel', function(done) {
|
|
1153
|
+
var connection3 = backend.connect();
|
|
1154
|
+
var notesDoc1 = connection1.get('notes', 'private.alice');
|
|
1155
|
+
var notesDoc3 = connection3.get('notes', 'private.alice');
|
|
1156
|
+
var diaryDoc2 = connection2.get('notes.private', 'alice');
|
|
1157
|
+
var localNotesPresence1 = connection1.getDocPresence('notes', 'private.alice').create('notes-presence');
|
|
1158
|
+
var localDiaryPresence2 = connection2.getDocPresence('notes.private', 'alice').create('diary-presence');
|
|
1159
|
+
var notesPresence3 = connection3.getDocPresence('notes', 'private.alice');
|
|
1160
|
+
|
|
1161
|
+
async.series([
|
|
1162
|
+
notesDoc1.create.bind(notesDoc1, 'Hello', presenceTestType.type.name),
|
|
1163
|
+
diaryDoc2.create.bind(diaryDoc2, 'Dear diary', presenceTestType.type.name),
|
|
1164
|
+
notesDoc3.subscribe.bind(notesDoc3),
|
|
1165
|
+
notesPresence3.subscribe.bind(notesPresence3),
|
|
1166
|
+
function(next) {
|
|
1167
|
+
notesPresence3.once('receive', function(id) {
|
|
1168
|
+
expect(id).to.equal('notes-presence');
|
|
1169
|
+
next();
|
|
1170
|
+
});
|
|
1171
|
+
localDiaryPresence2.submit({index: 1}, function(error) {
|
|
1172
|
+
if (error) return done(error);
|
|
1173
|
+
localNotesPresence1.submit({index: 1}, errorHandler(done));
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
], done);
|
|
602
1177
|
});
|
|
603
1178
|
|
|
604
1179
|
it('only sends presence responses for the associated doc', function(done) {
|
|
@@ -793,6 +1368,21 @@ describe('DocPresence', function() {
|
|
|
793
1368
|
], done);
|
|
794
1369
|
});
|
|
795
1370
|
|
|
1371
|
+
it('does not keep presence when fetching its doc version fails', function(done) {
|
|
1372
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1373
|
+
|
|
1374
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.readSnapshots, function(context, next) {
|
|
1375
|
+
next(new Error('denied'));
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
localPresence1.submit({index: 1}, function(error) {
|
|
1379
|
+
expect(error.message).to.contain('denied');
|
|
1380
|
+
expect(connection1.agent.presenceRequests).to.be.empty;
|
|
1381
|
+
expect(connection1.agent._pendingPresenceBroadcasts).to.be.empty;
|
|
1382
|
+
done();
|
|
1383
|
+
});
|
|
1384
|
+
});
|
|
1385
|
+
|
|
796
1386
|
it('errors when trying to submit presence on a type that does not support it', function(done) {
|
|
797
1387
|
var jsonDoc = connection1.get('books', 'snuff');
|
|
798
1388
|
var jsonPresence = connection1.getDocPresence('books', 'snuff');
|
|
@@ -976,12 +1566,19 @@ describe('DocPresence', function() {
|
|
|
976
1566
|
var localPresence1 = presence1.create('presence-1');
|
|
977
1567
|
var catchUpCount = 0;
|
|
978
1568
|
|
|
979
|
-
function
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1569
|
+
function receiveUntransformablePresenceAtVersion(version) {
|
|
1570
|
+
connection2.handleMessage({
|
|
1571
|
+
a: ACTIONS.presence,
|
|
1572
|
+
ch: 'books.northern-lights',
|
|
1573
|
+
src: connection1.id,
|
|
1574
|
+
id: 'presence-1',
|
|
1575
|
+
p: {index: 0},
|
|
1576
|
+
pv: version,
|
|
1577
|
+
c: 'books',
|
|
1578
|
+
d: 'northern-lights',
|
|
1579
|
+
v: version,
|
|
1580
|
+
t: null
|
|
1581
|
+
});
|
|
985
1582
|
}
|
|
986
1583
|
|
|
987
1584
|
async.series([
|
|
@@ -992,7 +1589,7 @@ describe('DocPresence', function() {
|
|
|
992
1589
|
catchUpCount++;
|
|
993
1590
|
if (catchUpCount === 1) next();
|
|
994
1591
|
});
|
|
995
|
-
|
|
1592
|
+
receiveUntransformablePresenceAtVersion(0);
|
|
996
1593
|
},
|
|
997
1594
|
function(next) {
|
|
998
1595
|
doc2.once('op', function() {
|
|
@@ -1006,7 +1603,7 @@ describe('DocPresence', function() {
|
|
|
1006
1603
|
expect(error.code).to.equal('ERR_DOC_TYPE_NOT_RECOGNIZED');
|
|
1007
1604
|
next();
|
|
1008
1605
|
});
|
|
1009
|
-
|
|
1606
|
+
receiveUntransformablePresenceAtVersion(1);
|
|
1010
1607
|
},
|
|
1011
1608
|
function(next) {
|
|
1012
1609
|
doc2.once('op', function() {
|
|
@@ -1047,6 +1644,252 @@ describe('DocPresence', function() {
|
|
|
1047
1644
|
], done);
|
|
1048
1645
|
});
|
|
1049
1646
|
|
|
1647
|
+
it('releases the doc version stream of presence in flight when unsubscribing', function(done) {
|
|
1648
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1649
|
+
var streamsCount;
|
|
1650
|
+
|
|
1651
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.receivePresence, function(context, next) {
|
|
1652
|
+
if (context.presence.p === null) return next();
|
|
1653
|
+
presence1.unsubscribe(function(error) {
|
|
1654
|
+
if (error) return done(error);
|
|
1655
|
+
next();
|
|
1656
|
+
});
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
async.series([
|
|
1660
|
+
presence2.subscribe.bind(presence2),
|
|
1661
|
+
function(next) {
|
|
1662
|
+
streamsCount = backend.pubsub.streamsCount;
|
|
1663
|
+
next();
|
|
1664
|
+
},
|
|
1665
|
+
presence1.subscribe.bind(presence1),
|
|
1666
|
+
function(next) {
|
|
1667
|
+
async.parallel([
|
|
1668
|
+
function(cb) {
|
|
1669
|
+
presence2.once('receive', function(id, presence) {
|
|
1670
|
+
expect(id).to.equal('presence-1');
|
|
1671
|
+
expect(presence).to.be.null;
|
|
1672
|
+
cb();
|
|
1673
|
+
});
|
|
1674
|
+
},
|
|
1675
|
+
function(cb) {
|
|
1676
|
+
localPresence1.submit({index: 1}, cb);
|
|
1677
|
+
}
|
|
1678
|
+
], next);
|
|
1679
|
+
},
|
|
1680
|
+
function(next) {
|
|
1681
|
+
expect(backend.pubsub.streamsCount).to.equal(streamsCount);
|
|
1682
|
+
next();
|
|
1683
|
+
}
|
|
1684
|
+
], done);
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
it('releases the doc version stream of presence transformed across a delete when unsubscribing', function(done) {
|
|
1688
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1689
|
+
|
|
1690
|
+
async.series([
|
|
1691
|
+
presence1.subscribe.bind(presence1),
|
|
1692
|
+
doc1.unsubscribe.bind(doc1),
|
|
1693
|
+
doc2.del.bind(doc2),
|
|
1694
|
+
localPresence1.submit.bind(localPresence1, {index: 1}),
|
|
1695
|
+
function(next) {
|
|
1696
|
+
expect(connection1.agent.presenceRequests['books.northern-lights']['presence-1'].p).to.be.null;
|
|
1697
|
+
expect(connection1.agent.latestDocVersionStreams).not.to.be.empty;
|
|
1698
|
+
presence1.unsubscribe(next);
|
|
1699
|
+
},
|
|
1700
|
+
function(next) {
|
|
1701
|
+
expect(connection1.agent.latestDocVersions).to.be.empty;
|
|
1702
|
+
expect(connection1.agent.latestDocVersionStreams).to.be.empty;
|
|
1703
|
+
next();
|
|
1704
|
+
}
|
|
1705
|
+
], done);
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
it('releases the doc version stream of presence that failed to transform when unsubscribing', function(done) {
|
|
1709
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1710
|
+
var localPresence2 = presence1.create('presence-2');
|
|
1711
|
+
|
|
1712
|
+
async.series([
|
|
1713
|
+
presence1.subscribe.bind(presence1),
|
|
1714
|
+
presence2.subscribe.bind(presence2),
|
|
1715
|
+
doc1.unsubscribe.bind(doc1),
|
|
1716
|
+
doc2.submitOp.bind(doc2, {index: 5, value: 'ern'}),
|
|
1717
|
+
function(next) {
|
|
1718
|
+
localPresence1.submit({badProp: 'foo'}, function(error) {
|
|
1719
|
+
expect(error.code).to.equal('ERR_PRESENCE_TRANSFORM_FAILED');
|
|
1720
|
+
expect(connection1.agent.latestDocVersionStreams).not.to.be.empty;
|
|
1721
|
+
presence1.unsubscribe(next);
|
|
1722
|
+
});
|
|
1723
|
+
},
|
|
1724
|
+
function(next) {
|
|
1725
|
+
expect(connection1.agent.latestDocVersions).to.be.empty;
|
|
1726
|
+
expect(connection1.agent.latestDocVersionStreams).to.be.empty;
|
|
1727
|
+
localPresence2.submit({index: 2}, errorHandler(done));
|
|
1728
|
+
presence2.once('receive', function(id) {
|
|
1729
|
+
expect(id).to.equal('presence-2');
|
|
1730
|
+
next();
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
], done);
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
it('does not broadcast an older presence that finishes transforming after its client unsubscribes', function(done) {
|
|
1737
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1738
|
+
var localPresence2 = presence1.create('presence-2');
|
|
1739
|
+
var releaseFirstTransform;
|
|
1740
|
+
|
|
1741
|
+
async.series([
|
|
1742
|
+
presence1.subscribe.bind(presence1),
|
|
1743
|
+
presence2.subscribe.bind(presence2),
|
|
1744
|
+
doc1.unsubscribe.bind(doc1),
|
|
1745
|
+
doc2.submitOp.bind(doc2, {index: 5, value: 'ern'}),
|
|
1746
|
+
function(next) {
|
|
1747
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.op, function(context, callback) {
|
|
1748
|
+
if (releaseFirstTransform || context.agent !== connection1.agent) return callback();
|
|
1749
|
+
releaseFirstTransform = callback;
|
|
1750
|
+
next();
|
|
1751
|
+
});
|
|
1752
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
1753
|
+
},
|
|
1754
|
+
function(next) {
|
|
1755
|
+
localPresence1.submit({index: 2}, errorHandler(done));
|
|
1756
|
+
presence2.once('receive', function(id, presence) {
|
|
1757
|
+
expect(presence).to.eql({index: 2});
|
|
1758
|
+
next();
|
|
1759
|
+
});
|
|
1760
|
+
},
|
|
1761
|
+
function(next) {
|
|
1762
|
+
presence1.unsubscribe(errorHandler(done));
|
|
1763
|
+
presence2.once('receive', function(id, presence) {
|
|
1764
|
+
expect(presence).to.be.null;
|
|
1765
|
+
next();
|
|
1766
|
+
});
|
|
1767
|
+
},
|
|
1768
|
+
function(next) {
|
|
1769
|
+
releaseFirstTransform();
|
|
1770
|
+
localPresence2.submit({index: 3}, errorHandler(done));
|
|
1771
|
+
presence2.once('receive', function(id) {
|
|
1772
|
+
expect(id).to.equal('presence-2');
|
|
1773
|
+
next();
|
|
1774
|
+
});
|
|
1775
|
+
},
|
|
1776
|
+
function(next) {
|
|
1777
|
+
expect(presence2.remotePresences).to.have.all.keys('presence-2');
|
|
1778
|
+
next();
|
|
1779
|
+
}
|
|
1780
|
+
], done);
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
it('broadcasts an older presence still transforming when a newer one fails to transform', function(done) {
|
|
1784
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1785
|
+
var releaseFirstTransform;
|
|
1786
|
+
|
|
1787
|
+
async.series([
|
|
1788
|
+
presence2.subscribe.bind(presence2),
|
|
1789
|
+
doc1.unsubscribe.bind(doc1),
|
|
1790
|
+
doc2.submitOp.bind(doc2, {index: 5, value: 'ern'}),
|
|
1791
|
+
function(next) {
|
|
1792
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.op, function(context, callback) {
|
|
1793
|
+
if (releaseFirstTransform || context.agent !== connection1.agent) return callback();
|
|
1794
|
+
releaseFirstTransform = callback;
|
|
1795
|
+
next();
|
|
1796
|
+
});
|
|
1797
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
1798
|
+
},
|
|
1799
|
+
function(next) {
|
|
1800
|
+
localPresence1.submit({badProp: 'foo'}, function(error) {
|
|
1801
|
+
expect(error.code).to.equal('ERR_PRESENCE_TRANSFORM_FAILED');
|
|
1802
|
+
next();
|
|
1803
|
+
});
|
|
1804
|
+
},
|
|
1805
|
+
function(next) {
|
|
1806
|
+
releaseFirstTransform();
|
|
1807
|
+
presence2.once('receive', function(id, presence) {
|
|
1808
|
+
expect(id).to.equal('presence-1');
|
|
1809
|
+
expect(presence).to.eql({index: 1});
|
|
1810
|
+
next();
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
], done);
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
it('broadcasts a null presence still being transformed when the connection closes', function(done) {
|
|
1817
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1818
|
+
|
|
1819
|
+
async.series([
|
|
1820
|
+
presence2.subscribe.bind(presence2),
|
|
1821
|
+
doc1.unsubscribe.bind(doc1),
|
|
1822
|
+
doc2.submitOp.bind(doc2, {index: 5, value: 'ern'}),
|
|
1823
|
+
function(next) {
|
|
1824
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
1825
|
+
presence2.once('receive', function() {
|
|
1826
|
+
next();
|
|
1827
|
+
});
|
|
1828
|
+
},
|
|
1829
|
+
function(next) {
|
|
1830
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.op, function(context, callback) {
|
|
1831
|
+
if (context.agent !== connection1.agent) return callback();
|
|
1832
|
+
connection1.agent.stream.once('end', function() {
|
|
1833
|
+
callback();
|
|
1834
|
+
});
|
|
1835
|
+
connection1.close();
|
|
1836
|
+
});
|
|
1837
|
+
localPresence1.submit(null, errorHandler(done));
|
|
1838
|
+
presence2.once('receive', function(id, presence) {
|
|
1839
|
+
expect(id).to.equal('presence-1');
|
|
1840
|
+
expect(presence).to.be.null;
|
|
1841
|
+
next();
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
], done);
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
it('does not broadcast presence still looking up its doc version when it is destroyed', function(done) {
|
|
1848
|
+
var localPresence1 = presence1.create('presence-1');
|
|
1849
|
+
var localPresence2 = presence1.create('presence-2');
|
|
1850
|
+
var releaseDocVersion;
|
|
1851
|
+
|
|
1852
|
+
async.series([
|
|
1853
|
+
presence2.subscribe.bind(presence2),
|
|
1854
|
+
function(next) {
|
|
1855
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.readSnapshots, function(context, callback) {
|
|
1856
|
+
if (releaseDocVersion || context.agent !== connection1.agent) return callback();
|
|
1857
|
+
releaseDocVersion = callback;
|
|
1858
|
+
next();
|
|
1859
|
+
});
|
|
1860
|
+
localPresence1.submit({index: 1}, errorHandler(done));
|
|
1861
|
+
},
|
|
1862
|
+
function(next) {
|
|
1863
|
+
localPresence1.destroy(errorHandler(done));
|
|
1864
|
+
presence2.once('receive', function(id, presence) {
|
|
1865
|
+
expect(id).to.equal('presence-1');
|
|
1866
|
+
expect(presence).to.be.null;
|
|
1867
|
+
next();
|
|
1868
|
+
});
|
|
1869
|
+
},
|
|
1870
|
+
function(next) {
|
|
1871
|
+
connection1.once('receive', function(request) {
|
|
1872
|
+
expect(request.data).to.include({id: 'presence-1', pv: 0});
|
|
1873
|
+
expect(connection1.agent.latestDocVersions).to.be.empty;
|
|
1874
|
+
expect(connection1.agent.latestDocVersionStreams).to.be.empty;
|
|
1875
|
+
next();
|
|
1876
|
+
});
|
|
1877
|
+
releaseDocVersion();
|
|
1878
|
+
},
|
|
1879
|
+
function(next) {
|
|
1880
|
+
localPresence2.submit({index: 2}, errorHandler(done));
|
|
1881
|
+
presence2.once('receive', function(id) {
|
|
1882
|
+
expect(id).to.equal('presence-2');
|
|
1883
|
+
next();
|
|
1884
|
+
});
|
|
1885
|
+
},
|
|
1886
|
+
function(next) {
|
|
1887
|
+
expect(presence2.remotePresences).to.have.all.keys('presence-2');
|
|
1888
|
+
next();
|
|
1889
|
+
}
|
|
1890
|
+
], done);
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1050
1893
|
it('does not error when destroying presence for a deleted doc', function(done) {
|
|
1051
1894
|
var localPresence1 = presence1.create('presence-1');
|
|
1052
1895
|
|