particle-api-js 9.4.1 → 10.1.0

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.
Files changed (51) hide show
  1. package/.circleci/config.yml +7 -5
  2. package/CHANGELOG.md +11 -0
  3. package/{test/EventStream-e2e-browser.html → EventStream-e2e-browser.html} +0 -1
  4. package/{test/EventStream-e2e-node.js → EventStream-e2e-node.js} +2 -3
  5. package/README.md +2 -2
  6. package/RELEASE.md +1 -1
  7. package/dist/particle.min.js +1 -399
  8. package/dist/particle.min.js.map +1 -1
  9. package/docs/api.md +5223 -115
  10. package/fs.js +2 -0
  11. package/karma.conf.js +18 -6
  12. package/package.json +23 -26
  13. package/src/Agent.js +407 -0
  14. package/src/Client.js +170 -0
  15. package/src/Defaults.js +7 -0
  16. package/src/EventStream.js +263 -0
  17. package/src/Library.js +33 -0
  18. package/src/Particle.js +2644 -0
  19. package/test/Agent.integration.js +5 -4
  20. package/test/Agent.spec.js +174 -291
  21. package/test/Client.spec.js +7 -7
  22. package/test/Defaults.spec.js +2 -2
  23. package/test/EventStream.spec.js +6 -4
  24. package/test/FakeAgent.js +2 -2
  25. package/test/Library.spec.js +2 -2
  26. package/test/Particle.integration.js +7 -7
  27. package/test/Particle.spec.js +332 -18
  28. package/test/fixtures/index.js +4 -18
  29. package/test/support/FixtureHttpServer.js +5 -3
  30. package/test/test-setup.js +5 -5
  31. package/tsconfig.json +14 -0
  32. package/webpack.config.js +45 -0
  33. package/.babelrc +0 -4
  34. package/lib/Agent.js +0 -516
  35. package/lib/Agent.js.map +0 -1
  36. package/lib/Client.js +0 -312
  37. package/lib/Client.js.map +0 -1
  38. package/lib/Defaults.js +0 -14
  39. package/lib/Defaults.js.map +0 -1
  40. package/lib/EventStream.js +0 -335
  41. package/lib/EventStream.js.map +0 -1
  42. package/lib/Library.js +0 -67
  43. package/lib/Library.js.map +0 -1
  44. package/lib/Particle.js +0 -3248
  45. package/lib/Particle.js.map +0 -1
  46. package/lib/superagent-binary-parser.js +0 -20
  47. package/lib/superagent-binary-parser.js.map +0 -1
  48. package/test/Client.integration.js +0 -69
  49. package/test/fixtures/tarball.tar.gz +0 -0
  50. package/test/fixtures/test-library-publish-0.0.1.tar.gz +0 -0
  51. package/test/fixtures/test-library-publish-0.0.2.tar.gz +0 -0
@@ -21,13 +21,14 @@
21
21
  * Tests for real the Agent class using an external service.
22
22
  */
23
23
 
24
- import { expect } from './test-setup';
25
- import Agent from '../src/Agent';
26
-
24
+ const { expect } = require('./test-setup');
25
+ const Agent = require('../src/Agent');
27
26
 
