www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

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 })