1 | require('./common');
|
2 | var crypto = require("crypto");
|
3 | var _ = require("underscore");
|
4 | var querystring = require("querystring");
|
5 | var fakes = require("./fakes");
|
6 | var LastFmRequest = fakes.LastFmRequest;
|
7 |
|
8 | (function() {
|
9 | var gently, lastfm;
|
10 | var options, expectations;
|
11 | var notExpected;
|
12 |
|
13 | describe("a lastfm request");
|
14 |
|
15 | before(function() {
|
16 | gently = new Gently();
|
17 | options = {};
|
18 | expectations = {
|
19 | pairs:[],
|
20 | handlers:[]
|
21 | };
|
22 | notExpected = {
|
23 | keys:[]
|
24 | }
|
25 | lastfm = new LastFmNode({
|
26 | api_key: "key",
|
27 | secret: "secret"
|
28 | });
|
29 | gently.expect(GENTLY_HIJACK.hijacked.http, "request", function(options, cb) {
|
30 | verifyCreateClient(options.port, options.host);
|
31 | var request = new fakes.ClientRequest();
|
32 | if (options.method == "POST") {
|
33 | gently.expect(request, "write", function(data) {
|
34 | verifyRequest(options.method, options.path, options.headers, data);
|
35 | });
|
36 | } else {
|
37 | verifyRequest(options.method, options.path, options.headers);
|
38 | }
|
39 | return request;
|
40 | });
|
41 | });
|
42 |
|
43 | after(function() {
|
44 | var request = doRequest();
|
45 | verifyHandlers(request);
|
46 | });
|
47 |
|
48 | function verifyCreateClient(port, host) {
|
49 | if (expectations.port) {
|
50 | assert.equal(expectations.port, port);
|
51 | }
|
52 | if (expectations.host) {
|
53 | assert.equal(expectations.host, host);
|
54 | }
|
55 | }
|
56 |
|
57 | function verifyRequest(method, url, header, data) {
|
58 | if (expectations.url) {
|
59 | assert.equal(expectations.url, url);
|
60 | }
|
61 | var pairs = querystring.parse(data || url.substr("/2.0?".length));
|
62 | _(Object.keys(expectations.pairs)).each(function(key) {
|
63 | assert.equal(expectations.pairs[key], pairs[key]);
|
64 | });
|
65 | if (expectations.signed || expectations.signatureHash) {
|
66 | assert.ok(pairs.api_sig);
|
67 | }
|
68 | else if (expectations.signed === false) {
|
69 | assert.ok(!pairs.api_sig);
|
70 | }
|
71 | if (expectations.signatureHash) {
|
72 | assert.equal(expectations.signatureHash, pairs.api_sig);
|
73 | }
|
74 | if (expectations.method) {
|
75 | assert.equal(expectations.method, method);
|
76 | }
|
77 | _(notExpected.keys).each(function(key) {
|
78 | assert.ok(!pairs[key]);
|
79 | });
|
80 | if (expectations.requestData) {
|
81 | assert.ok(data);
|
82 | }
|
83 | }
|
84 |
|
85 | function whenMethodIs(method) {
|
86 | options.method = method;
|
87 | }
|
88 |
|
89 | function andParamsAre(params) {
|
90 | options.params = params;
|
91 | }
|
92 |
|
93 | function expectHttpMethod(method) {
|
94 | expectations.method = method;
|
95 | }
|
96 |
|
97 | function expectDataPair(key, value) {
|
98 | expectations.pairs[key] = value;
|
99 | }
|
100 |
|
101 | function expectSignature() {
|
102 | expectations.signed = true;
|
103 | }
|
104 |
|
105 | function expectUrl(url) {
|
106 | expectations.url = url;
|
107 | }
|
108 |
|
109 | function expectSignatureHashOf(unhashed) {
|
110 | var expectedHash = crypto.createHash("md5").update(unhashed, "utf8").digest("hex");
|
111 | expectSignatureHashToBe(expectedHash);
|
112 | };
|
113 |
|
114 | this.expectSignatureHashToBe = function(hash) {
|
115 | expectations.signatureHash = hash;
|
116 | }
|
117 |
|
118 | function expectRequestOnPort(port) {
|
119 | expectations.port = port;
|
120 | }
|
121 |
|
122 | function expectRequestToHost(host) {
|
123 | expectations.host = host;
|
124 | }
|
125 |
|
126 | function expectHandlerFor(event) {
|
127 | expectations.handlers.push(event);
|
128 | }
|
129 |
|
130 | function expectRequestData() {
|
131 | expectations.requestData = true;
|
132 | }
|
133 |
|
134 | function doNotExpectDataKey(key) {
|
135 | notExpected.keys.push(key);
|
136 | }
|
137 |
|
138 | function doRequest() {
|
139 | return lastfm.request(options.method, options.params);
|
140 | }
|
141 |
|
142 | function verifyHandlers(request) {
|
143 | _(expectations.handlers).each(function(event) {
|
144 | var listeners = request.listeners(event);
|
145 | assert.equal(1, listeners.length, "No handler for event: " + event);
|
146 | });
|
147 | }
|
148 |
|
149 | it("default to port 80", function() {
|
150 | whenMethodIs("any.method");
|
151 | expectRequestOnPort(80);
|
152 | });
|
153 |
|
154 | it("makes request to audioscrobbler", function() {
|
155 | whenMethodIs("any.method");
|
156 | expectRequestToHost("ws.audioscrobbler.com");
|
157 | });
|
158 |
|
159 | it("always requests as json", function() {
|
160 | whenMethodIs("any.method");
|
161 | expectDataPair("format", "json");
|
162 | });
|
163 |
|
164 | it("always passes api_key", function() {
|
165 | whenMethodIs("any.method");
|
166 | expectDataPair("api_key", "key");
|
167 | });
|
168 |
|
169 | it("defaults to get request", function() {
|
170 | whenMethodIs("any.method");
|
171 | expectHttpMethod("GET");
|
172 | });
|
173 |
|
174 | it("calls the method specified", function() {
|
175 | whenMethodIs("user.getinfo");
|
176 | expectDataPair("method", "user.getinfo");
|
177 | });
|
178 |
|
179 | it("passes through parameters", function() {
|
180 | whenMethodIs("user.getinfo");
|
181 | andParamsAre({ user: "jammus" });
|
182 | expectDataPair("user", "jammus");
|
183 | });
|
184 |
|
185 | it("converts track object to separate parameters", function() {
|
186 | whenMethodIs("any.method");
|
187 | andParamsAre({
|
188 | track: {
|
189 | artist: { "#text": "The Mae Shi" },
|
190 | name: "Run To Your Grave",
|
191 | mbid: "1234567890"
|
192 | }
|
193 | });
|
194 | expectDataPair("artist", "The Mae Shi");
|
195 | expectDataPair("track", "Run To Your Grave");
|
196 | expectDataPair("mbid", "1234567890");
|
197 | });
|
198 |
|
199 | it("converts track object album details to separate parameters", function() {
|
200 | whenMethodIs("any.method");
|
201 | andParamsAre({
|
202 | track: {
|
203 | artist: { "#text": "The Mae Shi" },
|
204 | name: "Run To Your Grave",
|
205 | album: { "#text": "HLLLYH" }
|
206 | }
|
207 | });
|
208 | expectDataPair("album", "HLLLYH");
|
209 | });
|
210 |
|
211 | it("does not overwrite explicitly set album parameters", function() {
|
212 | whenMethodIs("any.method");
|
213 | andParamsAre({
|
214 | track: {
|
215 | artist: { "#text": "The Mae Shi" },
|
216 | name: "Run To Your Grave",
|
217 | album: { "#text": "HLLLYH" }
|
218 | },
|
219 | album: "Run To Your Grave"
|
220 | });
|
221 | expectDataPair("album", "Run To Your Grave");
|
222 | });
|
223 |
|
224 | it("doesn't include mbid if one isn't supplied", function() {
|
225 | whenMethodIs("any.method");
|
226 | andParamsAre({
|
227 | track: {
|
228 | artist: { "#text": "The Mae Shi" },
|
229 | name: "Run To Your Grave"
|
230 | }
|
231 | });
|
232 | expectDataPair("artist", "The Mae Shi");
|
233 | expectDataPair("track", "Run To Your Grave");
|
234 | doNotExpectDataKey("mbid");
|
235 | });
|
236 |
|
237 | it("does not pass through event handler parameters", function() {
|
238 | whenMethodIs("any.method");
|
239 | andParamsAre({ handlers: "handlers", error: "error", success: "success" });
|
240 | doNotExpectDataKey("handlers");
|
241 | doNotExpectDataKey("error");
|
242 | doNotExpectDataKey("success");
|
243 | });
|
244 |
|
245 | it("auth.getsession has signature", function() {
|
246 | whenMethodIs("auth.getsession");
|
247 | expectSignature();
|
248 | });
|
249 |
|
250 | it("attaches handlers to returned request", function() {
|
251 | whenMethodIs("any.method");
|
252 | andParamsAre({ handlers: {
|
253 | error: function() {console.log("errrors");},
|
254 | success: function() {},
|
255 | arbitrary: function() {},
|
256 | }});
|
257 | expectHandlerFor("error");
|
258 | expectHandlerFor("success");
|
259 | expectHandlerFor("arbitrary");
|
260 | });
|
261 |
|
262 | it("uses signed param to force signature", function() {
|
263 | whenMethodIs("any.method");
|
264 | andParamsAre({
|
265 | signed: true
|
266 | });
|
267 | expectSignature();
|
268 | });
|
269 |
|
270 | it("signature hashes api_key, method and secret", function() {
|
271 | whenMethodIs("auth.getsession");
|
272 | expectSignatureHashOf("api_keykeymethodauth.getsessionsecret");
|
273 | });
|
274 |
|
275 | it("signature includes other parameters", function() {
|
276 | whenMethodIs("auth.getsession");
|
277 | andParamsAre({ foo: "bar" });
|
278 | expectSignatureHashOf("api_keykeyfoobarmethodauth.getsessionsecret");
|
279 | });
|
280 |
|
281 | it("signature hashes all params alphabetically", function() {
|
282 | whenMethodIs("auth.getsession");
|
283 | andParamsAre({ foo : "bar", baz: "bash", flip : "flop" });
|
284 | expectSignatureHashOf("api_keykeybazbashflipflopfoobarmethodauth.getsessionsecret");
|
285 | });
|
286 |
|
287 | it("signature hash ignores format parameter", function() {
|
288 | whenMethodIs("auth.getsession");
|
289 | andParamsAre({ format: "json" });
|
290 | expectSignatureHashOf("api_keykeymethodauth.getsessionsecret");
|
291 | });
|
292 |
|
293 | it("signature hash ignores handlers parameter", function() {
|
294 | whenMethodIs("auth.getsession");
|
295 | andParamsAre({ handlers: "handlers" });
|
296 | expectSignatureHashOf("api_keykeymethodauth.getsessionsecret");
|
297 | });
|
298 |
|
299 | it("signature hash ignores write parameter", function() {
|
300 | whenMethodIs("auth.getsession");
|
301 | andParamsAre({ write: true });
|
302 | expectSignatureHashOf("api_keykeymethodauth.getsessionsecret");
|
303 | });
|
304 |
|
305 | it("signature hash ignores signed parameter", function() {
|
306 | whenMethodIs("any.method");
|
307 | andParamsAre({ signed: true });
|
308 | expectSignatureHashOf("api_keykeymethodany.methodsecret");
|
309 | });
|
310 |
|
311 | it("signature hash handles high characters as expected by last.fm (utf8)", function() {
|
312 | whenMethodIs("auth.getsession");
|
313 | andParamsAre({ track: "Tony’s Theme (Remastered)" });
|
314 | expectSignatureHashToBe("15f5159046bf1e76774b9dd46a4ed993");
|
315 | });
|
316 |
|
317 | it("signature hash treats undefined values as blank", function() {
|
318 | whenMethodIs("any.method");
|
319 | andParamsAre({ signed: true, track: 'Replicating Networks', artist: 'Rabbit Milk', albumArtist: undefined });
|
320 | expectSignatureHashOf("albumArtistapi_keykeyartistRabbit Milkmethodany.methodtrackReplicating Networkssecret");
|
321 | });
|
322 |
|
323 | it("signature hash treats null values as blank", function() {
|
324 | whenMethodIs("any.method");
|
325 | andParamsAre({ signed: true, track: 'Replicating Networks', artist: 'Rabbit Milk', albumArtist: null });
|
326 | expectSignatureHashOf("albumArtistapi_keykeyartistRabbit Milkmethodany.methodtrackReplicating Networkssecret");
|
327 | });
|
328 |
|
329 | it("write requests use post", function() {
|
330 | whenMethodIs("any.method");
|
331 | andParamsAre({ write: true });
|
332 | expectHttpMethod("POST");
|
333 | });
|
334 |
|
335 | it("write requests don't use get parameters", function() {
|
336 | whenMethodIs("any.method");
|
337 | andParamsAre({ write: true });
|
338 | expectUrl("/2.0");
|
339 | });
|
340 |
|
341 | it("write requests send data in request", function() {
|
342 | whenMethodIs("any.method");
|
343 | andParamsAre({
|
344 | write: true,
|
345 | foo: "bar"
|
346 | });
|
347 | expectRequestData();
|
348 | expectDataPair("foo", "bar");
|
349 | });
|
350 |
|
351 | it("write requests are always signed", function() {
|
352 | whenMethodIs("album.removeTag");
|
353 | andParamsAre({
|
354 | write: true
|
355 | });
|
356 | expectSignature();
|
357 | });
|
358 |
|
359 | _(["album.addTags", "album.removeTag", "album.share",
|
360 | "artist.addTags", "artist.removeTag", "artist.share", "artist.shout",
|
361 | "event.attend", "event.share", "event.shout",
|
362 | "library.addAlbum", "library.addArtist", "library.addTrack",
|
363 | "playlist.addTrack", "playlist.create",
|
364 | "radio.tune",
|
365 | "track.addTags", "track.ban", "track.love", "track.removeTag",
|
366 | "track.scrobble", "track.share", "track.unban", "track.unlove",
|
367 | "track.updateNowPlaying",
|
368 | "user.shout"]).each(function(method) {
|
369 | it(method + " is a write (post) request", function() {
|
370 | whenMethodIs(method);
|
371 | expectHttpMethod("POST");
|
372 | });
|
373 | });
|
374 |
|
375 | _(["auth.getMobileSession", "auth.getSession", "auth.getToken",
|
376 | "radio.getPlaylist"]).each(function(method) {
|
377 | it(method + " is signed", function() {
|
378 | whenMethodIs(method);
|
379 | expectSignature();
|
380 | });
|
381 | });
|
382 | })();
|