syncAPIClientTest.js (5832B)
1 "use strict"; 2 3 describe("Zotero.Sync.APIClient", function () { 4 Components.utils.import("resource://zotero/config.js"); 5 6 var apiKey = Zotero.Utilities.randomString(24); 7 var baseURL = "http://local.zotero/"; 8 var server, client; 9 10 function setResponse(response) { 11 setHTTPResponse(server, baseURL, response, {}); 12 } 13 14 before(function () { 15 Zotero.HTTP.mock = sinon.FakeXMLHttpRequest; 16 }); 17 18 beforeEach(function () { 19 Components.utils.import("resource://zotero/concurrentCaller.js"); 20 var caller = new ConcurrentCaller(1); 21 caller.setLogger(msg => Zotero.debug(msg)); 22 caller.stopOnError = true; 23 caller.onError = function (e) { 24 Zotero.logError(e); 25 if (e.fatal) { 26 caller.stop(); 27 throw e; 28 } 29 }; 30 31 client = new Zotero.Sync.APIClient({ 32 baseURL, 33 apiVersion: ZOTERO_CONFIG.API_VERSION, 34 apiKey, 35 caller 36 }) 37 38 server = sinon.fakeServer.create(); 39 server.autoRespond = true; 40 }) 41 42 after(function () { 43 Zotero.HTTP.mock = null; 44 }) 45 46 describe("#_checkConnection()", function () { 47 it("should catch an interrupted connection", function* () { 48 setResponse({ 49 method: "GET", 50 url: "empty", 51 status: 0, 52 text: "" 53 }) 54 var e = yield getPromiseError(client.makeRequest("GET", baseURL + "empty")); 55 assert.ok(e); 56 assert.equal(e.message, Zotero.getString('sync.error.checkConnection')); 57 }) 58 }) 59 60 describe("#getGroups()", function () { 61 it("should automatically fetch multiple pages of results", function* () { 62 function groupJSON(groupID) { 63 return { 64 id: groupID, 65 version: 1, 66 data: { 67 id: groupID, 68 version: 1, 69 name: "Group " + groupID 70 } 71 }; 72 } 73 74 server.respond(function (req) { 75 if (req.method == "GET" && req.url.startsWith(baseURL + "users/1/groups")) { 76 // TODO: Use a real parser 77 let matches = req.url.match(/start=(\d+)/); 78 let start = matches ? parseInt(matches[1]) : null; 79 matches = req.url.match(/limit=(\d+)/); 80 let limit = matches ? parseInt(matches[1]) : null; 81 if (start === null && limit === null) { 82 req.respond( 83 200, 84 { 85 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"`, 86 "Total-Results": 2 87 }, 88 JSON.stringify([ 89 groupJSON(1), 90 groupJSON(2) 91 ]) 92 ); 93 } 94 else if (start == 2 && limit == 2) { 95 req.respond( 96 200, 97 { 98 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"`, 99 "Total-Results": 5 100 }, 101 JSON.stringify([ 102 groupJSON(3), 103 groupJSON(4) 104 ]) 105 ); 106 } 107 else if (start == 4 && limit == 2) { 108 req.respond( 109 200, 110 { 111 Link: `<${baseURL}users/1/groups?limit=2&start=4>; rel="last", <${baseURL}users/1/groups>; rel="alternate"`, 112 "Total-Results": 5 113 }, 114 JSON.stringify([ 115 groupJSON(5), 116 ]) 117 ); 118 } 119 } 120 }); 121 122 var results = yield client.getGroups(1); 123 assert.lengthOf(results, 5); 124 assert.sameMembers(results.map(o => o.id), [1, 2, 3, 4, 5]); 125 }); 126 }); 127 128 129 describe("Retries", function () { 130 var spy; 131 var delayStub; 132 133 before(function () { 134 delayStub = sinon.stub(Zotero.Promise, "delay").returns(Zotero.Promise.resolve()); 135 }); 136 137 beforeEach(function () { 138 client.failureDelayIntervals = [10, 20]; 139 client.failureDelayMax = 25; 140 client.rateDelayIntervals = [15, 25]; 141 }); 142 143 afterEach(function () { 144 if (spy) { 145 spy.restore(); 146 } 147 delayStub.resetHistory(); 148 }); 149 150 after(function () { 151 delayStub.restore(); 152 }); 153 154 155 describe("#makeRequest()", function () { 156 it("should retry on 500 error", function* () { 157 setResponse({ 158 method: "GET", 159 url: "error", 160 status: 500, 161 text: "" 162 }); 163 spy = sinon.spy(Zotero.HTTP, "request"); 164 var e = yield getPromiseError(client.makeRequest("GET", baseURL + "error")); 165 assert.instanceOf(e, Zotero.HTTP.UnexpectedStatusException); 166 assert.isTrue(spy.calledTwice); 167 }); 168 169 it("should obey Retry-After for 503", function* () { 170 var called = 0; 171 server.respond(function (req) { 172 if (req.method == "GET" && req.url == baseURL + "error") { 173 if (called < 1) { 174 req.respond( 175 503, 176 { 177 "Retry-After": "5" 178 }, 179 "" 180 ); 181 } 182 else if (called < 2) { 183 req.respond( 184 503, 185 { 186 "Retry-After": "10" 187 }, 188 "" 189 ); 190 } 191 else { 192 req.respond( 193 200, 194 {}, 195 "" 196 ); 197 } 198 } 199 called++; 200 }); 201 spy = sinon.spy(Zotero.HTTP, "request"); 202 yield client.makeRequest("GET", baseURL + "error"); 203 assert.isTrue(spy.calledThrice); 204 // DEBUG: Why are these slightly off? 205 assert.approximately(delayStub.args[0][0], 5 * 1000, 5); 206 assert.approximately(delayStub.args[1][0], 10 * 1000, 5); 207 }); 208 }); 209 210 211 describe("#_check429()", function () { 212 it("should retry on 429 error", function* () { 213 var called = 0; 214 server.respond(function (req) { 215 if (req.method == "GET" && req.url == baseURL + "error") { 216 if (called < 2) { 217 req.respond( 218 429, 219 {}, 220 "" 221 ); 222 } 223 else { 224 req.respond( 225 200, 226 {}, 227 "" 228 ); 229 } 230 } 231 called++; 232 }); 233 spy = sinon.spy(Zotero.HTTP, "request"); 234 yield client.makeRequest("GET", baseURL + "error"); 235 assert.isTrue(spy.calledThrice); 236 // DEBUG: Why are these slightly off? 237 assert.approximately(delayStub.args[0][0], 15 * 1000, 5); 238 assert.approximately(delayStub.args[1][0], 25 * 1000, 5); 239 }); 240 }); 241 }); 242 })