mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-06-04 06:02:11 +03:00
Much of this transformation has been done automatically: * add expect import to each file * replace `not.to` with `toNot` * replace `to[Not]Be{Undefined,Null}` with equivalents * replace `jasmine.createSpy(...)` with `except.createSpy`, and `andCallFake` with `andCall` Also: * replace `jasmine.createSpyObj` with manual alternatives * replace `jasmine.Clock` with `lolex`
525 lines
20 KiB
JavaScript
525 lines
20 KiB
JavaScript
"use strict";
|
|
import 'source-map-support/register';
|
|
const q = require("q");
|
|
const sdk = require("../..");
|
|
const MatrixClient = sdk.MatrixClient;
|
|
const utils = require("../test-utils");
|
|
|
|
import expect from 'expect';
|
|
import lolex from 'lolex';
|
|
|
|
describe("MatrixClient", function() {
|
|
const userId = "@alice:bar";
|
|
const identityServerUrl = "https://identity.server";
|
|
const identityServerDomain = "identity.server";
|
|
let client;
|
|
let store;
|
|
let scheduler;
|
|
let clock;
|
|
|
|
const KEEP_ALIVE_PATH = "/_matrix/client/versions";
|
|
|
|
const PUSH_RULES_RESPONSE = {
|
|
method: "GET",
|
|
path: "/pushrules/",
|
|
data: {},
|
|
};
|
|
|
|
const FILTER_PATH = "/user/" + encodeURIComponent(userId) + "/filter";
|
|
|
|
const FILTER_RESPONSE = {
|
|
method: "POST",
|
|
path: FILTER_PATH,
|
|
data: { filter_id: "f1lt3r" },
|
|
};
|
|
|
|
const SYNC_DATA = {
|
|
next_batch: "s_5_3",
|
|
presence: { events: [] },
|
|
rooms: {},
|
|
};
|
|
|
|
const SYNC_RESPONSE = {
|
|
method: "GET",
|
|
path: "/sync",
|
|
data: SYNC_DATA,
|
|
};
|
|
|
|
let httpLookups = [
|
|
// items are objects which look like:
|
|
// {
|
|
// method: "GET",
|
|
// path: "/initialSync",
|
|
// data: {},
|
|
// error: { errcode: M_FORBIDDEN } // if present will reject promise,
|
|
// expectBody: {} // additional expects on the body
|
|
// expectQueryParams: {} // additional expects on query params
|
|
// thenCall: function(){} // function to call *AFTER* returning response.
|
|
// }
|
|
// items are popped off when processed and block if no items left.
|
|
];
|
|
let acceptKeepalives;
|
|
let pendingLookup = null;
|
|
function httpReq(cb, method, path, qp, data, prefix) {
|
|
if (path === KEEP_ALIVE_PATH && acceptKeepalives) {
|
|
return q();
|
|
}
|
|
const next = httpLookups.shift();
|
|
const logLine = (
|
|
"MatrixClient[UT] RECV " + method + " " + path + " " +
|
|
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
|
|
);
|
|
console.log(logLine);
|
|
|
|
if (!next) { // no more things to return
|
|
if (pendingLookup) {
|
|
if (pendingLookup.method === method && pendingLookup.path === path) {
|
|
return pendingLookup.promise;
|
|
}
|
|
// >1 pending thing, and they are different, whine.
|
|
expect(false).toBe(
|
|
true, ">1 pending request. You should probably handle them. " +
|
|
"PENDING: " + JSON.stringify(pendingLookup) + " JUST GOT: " +
|
|
method + " " + path,
|
|
);
|
|
}
|
|
pendingLookup = {
|
|
promise: q.defer().promise,
|
|
method: method,
|
|
path: path,
|
|
};
|
|
return pendingLookup.promise;
|
|
}
|
|
if (next.path === path && next.method === method) {
|
|
console.log(
|
|
"MatrixClient[UT] Matched. Returning " +
|
|
(next.error ? "BAD" : "GOOD") + " response",
|
|
);
|
|
if (next.expectBody) {
|
|
expect(next.expectBody).toEqual(data);
|
|
}
|
|
if (next.expectQueryParams) {
|
|
Object.keys(next.expectQueryParams).forEach(function(k) {
|
|
expect(qp[k]).toEqual(next.expectQueryParams[k]);
|
|
});
|
|
}
|
|
|
|
if (next.thenCall) {
|
|
process.nextTick(next.thenCall, 0); // next tick so we return first.
|
|
}
|
|
|
|
if (next.error) {
|
|
return q.reject({
|
|
errcode: next.error.errcode,
|
|
httpStatus: next.error.httpStatus,
|
|
name: next.error.errcode,
|
|
message: "Expected testing error",
|
|
data: next.error,
|
|
});
|
|
}
|
|
return q(next.data);
|
|
}
|
|
expect(true).toBe(false, "Expected different request. " + logLine);
|
|
return q.defer().promise;
|
|
}
|
|
|
|
beforeEach(function() {
|
|
utils.beforeEach(this); // eslint-disable-line no-invalid-this
|
|
clock = lolex.install();
|
|
scheduler = [
|
|
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
|
"setProcessFunction",
|
|
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
|
|
store = [
|
|
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
|
|
"setSyncToken", "storeEvents", "storeRoom", "storeUser",
|
|
"getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter",
|
|
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
|
|
client = new MatrixClient({
|
|
baseUrl: "https://my.home.server",
|
|
idBaseUrl: identityServerUrl,
|
|
accessToken: "my.access.token",
|
|
request: function() {}, // NOP
|
|
store: store,
|
|
scheduler: scheduler,
|
|
userId: userId,
|
|
});
|
|
// FIXME: We shouldn't be yanking _http like this.
|
|
client._http = [
|
|
"authedRequest", "authedRequestWithPrefix", "getContentUri",
|
|
"request", "requestWithPrefix", "uploadContent",
|
|
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
|
|
client._http.authedRequest.andCall(httpReq);
|
|
client._http.authedRequestWithPrefix.andCall(httpReq);
|
|
client._http.requestWithPrefix.andCall(httpReq);
|
|
client._http.request.andCall(httpReq);
|
|
|
|
// set reasonable working defaults
|
|
acceptKeepalives = true;
|
|
pendingLookup = null;
|
|
httpLookups = [];
|
|
httpLookups.push(PUSH_RULES_RESPONSE);
|
|
httpLookups.push(FILTER_RESPONSE);
|
|
httpLookups.push(SYNC_RESPONSE);
|
|
});
|
|
|
|
afterEach(function() {
|
|
clock.uninstall();
|
|
// need to re-stub the requests with NOPs because there are no guarantees
|
|
// clients from previous tests will be GC'd before the next test. This
|
|
// means they may call /events and then fail an expect() which will fail
|
|
// a DIFFERENT test (pollution between tests!) - we return unresolved
|
|
// promises to stop the client from continuing to run.
|
|
client._http.authedRequest.andCall(function() {
|
|
return q.defer().promise;
|
|
});
|
|
client._http.authedRequestWithPrefix.andCall(function() {
|
|
return q.defer().promise;
|
|
});
|
|
});
|
|
|
|
it("should not POST /filter if a matching filter already exists", function(done) {
|
|
httpLookups = [];
|
|
httpLookups.push(PUSH_RULES_RESPONSE);
|
|
httpLookups.push(SYNC_RESPONSE);
|
|
const filterId = "ehfewf";
|
|
store.getFilterIdByName.andReturn(filterId);
|
|
const filter = new sdk.Filter(0, filterId);
|
|
filter.setDefinition({"room": {"timeline": {"limit": 8}}});
|
|
store.getFilter.andReturn(filter);
|
|
client.startClient();
|
|
|
|
client.on("sync", function syncListener(state) {
|
|
if (state === "SYNCING") {
|
|
expect(httpLookups.length).toEqual(0);
|
|
client.removeListener("sync", syncListener);
|
|
done();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("getSyncState", function() {
|
|
it("should return null if the client isn't started", function() {
|
|
expect(client.getSyncState()).toBe(null);
|
|
});
|
|
|
|
it("should return the same sync state as emitted sync events", function(done) {
|
|
client.on("sync", function syncListener(state) {
|
|
expect(state).toEqual(client.getSyncState());
|
|
if (state === "SYNCING") {
|
|
client.removeListener("sync", syncListener);
|
|
done();
|
|
}
|
|
});
|
|
client.startClient();
|
|
});
|
|
});
|
|
|
|
describe("getOrCreateFilter", function() {
|
|
it("should POST createFilter if no id is present in localStorage", function() {
|
|
});
|
|
it("should use an existing filter if id is present in localStorage", function() {
|
|
});
|
|
it("should handle localStorage filterId missing from the server", function(done) {
|
|
function getFilterName(userId, suffix) {
|
|
// scope this on the user ID because people may login on many accounts
|
|
// and they all need to be stored!
|
|
return "FILTER_SYNC_" + userId + (suffix ? "_" + suffix : "");
|
|
}
|
|
const invalidFilterId = 'invalidF1lt3r';
|
|
httpLookups = [];
|
|
httpLookups.push({
|
|
method: "GET",
|
|
path: FILTER_PATH + '/' + invalidFilterId,
|
|
error: {
|
|
errcode: "M_UNKNOWN",
|
|
name: "M_UNKNOWN",
|
|
message: "No row found",
|
|
data: { errcode: "M_UNKNOWN", error: "No row found" },
|
|
httpStatus: 404,
|
|
},
|
|
});
|
|
httpLookups.push(FILTER_RESPONSE);
|
|
store.getFilterIdByName.andReturn(invalidFilterId);
|
|
|
|
const filterName = getFilterName(client.credentials.userId);
|
|
client.store.setFilterIdByName(filterName, invalidFilterId);
|
|
const filter = new sdk.Filter(client.credentials.userId);
|
|
|
|
client.getOrCreateFilter(filterName, filter).then(function(filterId) {
|
|
expect(filterId).toEqual(FILTER_RESPONSE.data.filter_id);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("retryImmediately", function() {
|
|
it("should return false if there is no request waiting", function() {
|
|
client.startClient();
|
|
expect(client.retryImmediately()).toBe(false);
|
|
});
|
|
|
|
it("should work on /filter", function(done) {
|
|
httpLookups = [];
|
|
httpLookups.push(PUSH_RULES_RESPONSE);
|
|
httpLookups.push({
|
|
method: "POST", path: FILTER_PATH, error: { errcode: "NOPE_NOPE_NOPE" },
|
|
});
|
|
httpLookups.push(FILTER_RESPONSE);
|
|
httpLookups.push(SYNC_RESPONSE);
|
|
|
|
client.on("sync", function syncListener(state) {
|
|
if (state === "ERROR" && httpLookups.length > 0) {
|
|
expect(httpLookups.length).toEqual(2);
|
|
expect(client.retryImmediately()).toBe(true);
|
|
clock.tick(1);
|
|
} else if (state === "PREPARED" && httpLookups.length === 0) {
|
|
client.removeListener("sync", syncListener);
|
|
done();
|
|
} else {
|
|
// unexpected state transition!
|
|
expect(state).toEqual(null);
|
|
}
|
|
});
|
|
client.startClient();
|
|
});
|
|
|
|
it("should work on /sync", function(done) {
|
|
httpLookups.push({
|
|
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
|
|
});
|
|
httpLookups.push({
|
|
method: "GET", path: "/sync", data: SYNC_DATA,
|
|
});
|
|
|
|
client.on("sync", function syncListener(state) {
|
|
if (state === "ERROR" && httpLookups.length > 0) {
|
|
expect(httpLookups.length).toEqual(1);
|
|
expect(client.retryImmediately()).toBe(
|
|
true, "retryImmediately returned false",
|
|
);
|
|
clock.tick(1);
|
|
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
|
|
clock.tick(10000);
|
|
} else if (state === "SYNCING" && httpLookups.length === 0) {
|
|
client.removeListener("sync", syncListener);
|
|
done();
|
|
}
|
|
});
|
|
client.startClient();
|
|
});
|
|
|
|
it("should work on /pushrules", function(done) {
|
|
httpLookups = [];
|
|
httpLookups.push({
|
|
method: "GET", path: "/pushrules/", error: { errcode: "NOPE_NOPE_NOPE" },
|
|
});
|
|
httpLookups.push(PUSH_RULES_RESPONSE);
|
|
httpLookups.push(FILTER_RESPONSE);
|
|
httpLookups.push(SYNC_RESPONSE);
|
|
|
|
client.on("sync", function syncListener(state) {
|
|
if (state === "ERROR" && httpLookups.length > 0) {
|
|
expect(httpLookups.length).toEqual(3);
|
|
expect(client.retryImmediately()).toBe(true);
|
|
clock.tick(1);
|
|
} else if (state === "PREPARED" && httpLookups.length === 0) {
|
|
client.removeListener("sync", syncListener);
|
|
done();
|
|
} else {
|
|
// unexpected state transition!
|
|
expect(state).toEqual(null);
|
|
}
|
|
});
|
|
client.startClient();
|
|
});
|
|
});
|
|
|
|
describe("emitted sync events", function() {
|
|
function syncChecker(expectedStates, done) {
|
|
return function syncListener(state, old) {
|
|
const expected = expectedStates.shift();
|
|
console.log(
|
|
"'sync' curr=%s old=%s EXPECT=%s", state, old, expected,
|
|
);
|
|
if (!expected) {
|
|
done();
|
|
return;
|
|
}
|
|
expect(state).toEqual(expected[0]);
|
|
expect(old).toEqual(expected[1]);
|
|
if (expectedStates.length === 0) {
|
|
client.removeListener("sync", syncListener);
|
|
done();
|
|
}
|
|
// standard retry time is 5 to 10 seconds
|
|
clock.tick(10000);
|
|
};
|
|
}
|
|
|
|
it("should transition null -> PREPARED after the first /sync", function(done) {
|
|
const expectedStates = [];
|
|
expectedStates.push(["PREPARED", null]);
|
|
client.on("sync", syncChecker(expectedStates, done));
|
|
client.startClient();
|
|
});
|
|
|
|
it("should transition null -> ERROR after a failed /filter", function(done) {
|
|
const expectedStates = [];
|
|
httpLookups = [];
|
|
httpLookups.push(PUSH_RULES_RESPONSE);
|
|
httpLookups.push({
|
|
method: "POST", path: FILTER_PATH, error: { errcode: "NOPE_NOPE_NOPE" },
|
|
});
|
|
expectedStates.push(["ERROR", null]);
|
|
client.on("sync", syncChecker(expectedStates, done));
|
|
client.startClient();
|
|
});
|
|
|
|
it("should transition ERROR -> PREPARED after /sync if prev failed",
|
|
function(done) {
|
|
const expectedStates = [];
|
|
acceptKeepalives = false;
|
|
httpLookups = [];
|
|
httpLookups.push(PUSH_RULES_RESPONSE);
|
|
httpLookups.push(FILTER_RESPONSE);
|
|
httpLookups.push({
|
|
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
|
|
});
|
|
httpLookups.push({
|
|
method: "GET", path: KEEP_ALIVE_PATH,
|
|
error: { errcode: "KEEPALIVE_FAIL" },
|
|
});
|
|
httpLookups.push({
|
|
method: "GET", path: KEEP_ALIVE_PATH, data: {},
|
|
});
|
|
httpLookups.push({
|
|
method: "GET", path: "/sync", data: SYNC_DATA,
|
|
});
|
|
|
|
expectedStates.push(["RECONNECTING", null]);
|
|
expectedStates.push(["ERROR", "RECONNECTING"]);
|
|
expectedStates.push(["PREPARED", "ERROR"]);
|
|
client.on("sync", syncChecker(expectedStates, done));
|
|
client.startClient();
|
|
});
|
|
|
|
it("should transition PREPARED -> SYNCING after /sync", function(done) {
|
|
const expectedStates = [];
|
|
expectedStates.push(["PREPARED", null]);
|
|
expectedStates.push(["SYNCING", "PREPARED"]);
|
|
client.on("sync", syncChecker(expectedStates, done));
|
|
client.startClient();
|
|
});
|
|
|
|
it("should transition SYNCING -> ERROR after a failed /sync", function(done) {
|
|
acceptKeepalives = false;
|
|
const expectedStates = [];
|
|
httpLookups.push({
|
|
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
|
|
});
|
|
httpLookups.push({
|
|
method: "GET", path: KEEP_ALIVE_PATH,
|
|
error: { errcode: "KEEPALIVE_FAIL" },
|
|
});
|
|
|
|
expectedStates.push(["PREPARED", null]);
|
|
expectedStates.push(["SYNCING", "PREPARED"]);
|
|
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
|
expectedStates.push(["ERROR", "RECONNECTING"]);
|
|
client.on("sync", syncChecker(expectedStates, done));
|
|
client.startClient();
|
|
});
|
|
|
|
xit("should transition ERROR -> SYNCING after /sync if prev failed",
|
|
function(done) {
|
|
const expectedStates = [];
|
|
httpLookups.push({
|
|
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
|
|
});
|
|
httpLookups.push(SYNC_RESPONSE);
|
|
|
|
expectedStates.push(["PREPARED", null]);
|
|
expectedStates.push(["SYNCING", "PREPARED"]);
|
|
expectedStates.push(["ERROR", "SYNCING"]);
|
|
client.on("sync", syncChecker(expectedStates, done));
|
|
client.startClient();
|
|
});
|
|
|
|
it("should transition SYNCING -> SYNCING on subsequent /sync successes",
|
|
function(done) {
|
|
const expectedStates = [];
|
|
httpLookups.push(SYNC_RESPONSE);
|
|
httpLookups.push(SYNC_RESPONSE);
|
|
|
|
expectedStates.push(["PREPARED", null]);
|
|
expectedStates.push(["SYNCING", "PREPARED"]);
|
|
expectedStates.push(["SYNCING", "SYNCING"]);
|
|
client.on("sync", syncChecker(expectedStates, done));
|
|
client.startClient();
|
|
});
|
|
|
|
it("should transition ERROR -> ERROR if keepalive keeps failing", function(done) {
|
|
acceptKeepalives = false;
|
|
const expectedStates = [];
|
|
httpLookups.push({
|
|
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
|
|
});
|
|
httpLookups.push({
|
|
method: "GET", path: KEEP_ALIVE_PATH,
|
|
error: { errcode: "KEEPALIVE_FAIL" },
|
|
});
|
|
httpLookups.push({
|
|
method: "GET", path: KEEP_ALIVE_PATH,
|
|
error: { errcode: "KEEPALIVE_FAIL" },
|
|
});
|
|
|
|
expectedStates.push(["PREPARED", null]);
|
|
expectedStates.push(["SYNCING", "PREPARED"]);
|
|
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
|
expectedStates.push(["ERROR", "RECONNECTING"]);
|
|
expectedStates.push(["ERROR", "ERROR"]);
|
|
client.on("sync", syncChecker(expectedStates, done));
|
|
client.startClient();
|
|
});
|
|
});
|
|
|
|
describe("inviteByEmail", function() {
|
|
const roomId = "!foo:bar";
|
|
|
|
it("should send an invite HTTP POST", function() {
|
|
httpLookups = [{
|
|
method: "POST",
|
|
path: "/rooms/!foo%3Abar/invite",
|
|
data: {},
|
|
expectBody: {
|
|
id_server: identityServerDomain,
|
|
medium: "email",
|
|
address: "alice@gmail.com",
|
|
},
|
|
}];
|
|
client.inviteByEmail(roomId, "alice@gmail.com");
|
|
expect(httpLookups.length).toEqual(0);
|
|
});
|
|
});
|
|
|
|
describe("guest rooms", function() {
|
|
it("should only do /sync calls (without filter/pushrules)", function(done) {
|
|
httpLookups = []; // no /pushrules or /filter
|
|
httpLookups.push({
|
|
method: "GET",
|
|
path: "/sync",
|
|
data: SYNC_DATA,
|
|
thenCall: function() {
|
|
done();
|
|
},
|
|
});
|
|
client.setGuest(true);
|
|
client.startClient();
|
|
});
|
|
|
|
xit("should be able to peek into a room using peekInRoom", function(done) {
|
|
});
|
|
});
|
|
});
|