1 | require('./common.js');
|
2 | var LastFmSession = require('../lib/lastfm/lastfm-session');
|
3 | var LastFmUpdate = require('../lib/lastfm/lastfm-update');
|
4 | var fakes = require("./fakes");
|
5 |
|
6 | (function() {
|
7 | describe("new LastFmUpdate")
|
8 | it("can have success and error handlers specified in option at creation", function() {
|
9 | var gently = new Gently();
|
10 | var lastfm = new LastFmNode();
|
11 | var update = new LastFmUpdate(lastfm, "method", new LastFmSession(lastfm, "user", "key"), { handlers: {
|
12 | error: gently.expect(function error() {}),
|
13 | success: gently.expect(function success() {})
|
14 | }});
|
15 | update.emit("error");
|
16 | update.emit("success");
|
17 | });
|
18 | })();
|
19 |
|
20 | (function() {
|
21 | var request, returndata, options, session, method, gently, lastfm, authorisedSession, errorCode, errorMessage, update;
|
22 |
|
23 | function setupFixture() {
|
24 | request = new fakes.LastFmRequest();
|
25 | returndata;
|
26 | options = {};
|
27 | session = null;
|
28 | method = "";
|
29 | gently = new Gently();
|
30 | lastfm = new LastFmNode();
|
31 | authorisedSession = new LastFmSession(lastfm, "user", "key");
|
32 | errorCode = -1;
|
33 | errorMessage = null;
|
34 | update = undefined;
|
35 | }
|
36 |
|
37 | function whenRequestReturns(data) {
|
38 | errorCode = -1;
|
39 | errorMessage = null;
|
40 | returndata = JSON.parse(data);
|
41 | request = new fakes.LastFmRequest();
|
42 | gently.expect(lastfm, "request", function() {
|
43 | return request;
|
44 | });
|
45 | }
|
46 |
|
47 | function whenRequestThrowsError(code, message) {
|
48 | errorCode = code;
|
49 | errorMessage = message;
|
50 | request = new fakes.LastFmRequest();
|
51 | gently.expect(lastfm, "request", function() {
|
52 | return request;
|
53 | });
|
54 | }
|
55 |
|
56 | function andOptionsAre(setOptions) {
|
57 | options = setOptions;
|
58 | }
|
59 |
|
60 | function andMethodIs(setMethod) {
|
61 | method = setMethod;
|
62 | }
|
63 |
|
64 | function andSessionIs(setSession) {
|
65 | session = setSession;
|
66 | }
|
67 |
|
68 | function expectSuccess(assertions) {
|
69 | var checkSuccess = function(track) {
|
70 | if (assertions) {
|
71 | assertions(track);
|
72 | }
|
73 | };
|
74 | if (update) {
|
75 | update.on("success", checkSuccess);
|
76 | }
|
77 | else {
|
78 | options.handlers = options.handlers || {};
|
79 | options.handlers.success = checkSuccess;
|
80 | }
|
81 | doUpdate();
|
82 | }
|
83 |
|
84 | function expectError(errorCode, expectedError) {
|
85 | var checkError = function(error) {
|
86 | if (errorCode || expectedError) {
|
87 | assert.equal(expectedError, error.message);
|
88 | assert.equal(errorCode, error.error);
|
89 | }
|
90 | };
|
91 | if (update) {
|
92 | update.on("error", checkError);
|
93 | }
|
94 | else {
|
95 | options.handlers = options.handlers || {};
|
96 | options.handlers.error = gently.expect(checkError);
|
97 | }
|
98 | doUpdate();
|
99 | }
|
100 |
|
101 | function doNotExpectError() {
|
102 | options.handlers = options.handlers || {};
|
103 | options.handlers.error = function checkNoErrorThrown(error) {
|
104 | assert.ok(false);
|
105 | };
|
106 | doUpdate();
|
107 | }
|
108 |
|
109 | function expectRetry(callback) {
|
110 | callback = callback || function() { };
|
111 | if (update) {
|
112 | gently.expect(update, "emit", function(event, retry) {
|
113 | assert.equal(event, "retrying");
|
114 | callback(retry);
|
115 | });
|
116 | }
|
117 | else {
|
118 | options.handlers = options.handlers || { };
|
119 | options.handlers.retrying = gently.expect(callback);
|
120 | }
|
121 | doUpdate();
|
122 | }
|
123 |
|
124 | function doUpdate() {
|
125 | update = update || new LastFmUpdate(lastfm, method, session, options);
|
126 | if (errorMessage) {
|
127 | request.emit("error", { error: errorCode, message: errorMessage });
|
128 | }
|
129 | else {
|
130 | request.emit("success", returndata);
|
131 | }
|
132 | }
|
133 |
|
134 | describe("update requests")
|
135 | before(function() {
|
136 | setupFixture();
|
137 | });
|
138 |
|
139 | it("fail when the session is not authorised", function() {
|
140 | var session = new LastFmSession()
|
141 | , update = new LastFmUpdate(lastfm, "method", session, {
|
142 | handlers: {
|
143 | error: gently.expect(function(error) {
|
144 | assert.equal(error.error, 4);
|
145 | assert.equal(error.message, "Authentication failed");
|
146 | })
|
147 | }
|
148 | });
|
149 | });
|
150 |
|
151 | describe("nowPlaying updates")
|
152 | before(function() {
|
153 | setupFixture();
|
154 | });
|
155 |
|
156 | it("uses updateNowPlaying method", function() {
|
157 | gently.expect(lastfm, "request", function(method, params) {
|
158 | assert.equal("track.updateNowPlaying", method);
|
159 | return request;
|
160 | });
|
161 | new LastFmUpdate(lastfm, "nowplaying", authorisedSession, {
|
162 | track: FakeTracks.RunToYourGrave
|
163 | });
|
164 | });
|
165 |
|
166 | it("sends required parameters", function() {
|
167 | gently.expect(lastfm, "request", function(method, params) {
|
168 | assert.equal(FakeTracks.RunToYourGrave, params.track);
|
169 | assert.equal("key", params.sk);
|
170 | return request;
|
171 | });
|
172 | new LastFmUpdate(lastfm, "nowplaying", authorisedSession, {
|
173 | track: FakeTracks.RunToYourGrave
|
174 | });
|
175 | });
|
176 |
|
177 | it("emits success when updated", function() {
|
178 | whenRequestReturns(FakeData.UpdateNowPlayingSuccess);
|
179 | andMethodIs("nowplaying");
|
180 | andSessionIs(authorisedSession);
|
181 | andOptionsAre({
|
182 | track: FakeTracks.RunToYourGrave
|
183 | });
|
184 | expectSuccess(function(track) {
|
185 | assert.equal("Run To Your Grave", track.name);
|
186 | });
|
187 | });
|
188 |
|
189 | it("sends duration when supplied", function() {
|
190 | gently.expect(lastfm, "request", function(method, params) {
|
191 | assert.equal(232000, params.duration);
|
192 | return request;
|
193 | });
|
194 | new LastFmUpdate(lastfm, "nowplaying", authorisedSession, {
|
195 | track: FakeTracks.RunToYourGrave,
|
196 | duration: 232000
|
197 | });
|
198 | });
|
199 |
|
200 | it("can have artist and track string parameters supplied", function() {
|
201 | gently.expect(lastfm, "request", function(method, params) {
|
202 | assert.equal("The Mae Shi", params.artist);
|
203 | assert.equal("Run To Your Grave", params.track);
|
204 | assert.equal("key", params.sk);
|
205 | return request;
|
206 | });
|
207 | new LastFmUpdate(lastfm, "nowplaying", authorisedSession, {
|
208 | track: "Run To Your Grave",
|
209 | artist: "The Mae Shi"
|
210 | });
|
211 | });
|
212 |
|
213 | it("bubbles up errors", function() {
|
214 | var errorMessage = "Bubbled error";
|
215 | whenRequestThrowsError(100, errorMessage);
|
216 | andMethodIs("nowplaying");
|
217 | andSessionIs(authorisedSession);
|
218 | andOptionsAre({
|
219 | track: FakeTracks.RunToYourGrave,
|
220 | timestamp: 12345678
|
221 | });
|
222 | expectError(100, errorMessage);
|
223 | });
|
224 |
|
225 | describe("a scrobble request")
|
226 | before(function() {
|
227 | setupFixture();
|
228 | });
|
229 |
|
230 | it("emits error when no timestamp supplied", function() {
|
231 | new LastFmUpdate(lastfm, "scrobble", authorisedSession, {
|
232 | track: FakeTracks.RunToYourGrave,
|
233 | handlers: {
|
234 | error: gently.expect(function error(error) {
|
235 | assert.equal(6, error.error);
|
236 | assert.equal("Invalid parameters - Timestamp is required for scrobbling", error.message);
|
237 | })
|
238 | }
|
239 | });
|
240 | });
|
241 |
|
242 | it("uses scrobble method", function() {
|
243 | gently.expect(lastfm, "request", function(method, params) {
|
244 | assert.equal("track.scrobble", method);
|
245 | return request;
|
246 | });
|
247 | new LastFmUpdate(lastfm, "scrobble", authorisedSession, {
|
248 | track: FakeTracks.RunToYourGrave,
|
249 | timestamp: 12345678
|
250 | });
|
251 | });
|
252 |
|
253 | it("sends required parameters", function() {
|
254 | gently.expect(lastfm, "request", function(method, params) {
|
255 | assert.equal(FakeTracks.RunToYourGrave, params.track);
|
256 | assert.equal("key", params.sk);
|
257 | assert.equal(12345678, params.timestamp);
|
258 | return request;
|
259 | });
|
260 |
|
261 | new LastFmUpdate(lastfm, "scrobble", authorisedSession, {
|
262 | track: FakeTracks.RunToYourGrave,
|
263 | timestamp: 12345678
|
264 | });
|
265 | });
|
266 |
|
267 | it("emits success when updated", function() {
|
268 | whenRequestReturns(FakeData.ScrobbleSuccess);
|
269 | andMethodIs("scrobble");
|
270 | andSessionIs(authorisedSession);
|
271 | andOptionsAre({
|
272 | track: FakeTracks.RunToYourGrave,
|
273 | timestamp: 12345678
|
274 | });
|
275 | expectSuccess(function(track) {
|
276 | assert.equal("Run To Your Grave", track.name);
|
277 | });
|
278 | });
|
279 |
|
280 | it("bubbles up errors", function() {
|
281 | var errorMessage = "Bubbled error";
|
282 | whenRequestThrowsError(100, errorMessage);
|
283 | andMethodIs("scrobble");
|
284 | andSessionIs(authorisedSession);
|
285 | andOptionsAre({
|
286 | track: FakeTracks.RunToYourGrave,
|
287 | timestamp: 12345678
|
288 | });
|
289 | expectError(100, errorMessage);
|
290 | });
|
291 |
|
292 | it("can have artist and track string parameters supplied", function() {
|
293 | gently.expect(lastfm, "request", function(method, params) {
|
294 | assert.equal("The Mae Shi", params.artist);
|
295 | assert.equal("Run To Your Grave", params.track);
|
296 | assert.equal("key", params.sk);
|
297 | return request;
|
298 | });
|
299 | new LastFmUpdate(lastfm, "scrobble", authorisedSession, {
|
300 | track: "Run To Your Grave",
|
301 | artist: "The Mae Shi",
|
302 | timestamp: 12345678
|
303 | });
|
304 | });
|
305 |
|
306 | it("can have arbitrary parameters supplied", function() {
|
307 | gently.expect(lastfm, "request", function(method, params) {
|
308 | assert.equal("somevalue", params.arbitrary);
|
309 | return request;
|
310 | });
|
311 | new LastFmUpdate(lastfm, "scrobble", authorisedSession, {
|
312 | track: "Run To Your Grave",
|
313 | artist: "The Mae Shi",
|
314 | timestamp: 12345678,
|
315 | arbitrary: "somevalue"
|
316 | });
|
317 | });
|
318 |
|
319 | it("does not include handler parameters", function() {
|
320 | gently.expect(lastfm, "request", function(method, params) {
|
321 | assert.equal(undefined, params.handlers);
|
322 | assert.equal(undefined, params.error);
|
323 | assert.equal(undefined, params.success);
|
324 | return request;
|
325 | });
|
326 | new LastFmUpdate(lastfm, "scrobble", authorisedSession, {
|
327 | track: "Run To Your Grave",
|
328 | artist: "The Mae Shi",
|
329 | timestamp: 12345678,
|
330 | handlers: { success: function() { } },
|
331 | success: function() { },
|
332 | error: function() { }
|
333 | });
|
334 | });
|
335 |
|
336 | var tmpFn;
|
337 | describe("update retries")
|
338 | before(function() {
|
339 | tmpFn = LastFmUpdate.prototype.scheduleCallback;
|
340 | LastFmUpdate.prototype.scheduleCallback = function(callback, delay) { };
|
341 | setupFixture();
|
342 | andMethodIs("scrobble");
|
343 | andSessionIs(authorisedSession);
|
344 | andOptionsAre({
|
345 | track: FakeTracks.RunToYourGrave,
|
346 | timestamp: 12345678
|
347 | });
|
348 | });
|
349 |
|
350 | after(function() {
|
351 | LastFmUpdate.prototype.scheduleCallback = tmpFn;
|
352 | });
|
353 |
|
354 | it("a error which should trigger a retry does not bubble errors", function() {
|
355 | whenRequestThrowsError(11, "Service Offline");
|
356 | doNotExpectError();
|
357 | });
|
358 |
|
359 | it("service offline triggers a retry", function() {
|
360 | whenRequestThrowsError(11, "Service Offline");
|
361 | expectRetry();
|
362 | });
|
363 |
|
364 | it("rate limit exceeded triggers a retry", function() {
|
365 | whenRequestThrowsError(29, "Rate limit exceeded");
|
366 | expectRetry();
|
367 | });
|
368 |
|
369 | it("temporarily unavailable triggers a retry", function() {
|
370 | whenRequestThrowsError(16, "Temporarily unavailable");
|
371 | expectRetry();
|
372 | });
|
373 |
|
374 | it("nowplaying update never trigger retries", function() {
|
375 | whenRequestThrowsError(16, "Temporarily unavailable");
|
376 | andMethodIs("nowplaying");
|
377 | expectError();
|
378 | });
|
379 |
|
380 | it("first retry schedules a request after a 10 second delay", function() {
|
381 | whenRequestThrowsError(16, "Temporarily unavailable");
|
382 | LastFmUpdate.prototype.scheduleCallback = gently.expect(function testSchedule(callback, delay) {
|
383 | assert.equal(delay, 10000);
|
384 | });
|
385 | doUpdate();
|
386 | });
|
387 |
|
388 | function onNextRequests(callback, count) {
|
389 | count = count || 1;
|
390 | var gently = new Gently();
|
391 | LastFmUpdate.prototype.scheduleCallback = gently.expect(count, callback);
|
392 | doUpdate();
|
393 | }
|
394 |
|
395 | function lastRequest() {
|
396 | LastFmUpdate.prototype.scheduleCallback = function() { };
|
397 | }
|
398 |
|
399 | function whenNextRequestThrowsError(request, code, message) {
|
400 | whenRequestThrowsError(code, message);
|
401 | request();
|
402 | }
|
403 |
|
404 | function whenNextRequestReturns(request, data) {
|
405 | whenRequestReturns(data);
|
406 | request();
|
407 | }
|
408 |
|
409 | it("retry triggers another request", function() {
|
410 | whenRequestThrowsError(16, "Temporarily unavailable");
|
411 | onNextRequests(function(nextRequest) {
|
412 | lastRequest();
|
413 | whenNextRequestThrowsError(nextRequest, 16, "Temporarily unavailable");
|
414 | expectRetry();
|
415 | });
|
416 | });
|
417 |
|
418 | it("emits succes if retry is successful", function() {
|
419 | whenRequestThrowsError(16, "Temporarily unavailable");
|
420 | onNextRequests(function(nextRequest) {
|
421 | whenNextRequestReturns(nextRequest, FakeData.ScrobbleSuccess);
|
422 | expectSuccess(function(track) {
|
423 | assert.equal("Run To Your Grave", track.name);
|
424 | });
|
425 | });
|
426 | });
|
427 |
|
428 | it("emits succes if retry is non-retry error", function() {
|
429 | whenRequestThrowsError(16, "Temporarily unavailable");
|
430 | onNextRequests(function(nextRequest) {
|
431 | whenNextRequestThrowsError(nextRequest, 6, "Invalid parameter");
|
432 | expectError(6, "Invalid parameter");
|
433 | });
|
434 | });
|
435 |
|
436 | it("retrying events include error received and delay details", function() {
|
437 | whenRequestThrowsError(16, "Temporarily unavailable");
|
438 | expectRetry(function(retry) {
|
439 | assert.equal(retry.delay, 10000);
|
440 | assert.equal(retry.error, 16);
|
441 | assert.equal(retry.message, "Temporarily unavailable");
|
442 | });
|
443 | });
|
444 |
|
445 | var retrySchedule = [
|
446 | 10 * 1000,
|
447 | 30 * 1000,
|
448 | 60 * 1000,
|
449 | 5 * 60 * 1000,
|
450 | 15 * 60 * 1000,
|
451 | 30 * 60 * 1000,
|
452 | 30 * 60 * 1000,
|
453 | 30 * 60 * 1000
|
454 | ];
|
455 |
|
456 | it("follows a retry schedule on subsequent failures", function() {
|
457 | var count = 0;
|
458 | whenRequestThrowsError(16, "Temporarily unavailable");
|
459 | onNextRequests(function(nextRequest, delay) {
|
460 | var expectedDelay = retrySchedule[count++];
|
461 | assert.equal(delay, expectedDelay);
|
462 | if (count >= retrySchedule.length) {
|
463 | lastRequest();
|
464 | }
|
465 | whenNextRequestThrowsError(nextRequest, 16, "Temporarily unavailable");
|
466 | expectRetry();
|
467 | }, retrySchedule.length);
|
468 | });
|
469 |
|
470 | it("includes delay in subsequent retry events", function() {
|
471 | var count = 0;
|
472 | whenRequestThrowsError(16, "Temporarily unavailable");
|
473 | onNextRequests(function(nextRequest, delay) {
|
474 | count++;
|
475 | if (count >= retrySchedule.length) {
|
476 | lastRequest();
|
477 | }
|
478 | var expectedDelay = retrySchedule[Math.min(count, retrySchedule.length - 1)];
|
479 | whenNextRequestThrowsError(nextRequest, 16, "Temporarily unavailable");
|
480 | expectRetry(function(retry) {
|
481 | assert.equal(retry.delay, expectedDelay);
|
482 | });
|
483 | }, retrySchedule.length);
|
484 | });
|
485 | })();
|