2015-12-09 09:07:48 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
describe("Zotero.Sync.APIClient", function () {
|
|
|
|
Components.utils.import("resource://zotero/config.js");
|
|
|
|
|
|
|
|
var apiKey = Zotero.Utilities.randomString(24);
|
|
|
|
var baseURL = "http://local.zotero/";
|
|
|
|
var server, client;
|
|
|
|
|
|
|
|
function setResponse(response) {
|
|
|
|
setHTTPResponse(server, baseURL, response, {});
|
|
|
|
}
|
|
|
|
|
|
|
|
before(function () {
|
2016-02-29 09:23:00 +00:00
|
|
|
Zotero.HTTP.mock = sinon.FakeXMLHttpRequest;
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(function () {
|
2015-12-09 09:07:48 +00:00
|
|
|
Components.utils.import("resource://zotero/concurrentCaller.js");
|
|
|
|
var caller = new ConcurrentCaller(1);
|
|
|
|
caller.setLogger(msg => Zotero.debug(msg));
|
|
|
|
caller.stopOnError = true;
|
|
|
|
caller.onError = function (e) {
|
|
|
|
Zotero.logError(e);
|
|
|
|
if (e.fatal) {
|
|
|
|
caller.stop();
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
client = new Zotero.Sync.APIClient({
|
|
|
|
baseURL,
|
|
|
|
apiVersion: ZOTERO_CONFIG.API_VERSION,
|
|
|
|
apiKey,
|
|
|
|
caller
|
|
|
|
})
|
2016-02-29 09:23:00 +00:00
|
|
|
|
2015-12-09 09:07:48 +00:00
|
|
|
server = sinon.fakeServer.create();
|
|
|
|
server.autoRespond = true;
|
|
|
|
})
|
|
|
|
|
|
|
|
after(function () {
|
|
|
|
Zotero.HTTP.mock = null;
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("#_checkConnection()", function () {
|
|
|
|
it("should catch an interrupted connection", function* () {
|
|
|
|
setResponse({
|
|
|
|
method: "GET",
|
|
|
|
url: "empty",
|
|
|
|
status: 0,
|
|
|
|
text: ""
|
|
|
|
})
|
|
|
|
var e = yield getPromiseError(client.makeRequest("GET", baseURL + "empty"));
|
|
|
|
assert.ok(e);
|
|
|
|
assert.equal(e.message, Zotero.getString('sync.error.checkConnection'));
|
|
|
|
})
|
|
|
|
})
|
2017-02-24 23:41:37 +00:00
|
|
|
|
|
|
|
describe("#getGroups()", function () {
|
|
|
|
it("should automatically fetch multiple pages of results", function* () {
|
|
|
|
function groupJSON(groupID) {
|
|
|
|
return {
|
|
|
|
id: groupID,
|
|
|
|
version: 1,
|
|
|
|
data: {
|
|
|
|
id: groupID,
|
|
|
|
version: 1,
|
|
|
|
name: "Group " + groupID
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
server.respond(function (req) {
|
|
|
|
if (req.method == "GET" && req.url.startsWith(baseURL + "users/1/groups")) {
|
|
|
|
// TODO: Use a real parser
|
|
|
|
let matches = req.url.match(/start=(\d+)/);
|
|
|
|
let start = matches ? parseInt(matches[1]) : null;
|
|
|
|
matches = req.url.match(/limit=(\d+)/);
|
|
|
|
let limit = matches ? parseInt(matches[1]) : null;
|
|
|
|
if (start === null && limit === null) {
|
|
|
|
req.respond(
|
|
|
|
200,
|
|
|
|
{
|
|
|
|
Link: `<${baseURL}users/1/groups?limit=2&start=2>; rel="next", <${baseURL}users/1/groups?limit=2&start=4>; rel="last", <${baseURL}users/1/groups>; rel="alternate"`,
|
|
|
|
"Total-Results": 2
|
|
|
|
},
|
|
|
|
JSON.stringify([
|
|
|
|
groupJSON(1),
|
|
|
|
groupJSON(2)
|
|
|
|
])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else if (start == 2 && limit == 2) {
|
|
|
|
req.respond(
|
|
|
|
200,
|
|
|
|
{
|
|
|
|
Link: `<${baseURL}users/1/groups?limit=2&start=4>; rel="next", <${baseURL}users/1/groups?limit=2&start=4>; rel="last", <${baseURL}users/1/groups>; rel="alternate"`,
|
|
|
|
"Total-Results": 5
|
|
|
|
},
|
|
|
|
JSON.stringify([
|
|
|
|
groupJSON(3),
|
|
|
|
groupJSON(4)
|
|
|
|
])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else if (start == 4 && limit == 2) {
|
|
|
|
req.respond(
|
|
|
|
200,
|
|
|
|
{
|
|
|
|
Link: `<${baseURL}users/1/groups?limit=2&start=4>; rel="last", <${baseURL}users/1/groups>; rel="alternate"`,
|
|
|
|
"Total-Results": 5
|
|
|
|
},
|
|
|
|
JSON.stringify([
|
|
|
|
groupJSON(5),
|
|
|
|
])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var results = yield client.getGroups(1);
|
|
|
|
assert.lengthOf(results, 5);
|
|
|
|
assert.sameMembers(results.map(o => o.id), [1, 2, 3, 4, 5]);
|
|
|
|
});
|
|
|
|
});
|
2017-04-11 08:17:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
describe("Retries", function () {
|
|
|
|
var spy;
|
|
|
|
var delayStub;
|
|
|
|
|
|
|
|
before(function () {
|
|
|
|
delayStub = sinon.stub(Zotero.Promise, "delay").returns(Zotero.Promise.resolve());
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
client.failureDelayIntervals = [10, 20];
|
|
|
|
client.failureDelayMax = 25;
|
|
|
|
client.rateDelayIntervals = [15, 25];
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(function () {
|
|
|
|
if (spy) {
|
|
|
|
spy.restore();
|
|
|
|
}
|
2017-06-01 19:30:06 +00:00
|
|
|
delayStub.resetHistory();
|
2017-04-11 08:17:16 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
after(function () {
|
|
|
|
delayStub.restore();
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("#makeRequest()", function () {
|
|
|
|
it("should retry on 500 error", function* () {
|
|
|
|
setResponse({
|
|
|
|
method: "GET",
|
|
|
|
url: "error",
|
|
|
|
status: 500,
|
|
|
|
text: ""
|
|
|
|
});
|
|
|
|
spy = sinon.spy(Zotero.HTTP, "request");
|
|
|
|
var e = yield getPromiseError(client.makeRequest("GET", baseURL + "error"));
|
|
|
|
assert.instanceOf(e, Zotero.HTTP.UnexpectedStatusException);
|
|
|
|
assert.isTrue(spy.calledTwice);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should obey Retry-After for 503", function* () {
|
|
|
|
var called = 0;
|
|
|
|
server.respond(function (req) {
|
|
|
|
if (req.method == "GET" && req.url == baseURL + "error") {
|
|
|
|
if (called < 1) {
|
|
|
|
req.respond(
|
|
|
|
503,
|
|
|
|
{
|
2018-06-12 19:13:00 +00:00
|
|
|
"Retry-After": "5"
|
2017-04-11 08:17:16 +00:00
|
|
|
},
|
|
|
|
""
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else if (called < 2) {
|
|
|
|
req.respond(
|
|
|
|
503,
|
|
|
|
{
|
2018-06-12 19:13:00 +00:00
|
|
|
"Retry-After": "10"
|
2017-04-11 08:17:16 +00:00
|
|
|
},
|
|
|
|
""
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
req.respond(
|
|
|
|
200,
|
|
|
|
{},
|
|
|
|
""
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
called++;
|
|
|
|
});
|
|
|
|
spy = sinon.spy(Zotero.HTTP, "request");
|
|
|
|
yield client.makeRequest("GET", baseURL + "error");
|
|
|
|
assert.isTrue(spy.calledThrice);
|
|
|
|
// DEBUG: Why are these slightly off?
|
|
|
|
assert.approximately(delayStub.args[0][0], 5 * 1000, 5);
|
|
|
|
assert.approximately(delayStub.args[1][0], 10 * 1000, 5);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("#_check429()", function () {
|
|
|
|
it("should retry on 429 error", function* () {
|
|
|
|
var called = 0;
|
|
|
|
server.respond(function (req) {
|
|
|
|
if (req.method == "GET" && req.url == baseURL + "error") {
|
|
|
|
if (called < 2) {
|
|
|
|
req.respond(
|
|
|
|
429,
|
|
|
|
{},
|
|
|
|
""
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
req.respond(
|
|
|
|
200,
|
|
|
|
{},
|
|
|
|
""
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
called++;
|
|
|
|
});
|
|
|
|
spy = sinon.spy(Zotero.HTTP, "request");
|
|
|
|
yield client.makeRequest("GET", baseURL + "error");
|
|
|
|
assert.isTrue(spy.calledThrice);
|
|
|
|
// DEBUG: Why are these slightly off?
|
|
|
|
assert.approximately(delayStub.args[0][0], 15 * 1000, 5);
|
|
|
|
assert.approximately(delayStub.args[1][0], 25 * 1000, 5);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2015-12-09 09:07:48 +00:00
|
|
|
})
|