28
27
  describe('Agent', () => {
29
28
  if (!process.env.SKIP_AGENT_TEST){
30
- it('can fetch a webpage', () => {
29
+ it('can fetch a webpage', function cb() {
30
+ this.retries(5);
31
+ this.timeout(6000);
31
32
  const agent = new Agent();
32
33
  const query = { a: '1', b: '2' };
33
34
  const result = agent.get({ uri: 'http://httpbin.org/get', query });
@@ -1,5 +1,5 @@
1
- import { sinon, expect } from './test-setup';
2
- import Agent from '../src/Agent.js';
1
+ const { sinon, expect } = require('./test-setup');
2
+ const Agent = require('../src/Agent.js');
3
3
 
4
4
  describe('Agent', () => {
5
5
  beforeEach(() => {
@@ -15,14 +15,6 @@ describe('Agent', () => {
15
15
  expect(agent.setBaseUrl.firstCall.args).to.have.lengthOf(1);
16
16
  expect(agent.setBaseUrl.firstCall.args[0]).to.eql(baseUrl);
17
17
  });
18
-
19
- it('creates a prefix function instance property from the supplied baseUrl (via setBaseUrl)', () => {
20
- const agent = new Agent();
21
- expect(agent.prefix).to.be.a('Function');
22
- // Note: validating specific .prefix function behavior seems hard without proxyquire
23
- // and this test seems sufficient
24
- // and redundant with the 'build request uses prefix if provided' test below
25
- });
26
18
  });
27
19
 
28
20
  describe('sanitize files', () => {
@@ -107,25 +99,37 @@ describe('Agent', () => {
107
99
  });
108
100
 
109
101
  it('authorize no auth is unchanged', () => {
110
- expect(agent._authorizationHeader(undefined)).to.be.undefined;
111
- });
112
-
113
- it('authorize with credentials', () => {
114
- const authfn = sinon.spy();
115
- const req = { auth: authfn };
116
- const auth = { username: 'me', password: 'pwd' };
117
- expect(agent._authorizationHeader(req, auth)).to.be.equal(req);
118
- expect(authfn).to.have.been.calledWith('me', 'pwd');
102
+ expect(agent._getAuthorizationHeader(undefined)).to.eql({});
119
103
  });
120
104
 
121
105
  it('authorize with bearer', () => {
122
106
  const auth = '123';
123
107
  const bearer = 'Bearer 123';
124
- const setfn = sinon.spy();
125
- const req = { set: setfn };
126
- expect(agent._authorizationHeader(req, auth)).to.be.equal(req);
127
- expect(setfn).to.have.been.calledWith({ Authorization: bearer });
108
+ const headers = agent._getAuthorizationHeader(auth);
109
+ expect(headers).to.eql({ Authorization: bearer });
128
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
+ }
129
133
  });
130
134
 
131
135
  describe('request', () => {
@@ -133,8 +137,9 @@ describe('Agent', () => {
133
137
 
134
138
  beforeEach(() => {
135
139
  agent = new Agent();
136
- agent._request = sinon.stub();
137
- agent._request.resolves('fake-response');
140
+ agent._promiseResponse = sinon.stub();
141
+ agent._promiseResponse.resolves('fake-response');
142
+ agent._buildRequest = sinon.stub();
138
143
  agent._sanitizeFiles = sinon.stub();
139
144
  });
140
145
 
@@ -148,37 +153,16 @@ describe('Agent', () => {
148
153
  .then((res) => {
149
154
  expect(res).to.be.equal('fake-response');
150
155
  expect(agent._sanitizeFiles).calledOnce.calledWith(sinon.match.same(files));
151
- expect(agent._request).calledOnce.calledWith({
152
- uri: 'abc',
153
- method: 'post',
154
- auth: undefined,
155
- headers: undefined,
156
- query: 'all',
157
- data: '123',
158
- files: sanitizedFiles,
159
- context: undefined,
160
- raw: false,
161
- form
162
- });
163
156
  });
164
157
  });
165
158
 
166
159
  it('uses default arguments for request', () => {
160
+ const args = ['abc', { args: '123' }];
161
+ agent._buildRequest.returns(args);
167
162
  return agent.request({ uri: 'abc', method:'post' })
168
163
  .then((res) => {
169
164
  expect(res).to.be.equal('fake-response');
170
- expect(agent._request).calledOnce.calledWith({
171
- uri: 'abc',
172
- method:'post',
173
- auth: undefined,
174
- headers: undefined,
175
- data: undefined,
176
- files: undefined,
177
- form: undefined,
178
- query: undefined,
179
- context: undefined,
180
- raw: false
181
- });
165
+ expect(agent._promiseResponse).calledOnce.calledWith(args);
182
166
  });
183
167
  });
184
168
 
@@ -200,7 +184,7 @@ describe('Agent', () => {
200
184
  agent._promiseResponse = sinon.stub();
201
185
  agent._promiseResponse.resolves('fake-response');
202
186
 
203
- return agent._request(options).then((res) => {
187
+ return agent.request(options).then((res) => {
204
188
  expect(res).to.be.equal('fake-response');
205
189
  expect(agent._buildRequest).calledOnce;
206
190
  expect(agent._buildRequest).calledWith(options);
@@ -209,22 +193,65 @@ describe('Agent', () => {
209
193
  });
210
194
  });
211
195
 
212
- it('builds a promise to call _sendRequest from _promiseResponse', () => {
196
+ it('builds a promise to call _promiseResponse', () => {
213
197
  const agent = new Agent();
214
198
  const req = sinon.stub();
215
- const response = 'response';
216
- const sendRequest = sinon.spy((req, fulfill) => {
217
- fulfill(response);
218
- });
219
- agent._sendRequest = sendRequest;
220
- const promise = agent._promiseResponse(req);
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);
221
206
  expect(promise).has.property('then');
222
- return promise.then((response) => {
223
- expect(sendRequest).calledOnce;
224
- // how to verify that fulfill/reject arguments are correctly passed to the promised function?
225
- //expect(sendRequest).calledWith(req, fulfill, reject);
226
- expect(response).to.be.equal('response');
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
+ });
227
253
  });
254
+ return Promise.all(requests);
228
255
  });
229
256
  });
230
257
 
@@ -232,136 +259,80 @@ describe('Agent', () => {
232
259
  let agent;
233
260
 
234
261
  beforeEach(() => {
235
- agent = new Agent();
262
+ agent = new Agent('abc');
236
263
  });
237
264
 
238
- it('uses prefix if provided', () => {
239
- agent.prefix = 'abc';
240
- const use = sinon.stub();
241
- const req = sinon.stub();
242
- req.returns({ use: use });
243
- const result = agent._buildRequest({ uri: 'uri', method: 'get', makerequest: req });
244
- expect(result).to.be.ok;
245
- expect(req).to.be.calledWith('get', 'uri');
246
- expect(use).to.be.calledWith('abc');
265
+ it('uses a baseURL if provided', () => {
266
+ const [uri] = agent._buildRequest({ uri: '/uri', method: 'get' });
267
+ expect(uri).to.equal('abc/uri');
247
268
  });
248
269
 
249
- it('does not call used if no prefix provided', () => {
250
- agent.prefix = undefined;
251
- const use = sinon.stub();
252
- const req = sinon.stub();
253
- req.returns({ use: use });
254
- const result = agent._buildRequest({ uri: 'uri', method: 'get', makerequest: req });
255
- expect(result).to.be.ok;
256
- expect(req).to.be.calledWith('get', 'uri');
257
- expect(use).to.not.have.been.called;
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');
258
274
  });
259
275
 
260
- it('should invoke _applyContext with the request and context when provided', () => {
261
- agent._applyContext = sinon.stub();
262
- agent.prefix = undefined;
263
- const request = {};
264
- const context = { foo: {} };
265
- const req = sinon.stub().returns(request);
266
- agent._buildRequest({ uri: 'uri', method: 'get', context, makerequest: req });
267
- expect(agent._applyContext).to.be.calledWith(sinon.match.same(request), sinon.match.same(context));
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');
268
280
  });
269
281
 
270
- it('should not invoke _applyContext when no context is provided', () => {
271
- agent._applyContext = sinon.stub();
272
- agent.prefix = undefined;
273
- const request = {};
274
- const req = sinon.stub().returns(request);
275
- agent._buildRequest({ uri: 'uri', method: 'get', makerequest: req });
276
- expect(agent._applyContext).to.not.be.called;
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}`);
277
286
  });
278
287
 
279
-
280
- it('should invoke authorize with the request and auth', () => {
281
- agent.prefix = undefined;
282
- const request = {};
283
- const req = sinon.stub();
284
- req.returns(request);
285
- const authorize = sinon.stub();
286
- agent._authorizationHeader = authorize;
287
- agent._buildRequest({ uri: 'uri', method: 'get', auth: '123', makerequest: req });
288
- expect(authorize).to.be.calledWith(sinon.match.same(request), '123');
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');
289
292
  });
290
293
 
291
- it('should invoke query with the given query', () => {
292
- agent.prefix = undefined;
293
- const query = sinon.stub();
294
- const req = sinon.stub();
295
- req.returns({ query: query, authorize: sinon.stub() });
296
- agent._buildRequest({ uri: 'uri', method: 'get', query: '123', makerequest: req });
297
- expect(query).to.be.calledWith('123');
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
298
  });
299
299
 
300
- it('should not query when no query given', () => {
301
- agent.prefix = undefined;
302
- const query = sinon.stub();
303
- const req = sinon.stub();
304
- req.returns({ query: query, _authorizationHeader: sinon.stub() });
305
- agent._buildRequest({ uri: 'uri', method: 'get', makerequest: req });
306
- expect(query).to.be.not.been.called;
307
- });
308
-
309
- it('should invoke send when data given', () => {
310
- agent.prefix = undefined;
311
- const req = sinon.stub();
312
- const send = sinon.stub();
313
- req.returns({ send: send });
314
- agent._buildRequest({ uri: 'uri', method: 'get', data: 'abcd', makerequest: req });
315
- expect(send).to.be.calledWith('abcd');
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');
316
304
  });
317
305
 
318
306
  it('should setup form send when form data is given', () => {
319
- agent.prefix = undefined;
320
- const req = sinon.stub();
321
- const send = sinon.stub();
322
- const type = sinon.stub();
323
- req.returns({ send: send, type: type });
324
- agent._buildRequest({ uri: 'uri', method: 'get', form: 'abcd', makerequest: req });
325
- expect(send).to.be.calledWith('abcd');
326
- expect(type).to.be.calledWith('form');
307
+ const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get', form: { a: 'abcd' } });
308
+ expect(opts.body).to.eql('a=abcd');
327
309
  });
328
310
 
329
311
  it('should attach files', () => {
330
- agent.prefix = undefined;
331
- const req = sinon.stub();
332
- const attach = sinon.stub();
333
- req.returns({ attach: attach });
334
312
  const files = {
335
- file: { data: 'filedata', path: 'filepath' },
336
- file2: { data: 'file2data', path: 'file2path' }
313
+ file: { data: makeFile('filedata'), path: 'filepath' },
314
+ file2: { data: makeFile('file2data'), path: 'file2path' }
337
315
  };
338
- agent._buildRequest({ uri: 'uri', method: 'get', files: files, makerequest: req });
339
- expect(attach.callCount).to.be.equal(2);
340
- expect(attach).to.be.calledWith('file', 'filedata', { filepath: 'filepath' });
341
- expect(attach).to.be.calledWith('file2', 'file2data', { filepath: 'file2path' });
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');
342
321
  });
343
322
 
344
323
  it('should attach files and form data', () => {
345
- agent.prefix = undefined;
346
- const req = sinon.stub();
347
- const attach = sinon.stub();
348
- const field = sinon.stub();
349
- req.returns({
350
- attach: attach,
351
- field: field
352
- });
353
324
  const files = {
354
- file: { data: 'filedata', path: 'filepath' },
355
- file2: { data: 'file2data', path: 'file2path' }
325
+ file: { data: makeFile('filedata'), path: 'filepath' },
326
+ file2: { data: makeFile('file2data'), path: 'file2path' }
356
327
  };
357
328
  const form = { form1: 'value1', form2: 'value2' };
358
- agent._buildRequest({ uri: 'uri', method: 'get', files: files, form: form, makerequest: req });
359
- expect(attach.callCount).to.be.equal(2);
360
- expect(attach).to.be.calledWith('file', 'filedata', { filepath: 'filepath' });
361
- expect(attach).to.be.calledWith('file2', 'file2data', { filepath: 'file2path' });
362
- expect(field.callCount).to.be.equal(2);
363
- expect(field).to.be.calledWith('form1', 'value1');
364
- expect(field).to.be.calledWith('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');
365
336
  });
366
337
 
367
338
  it('should handle nested dirs', () => {
@@ -369,9 +340,14 @@ describe('Agent', () => {
369
340
  file: { data: makeFile('filedata'), path: 'filepath.ino' },
370
341
  file2: { data: makeFile('file2data'), path: 'dir/file2path.cpp' }
371
342
  };
372
- const req = agent._buildRequest({ uri: 'uri', method: 'get', files: files });
373
- expect(extractFilename(req._formData, 'file', 0)).to.eql('filepath.ino');
374
- expect(extractFilename(req._formData, 'file2', 3)).to.eql('dir/file2path.cpp');
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/);
375
351
  });
376
352
 
377
353
  if (!inBrowser()){
@@ -379,8 +355,8 @@ describe('Agent', () => {
379
355
  const files = {
380
356
  file: { data: makeFile('filedata'), path: 'dir\\windowsfilepath.cpp' }
381
357
  };
382
- const req = agent._buildRequest({ uri: 'uri', method: 'get', files: files });
383
- expect(extractFilename(req._formData, 'file', 0)).to.eql('dir/windowsfilepath.cpp');
358
+ const [, opts] = agent._buildRequest({ uri: 'uri', method: 'get', files });
359
+ expect(extractFilename(opts.body, 'file', 0)).to.eql('dir/windowsfilepath.cpp');
384
360
  });
385
361
  }
386
362
 
@@ -403,93 +379,13 @@ describe('Agent', () => {
403
379
  return /filename="([^"]*)"/.exec(formData._streams[fieldIndex])[1];
404
380
  }
405
381
  }
406
- });
407
382
 
408
- describe('_sendRequest', () => {
409
- it('can retrieve a success response', () => {
410
- const response = { body: 'abc', statusCode:200 };
411
- const fulfill = sinon.stub();
412
- const reject = sinon.stub();
413
-
414
- const request = {
415
- end: function end(callback){
416
- callback(undefined, response);
417
- }
418
- };
419
- const end = sinon.spy(request, 'end');
420
-
421
- const agent = new Agent();
422
- const result = agent._sendRequest(request, fulfill, reject);
423
- expect(result).to.be.undefined;
424
- expect(end).to.be.calledOnce;
425
-
426
- expect(fulfill).to.be.calledOnce;
427
- // not called with response directly but with an object that is equivalent
428
- expect(fulfill).to.be.calledWith(response);
429
- expect(reject).to.not.be.called;
430
- });
431
-
432
- const failResponseData = [
433
- {
434
- name: 'error text includes body error description',
435
- response: {
436
- body: {
437
- error_description: 'file not found'
438
- }
439
- },
440
- error: { status: 404 },
441
- errorDescription: 'HTTP error 404 from 123.url - file not found'
442
- },
443
-
444
- {
445
- name: 'error text with no body description',
446
- response: { body: {} },
447
- error: { status: 404 },
448
- errorDescription: 'HTTP error 404 from 123.url'
449
- },
450
-
451
- {
452
- name: 'error text with no body',
453
- response: { },
454
- error: { status: 404 },
455
- errorDescription: 'HTTP error 404 from 123.url'
456
- },
457
-
458
- {
459
- name: 'error text with no response',
460
- error: { status: 404 },
461
- errorDescription: 'HTTP error 404 from 123.url' },
462
-
463
- {
464
- name: 'error text with no status',
465
- error: {},
466
- errorDescription: 'Network error from 123.url'
383
+ function extractFormName(formData, fieldName, fieldIndex){
384
+ if (inBrowser()){
385
+ return formData.get(fieldName);
386
+ } else {
387
+ return formData._streams[fieldIndex + 1];
467
388
  }
468
- ];
469
- for (let failData of failResponseData){
470
- it(`can retrieve an error response - ${failData.name}`, () => {
471
- const fulfill = sinon.stub();
472
- const reject = sinon.stub();
473
-
474
- const request = {
475
- url: '123.url', end: function end(callback){
476
- callback(failData.error, failData.response);
477
- }
478
- };
479
- const end = sinon.spy(request, 'end');
480
-
481
- const agent = new Agent();
482
- const result = agent._sendRequest(request, fulfill, reject);
483
- expect(result).to.be.undefined;
484
- expect(end).to.be.calledOnce;
485
- expect(fulfill).to.not.be.called;
486
- expect(reject).to.be.calledWithMatch({
487
- statusCode: failData.error.status,
488
- errorDescription: failData.errorDescription,
489
- error:failData.error,
490
- body:failData.response ? failData.response.body : undefined
491
- });
492
- });
493
389
  }
494
390
  });
495
391
 
@@ -514,57 +410,46 @@ describe('Agent', () => {
514
410
  });
515
411
  });
516
412
 
517
- describe('_applyContext', () => {
518
- let req;
519
-
520
- beforeEach(() => {
521
- req = { set: sinon.stub() };
522
- });
523
-
524
- it('applies the tool context when defined', () => {
413
+ describe('_getContextHeaders', () => {
414
+ it('generates the tool context when defined', () => {
525
415
  const context = { tool: { name: 'spanner' } };
526
- agent._applyContext(req, context);
527
- expect(req.set).to.have.been.calledOnce;
528
- expect(req.set).to.have.been.calledWith('X-Particle-Tool', 'spanner');
416
+ const subject = agent._getContextHeaders(context);
417
+ expect(subject).to.have.property('X-Particle-Tool', 'spanner');
529
418
  });
530
419
 
531
- it('does not apply the tool context when not defined',() => {
420
+ it('does not add the tool context header when not defined',() => {
532
421
  const context = { tool: { name2: 'spanner' } };
533
- agent._applyContext(req, context);
534
- expect(req.set).to.have.not.been.called;
422
+ const subject = agent._getContextHeaders(context);
423
+ expect(subject).to.not.have.property('X-Particle-Tool');
535
424
  });
536
425
 
537
- it('applies the project context when defined',() => {
426
+ it('generates the project context header when defined',() => {
538
427
  const context = { project: { name: 'blinky' } };
539
- agent._applyContext(req, context);
540
- expect(req.set).to.have.been.calledOnce;
541
- expect(req.set).to.have.been.calledWith('X-Particle-Project', 'blinky');
428
+ const subject = agent._getContextHeaders(context);
429
+ expect(subject).to.have.property('X-Particle-Project', 'blinky');
542
430
  });
543
431
 
544
- it('does not apply the tool context when not defined',() => {
432
+ it('does not generate the project context header when not defined',() => {
545
433
  const context = { project: { name2: 'blinky' } };
546
- agent._applyContext(req, context);
547
- expect(req.set).to.have.been.not.called;
434
+ const subject = agent._getContextHeaders(context);
435
+ expect(subject).to.not.have.property('X-Particle-Project');
548
436
  });
549
437
  });
550
438
 
551
- describe('_addToolContext', () => {
439
+ describe('_getToolContext', () => {
552
440
  it('does not add a header when the tool name is not defined', () => {
553
- const req = { set: sinon.stub() };
554
441
  const tool = { noname: 'cli' };
555
- agent._addToolContext(req, tool);
556
- expect(req.set).to.have.not.been.called;
442
+ const subject = agent._getToolContext(tool);
443
+ expect(subject).to.eql({});
557
444
  });
558
445
 
559
446
  it('adds a header when the tool is defined', () => {
560
- const req = { set: sinon.stub() };
561
447
  const tool = { name: 'cli' };
562
- agent._addToolContext(req, tool);
563
- expect(req.set).to.have.been.calledWith('X-Particle-Tool', 'cli');
448
+ const subject = agent._getToolContext(tool);
449
+ expect(subject).to.eql({ 'X-Particle-Tool': 'cli' });
564
450
  });
565
451
 
566
452
  it('adds a header when the tool and components is defined', () => {
567
- const req = { set: sinon.stub() };
568
453
  const tool = {
569
454
  name: 'cli',
570
455
  version: '1.2.3',
@@ -573,24 +458,22 @@ describe('Agent', () => {
573
458
  { name: 'foo', version: '0.0.1' }
574
459
  ]
575
460
  };
576
- agent._addToolContext(req, tool);
577
- expect(req.set).to.have.been.calledWith('X-Particle-Tool', 'cli@1.2.3, bar@a.b.c, foo@0.0.1');
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' });
578
463
  });
579
464
  });
580
465
 
581
466
  describe('_addProjectContext', () => {
582
467
  it('adds a header when the project is defined', () => {
583
- const req = { set: sinon.stub() };
584
468
  const project = { name: 'blinky' };
585
- agent._addProjectContext(req, project);
586
- expect(req.set).to.have.been.calledWith('X-Particle-Project', 'blinky');
469
+ const subject = agent._getProjectContext(project);
470
+ expect(subject).to.have.property('X-Particle-Project', 'blinky');
587
471
  });
588
472
 
589
473
  it('does not set the header when the project has no name', () => {
590
- const req = { set: sinon.stub() };
591
474
  const project = { noname: 'blinky' };
592
- agent._addProjectContext(req, project);
593
- expect(req.set).to.have.not.been.called;
475
+ const subject = agent._getProjectContext(project);
476
+ expect(subject).to.not.have.property('X-Particle-Project');
594
477
  });
595
478
  });
596
479