particle-api-js 10.3.0 → 10.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +23 -21
- package/CHANGELOG.md +8 -0
- package/EventStream-e2e-browser.html +7 -7
- package/EventStream-e2e-node.js +14 -14
- package/dist/particle.min.js +1 -1
- package/dist/particle.min.js.map +1 -1
- package/docs/api.md +3448 -875
- package/karma.conf.js +59 -59
- package/package.json +1 -1
- package/src/Agent.js +387 -363
- package/src/Client.js +162 -162
- package/src/Defaults.js +5 -5
- package/src/EventStream.js +254 -253
- package/src/Library.js +21 -21
- package/src/Particle.js +2685 -2649
- package/test/.eslintrc +5 -5
- package/test/Agent.integration.js +14 -14
- package/test/Agent.spec.js +495 -495
- package/test/Client.spec.js +203 -203
- package/test/Defaults.spec.js +20 -20
- package/test/EventStream.spec.js +231 -231
- package/test/FakeAgent.js +18 -18
- package/test/Library.spec.js +29 -29
- package/test/Particle.integration.js +29 -29
- package/test/Particle.spec.js +3127 -3127
- package/test/fixtures/index.js +7 -7
- package/test/support/FixtureHttpServer.js +16 -16
- package/test/test-setup.js +3 -3
- package/tsconfig.json +3 -1
- package/webpack.config.js +39 -39
package/test/Agent.spec.js
CHANGED
|
@@ -2,499 +2,499 @@ const { sinon, expect } = require('./test-setup');
|
|
|
2
2
|
const Agent = require('../src/Agent.js');
|
|
3
3
|
|
|
4
4
|
describe('Agent', () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
sinon.restore();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('constructor', () => {
|
|
10
|
+
it('calls setBaseUrl', () => {
|
|
11
|
+
const baseUrl = 'https://foo.com';
|
|
12
|
+
sinon.stub(Agent.prototype, 'setBaseUrl');
|
|
13
|
+
const agent = new Agent(baseUrl);
|
|
14
|
+
expect(agent.setBaseUrl).to.have.property('callCount', 1);
|
|
15
|
+
expect(agent.setBaseUrl.firstCall.args).to.have.lengthOf(1);
|
|
16
|
+
expect(agent.setBaseUrl.firstCall.args[0]).to.eql(baseUrl);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('sanitize files', () => {
|
|
21
|
+
it('can call sanitize will falsy value', () => {
|
|
22
|
+
const agent = new Agent();
|
|
23
|
+
expect(agent._sanitizeFiles(undefined)).to.be.undefined;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('sanitizes file names', () => {
|
|
27
|
+
const agent = new Agent();
|
|
28
|
+
const original = { one: 'content1', two: 'content2' };
|
|
29
|
+
const actual = agent._sanitizeFiles(original);
|
|
30
|
+
expect(actual).to.eql({
|
|
31
|
+
'file': {
|
|
32
|
+
'data': 'content1',
|
|
33
|
+
'path': 'one'
|
|
34
|
+
},
|
|
35
|
+
'file2': {
|
|
36
|
+
'data': 'content2',
|
|
37
|
+
'path': 'two'
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('resource operations', () => {
|
|
44
|
+
let uri, method, auth, headers, query, data, context, agent;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
uri = 'http://example.com/v1';
|
|
48
|
+
method = 'get';
|
|
49
|
+
auth = 'fake-token';
|
|
50
|
+
headers = { 'X-FOO': 'foo', 'X-BAR': 'bar' };
|
|
51
|
+
query = 'foo=1&bar=2';
|
|
52
|
+
data = { foo: true, bar: false };
|
|
53
|
+
context = { blah: {} };
|
|
54
|
+
agent = new Agent();
|
|
55
|
+
agent.request = sinon.stub();
|
|
56
|
+
agent.request.resolves('fake-response');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('can GET a resource', () => {
|
|
60
|
+
return agent.get({ uri, auth, headers, query, context }).then(() => {
|
|
61
|
+
expect(agent.request).to.be.calledWith({ uri, method, auth, headers, query, context });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('can HEAD a resource', () => {
|
|
66
|
+
method = 'head';
|
|
67
|
+
return agent.head({ uri, auth, headers, query, context }).then(() => {
|
|
68
|
+
expect(agent.request).to.be.calledWith({ uri, method, auth, headers, query, context });
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('can POST a resource', () => {
|
|
73
|
+
method = 'post';
|
|
74
|
+
return agent.post({ uri, auth, headers, data, context }).then(() => {
|
|
75
|
+
expect(agent.request).to.be.calledWith({ uri, method, auth, headers, data, context });
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('can PUT a resource', () => {
|
|
80
|
+
method = 'put';
|
|
81
|
+
return agent.put({ uri, auth, headers, data, context }).then(() => {
|
|
82
|
+
expect(agent.request).to.be.calledWith({ uri, method, auth, headers, data, context });
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('can DELETE a resource', () => {
|
|
87
|
+
method = 'delete';
|
|
88
|
+
return agent.delete({ uri, auth, headers, data, context }).then(() => {
|
|
89
|
+
expect(agent.request).to.be.calledWith({ uri, method, auth, headers, data, context });
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('authorize', () => {
|
|
95
|
+
let agent;
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
agent = new Agent();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('authorize no auth is unchanged', () => {
|
|
102
|
+
expect(agent._getAuthorizationHeader(undefined)).to.eql({});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('authorize with bearer', () => {
|
|
106
|
+
const auth = '123';
|
|
107
|
+
const bearer = 'Bearer 123';
|
|
108
|
+
const headers = agent._getAuthorizationHeader(auth);
|
|
109
|
+
expect(headers).to.eql({ Authorization: bearer });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (typeof window !== 'undefined') {
|
|
113
|
+
it('supports auth with user/pass in browsers', () => {
|
|
114
|
+
const auth = {
|
|
115
|
+
username: 'test@particle.io',
|
|
116
|
+
password: 'super_secret'
|
|
117
|
+
};
|
|
118
|
+
const basic = 'Basic dGVzdEBwYXJ0aWNsZS5pbzpzdXBlcl9zZWNyZXQ=';
|
|
119
|
+
const headers = agent._getAuthorizationHeader(auth);
|
|
120
|
+
expect(headers).to.eql({ Authorization: basic });
|
|
121
|
+
});
|
|
122
|
+
} else {
|
|
123
|
+
it('supports auth with user/pass in node', () => {
|
|
124
|
+
const auth = {
|
|
125
|
+
username: 'test@particle.io',
|
|
126
|
+
password: 'super_secret'
|
|
127
|
+
};
|
|
128
|
+
const basic = 'Basic dGVzdEBwYXJ0aWNsZS5pbzpzdXBlcl9zZWNyZXQ=';
|
|
129
|
+
const headers = agent._getAuthorizationHeader(auth);
|
|
130
|
+
expect(headers).to.eql({ Authorization: basic });
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('request', () => {
|
|
136
|
+
let agent;
|
|
137
|
+
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
agent = new Agent();
|
|
140
|
+
agent._promiseResponse = sinon.stub();
|
|
141
|
+
agent._promiseResponse.resolves('fake-response');
|
|
142
|
+
agent._buildRequest = sinon.stub();
|
|
143
|
+
agent._sanitizeFiles = sinon.stub();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('sanitizes files from a request', () => {
|
|
147
|
+
const sanitizedFiles = { a:'a' };
|
|
148
|
+
const files = {};
|
|
149
|
+
const form = {};
|
|
150
|
+
agent._sanitizeFiles.returns(sanitizedFiles);
|
|
151
|
+
|
|
152
|
+
return agent.request({ uri: 'abc', method: 'post', data: '123', query: 'all', form, files })
|
|
153
|
+
.then((res) => {
|
|
154
|
+
expect(res).to.be.equal('fake-response');
|
|
155
|
+
expect(agent._sanitizeFiles).calledOnce.calledWith(sinon.match.same(files));
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('uses default arguments for request', () => {
|
|
160
|
+
const args = ['abc', { args: '123' }];
|
|
161
|
+
agent._buildRequest.returns(args);
|
|
162
|
+
return agent.request({ uri: 'abc', method:'post' })
|
|
163
|
+
.then((res) => {
|
|
164
|
+
expect(res).to.be.equal('fake-response');
|
|
165
|
+
expect(agent._promiseResponse).calledOnce.calledWith(args);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('builds and sends the request', () => {
|
|
170
|
+
const agent = new Agent();
|
|
171
|
+
const options = {
|
|
172
|
+
uri: 'http://example.com/v1',
|
|
173
|
+
method: 'get',
|
|
174
|
+
auth: 'fake-token',
|
|
175
|
+
headers: { 'X-FOO': 'foo', 'X-BAR': 'bar' },
|
|
176
|
+
query: 'foo=1&bar=2',
|
|
177
|
+
data: { foo: true, bar: false },
|
|
178
|
+
files: undefined,
|
|
179
|
+
form: undefined,
|
|
180
|
+
context
|
|
181
|
+
};
|
|
182
|
+
agent._buildRequest = sinon.stub();
|
|
183
|
+
agent._buildRequest.returns('fake-request');
|
|
184
|
+
agent._promiseResponse = sinon.stub();
|
|
185
|
+
agent._promiseResponse.resolves('fake-response');
|
|
186
|
+
|
|
187
|
+
return agent.request(options).then((res) => {
|
|
188
|
+
expect(res).to.be.equal('fake-response');
|
|
189
|
+
expect(agent._buildRequest).calledOnce;
|
|
190
|
+
expect(agent._buildRequest).calledWith(options);
|
|
191
|
+
expect(agent._promiseResponse).calledOnce;
|
|
192
|
+
expect(agent._promiseResponse).calledWith('fake-request');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('builds a promise to call _promiseResponse', () => {
|
|
197
|
+
const agent = new Agent();
|
|
198
|
+
const req = sinon.stub();
|
|
199
|
+
const response = {
|
|
200
|
+
ok: true,
|
|
201
|
+
status: 200,
|
|
202
|
+
json: () => Promise.resolve('response')
|
|
203
|
+
};
|
|
204
|
+
req.resolves(response);
|
|
205
|
+
const promise = agent._promiseResponse([], false, req);
|
|
206
|
+
expect(promise).has.property('then');
|
|
207
|
+
return promise.then((resp) => {
|
|
208
|
+
expect(resp).to.be.eql({
|
|
209
|
+
body: 'response',
|
|
210
|
+
statusCode: 200
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('can handle error responses', () => {
|
|
216
|
+
const failResponseData = [
|
|
217
|
+
{
|
|
218
|
+
name: 'error text includes body error description',
|
|
219
|
+
response: {
|
|
220
|
+
status: 404,
|
|
221
|
+
statusText: 'file not found',
|
|
222
|
+
text: () => Promise.resolve('{"error_description": "file not found"}')
|
|
223
|
+
},
|
|
224
|
+
errorDescription: 'HTTP error 404 from 123.url - file not found'
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'error text with no body description',
|
|
228
|
+
response: {
|
|
229
|
+
status: 404,
|
|
230
|
+
text: () => Promise.resolve(''),
|
|
231
|
+
},
|
|
232
|
+
errorDescription: 'HTTP error 404 from 123.url'
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'error text with no status',
|
|
236
|
+
response: {},
|
|
237
|
+
errorDescription: 'Network error from 123.url'
|
|
238
|
+
}
|
|
239
|
+
];
|
|
240
|
+
const agent = new Agent();
|
|
241
|
+
const req = sinon.stub();
|
|
242
|
+
const requests = failResponseData.map((failData) => {
|
|
243
|
+
const response = Object.assign({
|
|
244
|
+
ok: false
|
|
245
|
+
}, failData.response);
|
|
246
|
+
req.resolves(response);
|
|
247
|
+
const promise = agent._promiseResponse(['123.url'] , false, req);
|
|
248
|
+
return promise.catch((resp) => {
|
|
249
|
+
expect(resp.statusCode).to.eql(failData.response.status);
|
|
250
|
+
expect(resp.errorDescription).to.eql(failData.errorDescription);
|
|
251
|
+
expect(resp.shortErrorDescription).to.eql(failData.response.statusText);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
return Promise.all(requests);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe('build request', () => {
|
|
259
|
+
let agent;
|
|
260
|
+
|
|
261
|
+
beforeEach(() => {
|
|
262
|
+
agent = new Agent('abc');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('uses a baseURL if provided', () => {
|
|
266
|
+
const [uri] = agent._buildRequest({ uri: '/uri', method: 'get' });
|
|
267
|
+
expect(uri).to.equal('abc/uri');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('uses the provided uri if no baseURL is provided', () => {
|
|
271
|
+
agent.setBaseUrl(undefined);
|
|
272
|
+
const [uri] = agent._buildRequest({ uri: 'uri', method: 'get' });
|
|
273
|
+
expect(uri).to.equal('uri');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('generates context headers when one is provided', () => {
|
|
277
|
+
const context = { tool: { name: 'spanner' } };
|
|
278
|
+
const [, opts] = agent._buildRequest({ uri: '/uri', method: 'get', context });
|
|
279
|
+
expect(opts.headers).to.have.property('X-Particle-Tool', 'spanner');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('generates auth headers when an auth token is provided', () => {
|
|
283
|
+
const auth = 'abcd-1235';
|
|
284
|
+
const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get', auth });
|
|
285
|
+
expect(opts.headers).to.have.property('Authorization', `Bearer ${auth}`);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('adds new query params with the given query object', () => {
|
|
289
|
+
const query = { foo: 1, bar: 2 };
|
|
290
|
+
const [uri] = agent._buildRequest({ uri: '/uri', method: 'get', query });
|
|
291
|
+
expect(uri).to.equal('abc/uri?foo=1&bar=2');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('adds query params without colliding with existing ones', () => {
|
|
295
|
+
const query = { foo: 1, bar: 2 };
|
|
296
|
+
const [uri] = agent._buildRequest({ uri: '/uri?test=true', method: 'get', query });
|
|
297
|
+
expect(uri).to.equal('abc/uri?test=true&foo=1&bar=2');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('adds the provided data as a JSON request body', () => {
|
|
301
|
+
const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get', data: { a: 'abcd' } });
|
|
302
|
+
expect(opts.body).to.eql('{"a":"abcd"}');
|
|
303
|
+
expect(opts.headers).to.have.property('Content-Type', 'application/json');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should setup form send when form data is given', () => {
|
|
307
|
+
const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get', form: { a: 'abcd' } });
|
|
308
|
+
expect(opts.body).to.eql('a=abcd');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should attach files', () => {
|
|
312
|
+
const files = {
|
|
313
|
+
file: { data: makeFile('filedata'), path: 'filepath' },
|
|
314
|
+
file2: { data: makeFile('file2data'), path: 'file2path' }
|
|
315
|
+
};
|
|
316
|
+
const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get', files });
|
|
317
|
+
expect(opts.body.toString()).to.equal('[object FormData]');
|
|
318
|
+
expect(extractFilename(opts.body, 'file', 0)).to.eql('filepath');
|
|
319
|
+
expect(extractFilename(opts.body, 'file2', 3)).to.eql('file2path');
|
|
320
|
+
expect(opts.headers).to.not.have.property('Content-Type');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should attach files and form data', () => {
|
|
324
|
+
const files = {
|
|
325
|
+
file: { data: makeFile('filedata'), path: 'filepath' },
|
|
326
|
+
file2: { data: makeFile('file2data'), path: 'file2path' }
|
|
327
|
+
};
|
|
328
|
+
const form = { form1: 'value1', form2: 'value2' };
|
|
329
|
+
const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get', files, form });
|
|
330
|
+
expect(opts.body.toString()).to.equal('[object FormData]');
|
|
331
|
+
expect(extractFilename(opts.body, 'file', 0)).to.eql('filepath');
|
|
332
|
+
expect(extractFilename(opts.body, 'file2', 3)).to.eql('file2path');
|
|
333
|
+
expect(extractFormName(opts.body, 'form1', 6, true)).to.eql('value1');
|
|
334
|
+
expect(extractFormName(opts.body, 'form2', 9, true)).to.eql('value2');
|
|
335
|
+
expect(opts.headers).to.not.have.property('Content-Type');
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should handle nested dirs', () => {
|
|
339
|
+
const files = {
|
|
340
|
+
file: { data: makeFile('filedata'), path: 'filepath.ino' },
|
|
341
|
+
file2: { data: makeFile('file2data'), path: 'dir/file2path.cpp' }
|
|
342
|
+
};
|
|
343
|
+
const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get', files });
|
|
344
|
+
expect(extractFilename(opts.body, 'file', 0)).to.eql('filepath.ino');
|
|
345
|
+
expect(extractFilename(opts.body, 'file2', 3)).to.eql('dir/file2path.cpp');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('sets the user agent to particle-api-js', () => {
|
|
349
|
+
const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get' });
|
|
350
|
+
expect(opts.headers).to.have.property('User-Agent').that.match(/^particle-api-js/);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
if (!inBrowser()){
|
|
354
|
+
it('should handle Windows nested dirs', () => {
|
|
355
|
+
const files = {
|
|
356
|
+
file: { data: makeFile('filedata'), path: 'dir\\windowsfilepath.cpp' }
|
|
357
|
+
};
|
|
358
|
+
const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get', files });
|
|
359
|
+
expect(extractFilename(opts.body, 'file', 0)).to.eql('dir/windowsfilepath.cpp');
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function inBrowser(){
|
|
364
|
+
return typeof window !== 'undefined';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function makeFile(data){
|
|
368
|
+
if (inBrowser()){
|
|
369
|
+
return new Blob([data]);
|
|
370
|
+
} else {
|
|
371
|
+
return data;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function extractFilename(formData, fieldName, fieldIndex){
|
|
376
|
+
if (inBrowser()){
|
|
377
|
+
return formData.get(fieldName).name;
|
|
378
|
+
} else {
|
|
379
|
+
return /filename="([^"]*)"/.exec(formData._streams[fieldIndex])[1];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function extractFormName(formData, fieldName, fieldIndex){
|
|
384
|
+
if (inBrowser()){
|
|
385
|
+
return formData.get(fieldName);
|
|
386
|
+
} else {
|
|
387
|
+
return formData._streams[fieldIndex + 1];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('context', () => {
|
|
393
|
+
let agent;
|
|
394
|
+
|
|
395
|
+
beforeEach(() => {
|
|
396
|
+
agent = new Agent();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('_nameAtVersion', () => {
|
|
400
|
+
it('returns empty string when no name given', () => {
|
|
401
|
+
expect(agent._nameAtVersion('', '1.2.3')).to.eql('');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('returns just the name when no version given', () => {
|
|
405
|
+
expect(agent._nameAtVersion('fred')).to.eql('fred');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('returns name@version when both are given', () => {
|
|
409
|
+
expect(agent._nameAtVersion('fred', '1.2.3')).to.eql('fred@1.2.3');
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe('_getContextHeaders', () => {
|
|
414
|
+
it('generates the tool context when defined', () => {
|
|
415
|
+
const context = { tool: { name: 'spanner' } };
|
|
416
|
+
const subject = agent._getContextHeaders(context);
|
|
417
|
+
expect(subject).to.have.property('X-Particle-Tool', 'spanner');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('does not add the tool context header when not defined',() => {
|
|
421
|
+
const context = { tool: { name2: 'spanner' } };
|
|
422
|
+
const subject = agent._getContextHeaders(context);
|
|
423
|
+
expect(subject).to.not.have.property('X-Particle-Tool');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('generates the project context header when defined',() => {
|
|
427
|
+
const context = { project: { name: 'blinky' } };
|
|
428
|
+
const subject = agent._getContextHeaders(context);
|
|
429
|
+
expect(subject).to.have.property('X-Particle-Project', 'blinky');
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('does not generate the project context header when not defined',() => {
|
|
433
|
+
const context = { project: { name2: 'blinky' } };
|
|
434
|
+
const subject = agent._getContextHeaders(context);
|
|
435
|
+
expect(subject).to.not.have.property('X-Particle-Project');
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
describe('_getToolContext', () => {
|
|
440
|
+
it('does not add a header when the tool name is not defined', () => {
|
|
441
|
+
const tool = { noname: 'cli' };
|
|
442
|
+
const subject = agent._getToolContext(tool);
|
|
443
|
+
expect(subject).to.eql({});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('adds a header when the tool is defined', () => {
|
|
447
|
+
const tool = { name: 'cli' };
|
|
448
|
+
const subject = agent._getToolContext(tool);
|
|
449
|
+
expect(subject).to.eql({ 'X-Particle-Tool': 'cli' });
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('adds a header when the tool and components is defined', () => {
|
|
453
|
+
const tool = {
|
|
454
|
+
name: 'cli',
|
|
455
|
+
version: '1.2.3',
|
|
456
|
+
components: [
|
|
457
|
+
{ name: 'bar', version: 'a.b.c' },
|
|
458
|
+
{ name: 'foo', version: '0.0.1' }
|
|
459
|
+
]
|
|
460
|
+
};
|
|
461
|
+
const subject = agent._getToolContext(tool);
|
|
462
|
+
expect(subject).to.eql({ 'X-Particle-Tool': 'cli@1.2.3, bar@a.b.c, foo@0.0.1' });
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
describe('_addProjectContext', () => {
|
|
467
|
+
it('adds a header when the project is defined', () => {
|
|
468
|
+
const project = { name: 'blinky' };
|
|
469
|
+
const subject = agent._getProjectContext(project);
|
|
470
|
+
expect(subject).to.have.property('X-Particle-Project', 'blinky');
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('does not set the header when the project has no name', () => {
|
|
474
|
+
const project = { noname: 'blinky' };
|
|
475
|
+
const subject = agent._getProjectContext(project);
|
|
476
|
+
expect(subject).to.not.have.property('X-Particle-Project');
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
describe('_buildSemicolonSeparatedProperties', () => {
|
|
481
|
+
const obj = { name: 'fred', color: 'pink' };
|
|
482
|
+
|
|
483
|
+
it('returns empty string when no default property', () => {
|
|
484
|
+
expect(agent._buildSemicolonSeparatedProperties(obj)).to.be.eql('');
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('returns empty string when default property does not exist', () => {
|
|
488
|
+
expect(agent._buildSemicolonSeparatedProperties(obj, 'job')).to.be.eql('');
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('returns the default property only', () => {
|
|
492
|
+
expect(agent._buildSemicolonSeparatedProperties({ name:'fred' }, 'name')).eql('fred');
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('returns the default property plus additional properties', () => {
|
|
496
|
+
expect(agent._buildSemicolonSeparatedProperties(obj, 'name')).eql('fred; color=pink');
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
500
|
});
|