Skip to content

Commit 8f13df2

Browse files
authored
Merge pull request #2567 from matrix-org/kegan/sync-v3
Add txn_id support to sliding sync
2 parents 055af93 + fa9f078 commit 8f13df2

File tree

2 files changed

+296
-11
lines changed

2 files changed

+296
-11
lines changed

spec/integ/sliding-sync.spec.ts

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,225 @@ describe("SlidingSync", () => {
562562
});
563563
});
564564

565+
describe("transaction IDs", () => {
566+
beforeAll(setupClient);
567+
afterAll(teardownClient);
568+
const roomId = "!foo:bar";
569+
570+
let slidingSync: SlidingSync;
571+
572+
// really this applies to them all but it's easier to just test one
573+
it("should resolve modifyRoomSubscriptions after SlidingSync.start() is called", async () => {
574+
const roomSubInfo = {
575+
timeline_limit: 1,
576+
required_state: [
577+
["m.room.name", ""],
578+
],
579+
};
580+
// add the subscription
581+
slidingSync = new SlidingSync(proxyBaseUrl, [], roomSubInfo, client, 1);
582+
// modification before SlidingSync.start()
583+
const subscribePromise = slidingSync.modifyRoomSubscriptions(new Set([roomId]));
584+
let txnId;
585+
httpBackend.when("POST", syncUrl).check(function(req) {
586+
const body = req.data;
587+
logger.debug("got ", body);
588+
expect(body.room_subscriptions).toBeTruthy();
589+
expect(body.room_subscriptions[roomId]).toEqual(roomSubInfo);
590+
expect(body.txn_id).toBeTruthy();
591+
txnId = body.txn_id;
592+
}).respond(200, function() {
593+
return {
594+
pos: "aaa",
595+
txn_id: txnId,
596+
lists: [],
597+
extensions: {},
598+
rooms: {
599+
[roomId]: {
600+
name: "foo bar",
601+
required_state: [],
602+
timeline: [],
603+
},
604+
},
605+
};
606+
});
607+
slidingSync.start();
608+
await httpBackend.flushAllExpected();
609+
await subscribePromise;
610+
});
611+
it("should resolve setList during a connection", async () => {
612+
const newList = {
613+
ranges: [[0, 20]],
614+
};
615+
const promise = slidingSync.setList(0, newList);
616+
let txnId;
617+
httpBackend.when("POST", syncUrl).check(function(req) {
618+
const body = req.data;
619+
logger.debug("got ", body);
620+
expect(body.room_subscriptions).toBeFalsy();
621+
expect(body.lists[0]).toEqual(newList);
622+
expect(body.txn_id).toBeTruthy();
623+
txnId = body.txn_id;
624+
}).respond(200, function() {
625+
return {
626+
pos: "bbb",
627+
txn_id: txnId,
628+
lists: [{ count: 5 }],
629+
extensions: {},
630+
};
631+
});
632+
await httpBackend.flushAllExpected();
633+
await promise;
634+
expect(txnId).toBeDefined();
635+
});
636+
it("should resolve setListRanges during a connection", async () => {
637+
const promise = slidingSync.setListRanges(0, [[20, 40]]);
638+
let txnId;
639+
httpBackend.when("POST", syncUrl).check(function(req) {
640+
const body = req.data;
641+
logger.debug("got ", body);
642+
expect(body.room_subscriptions).toBeFalsy();
643+
expect(body.lists[0]).toEqual({
644+
ranges: [[20, 40]],
645+
});
646+
expect(body.txn_id).toBeTruthy();
647+
txnId = body.txn_id;
648+
}).respond(200, function() {
649+
return {
650+
pos: "ccc",
651+
txn_id: txnId,
652+
lists: [{ count: 5 }],
653+
extensions: {},
654+
};
655+
});
656+
await httpBackend.flushAllExpected();
657+
await promise;
658+
expect(txnId).toBeDefined();
659+
});
660+
it("should resolve modifyRoomSubscriptionInfo during a connection", async () => {
661+
const promise = slidingSync.modifyRoomSubscriptionInfo({
662+
timeline_limit: 99,
663+
});
664+
let txnId;
665+
httpBackend.when("POST", syncUrl).check(function(req) {
666+
const body = req.data;
667+
logger.debug("got ", body);
668+
expect(body.room_subscriptions).toBeTruthy();
669+
expect(body.room_subscriptions[roomId]).toEqual({
670+
timeline_limit: 99,
671+
});
672+
expect(body.txn_id).toBeTruthy();
673+
txnId = body.txn_id;
674+
}).respond(200, function() {
675+
return {
676+
pos: "ddd",
677+
txn_id: txnId,
678+
extensions: {},
679+
};
680+
});
681+
await httpBackend.flushAllExpected();
682+
await promise;
683+
expect(txnId).toBeDefined();
684+
});
685+
it("should reject earlier pending promises if a later transaction is acknowledged", async () => {
686+
// i.e if we have [A,B,C] and see txn_id=C then A,B should be rejected.
687+
const gotTxnIds = [];
688+
const pushTxn = function(req) {
689+
gotTxnIds.push(req.data.txn_id);
690+
};
691+
const failPromise = slidingSync.setListRanges(0, [[20, 40]]);
692+
httpBackend.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "e" }); // missing txn_id
693+
await httpBackend.flushAllExpected();
694+
const failPromise2 = slidingSync.setListRanges(0, [[60, 70]]);
695+
httpBackend.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "f" }); // missing txn_id
696+
await httpBackend.flushAllExpected();
697+
698+
// attach rejection handlers now else if we do it later Jest treats that as an unhandled rejection
699+
// which is a fail.
700+
expect(failPromise).rejects.toEqual(gotTxnIds[0]);
701+
expect(failPromise2).rejects.toEqual(gotTxnIds[1]);
702+
703+
const okPromise = slidingSync.setListRanges(0, [[0, 20]]);
704+
let txnId;
705+
httpBackend.when("POST", syncUrl).check((req) => {
706+
txnId = req.data.txn_id;
707+
}).respond(200, () => {
708+
// include the txn_id, earlier requests should now be reject()ed.
709+
return {
710+
pos: "g",
711+
txn_id: txnId,
712+
};
713+
});
714+
await httpBackend.flushAllExpected();
715+
await okPromise;
716+
717+
expect(txnId).toBeDefined();
718+
});
719+
it("should not reject later pending promises if an earlier transaction is acknowledged", async () => {
720+
// i.e if we have [A,B,C] and see txn_id=B then C should not be rejected but A should.
721+
const gotTxnIds = [];
722+
const pushTxn = function(req) {
723+
gotTxnIds.push(req.data.txn_id);
724+
};
725+
const A = slidingSync.setListRanges(0, [[20, 40]]);
726+
httpBackend.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "A" });
727+
await httpBackend.flushAllExpected();
728+
const B = slidingSync.setListRanges(0, [[60, 70]]);
729+
httpBackend.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "B" }); // missing txn_id
730+
await httpBackend.flushAllExpected();
731+
732+
// attach rejection handlers now else if we do it later Jest treats that as an unhandled rejection
733+
// which is a fail.
734+
expect(A).rejects.toEqual(gotTxnIds[0]);
735+
736+
const C = slidingSync.setListRanges(0, [[0, 20]]);
737+
let pendingC = true;
738+
C.finally(() => {
739+
pendingC = false;
740+
});
741+
httpBackend.when("POST", syncUrl).check(pushTxn).respond(200, () => {
742+
// include the txn_id for B, so C's promise is outstanding
743+
return {
744+
pos: "C",
745+
txn_id: gotTxnIds[1],
746+
};
747+
});
748+
await httpBackend.flushAllExpected();
749+
// A is rejected, see above
750+
expect(B).resolves.toEqual(gotTxnIds[1]); // B is resolved
751+
expect(pendingC).toBe(true); // C is pending still
752+
});
753+
it("should do nothing for unknown txn_ids", async () => {
754+
const promise = slidingSync.setListRanges(0, [[20, 40]]);
755+
let pending = true;
756+
promise.finally(() => {
757+
pending = false;
758+
});
759+
let txnId;
760+
httpBackend.when("POST", syncUrl).check(function(req) {
761+
const body = req.data;
762+
logger.debug("got ", body);
763+
expect(body.room_subscriptions).toBeFalsy();
764+
expect(body.lists[0]).toEqual({
765+
ranges: [[20, 40]],
766+
});
767+
expect(body.txn_id).toBeTruthy();
768+
txnId = body.txn_id;
769+
}).respond(200, function() {
770+
return {
771+
pos: "ccc",
772+
txn_id: "bogus transaction id",
773+
lists: [{ count: 5 }],
774+
extensions: {},
775+
};
776+
});
777+
await httpBackend.flushAllExpected();
778+
expect(txnId).toBeDefined();
779+
expect(pending).toBe(true);
780+
slidingSync.stop();
781+
});
782+
});
783+
565784
describe("extensions", () => {
566785
beforeAll(setupClient);
567786
afterAll(teardownClient);

0 commit comments

Comments
 (0)