hmpo-model 3.2.0 → 4.0.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.
@@ -1,14 +1,15 @@
1
1
  'use strict';
2
2
 
3
- const proxyquire = require('proxyquire');
3
+ const Model = require('../../lib/remote-model');
4
4
  const BaseModel = require('../../lib/local-model');
5
- const _ = require('underscore');
5
+ const logger = require('hmpo-logger');
6
+
7
+ const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
6
8
 
7
9
  describe('Remote Model', () => {
8
- let model, Model, cb, mocks;
10
+ let model, cb, mocks;
9
11
 
10
12
  beforeEach(() => {
11
- Model = require('../../lib/remote-model');
12
13
  model = new Model();
13
14
 
14
15
  cb = sinon.stub();
@@ -24,7 +25,6 @@ describe('Remote Model', () => {
24
25
  });
25
26
 
26
27
  it('should be an instance of LocalModel', () => {
27
- Model = require('../../lib/remote-model');
28
28
  model = new Model();
29
29
 
30
30
  model.should.be.an.instanceOf(BaseModel);
@@ -54,16 +54,31 @@ describe('Remote Model', () => {
54
54
  });
55
55
 
56
56
  describe('setLogger', () => {
57
- let getStub = sinon.stub();
58
- let Model = proxyquire('../../lib/remote-model', {
59
- 'hmpo-logger': {
60
- get: getStub
61
- }
57
+ beforeEach(() => {
58
+ sinon.stub(logger, 'get').returns('logger');
62
59
  });
63
60
 
64
- model = new Model();
61
+ afterEach(() => {
62
+ logger.get.restore();
63
+ });
64
+
65
+ it('should set up a new hmpo-logger', () => {
66
+ model = new Model();
67
+
68
+ logger.get.should.have.been.calledWithExactly(':remote-model');
69
+ model.logger.should.equal('logger');
70
+ });
71
+
72
+ it('should use console log and a trimHtml pass-through if hmpo-logger is not available', () => {
73
+ logger.get.throws(new Error());
74
+
75
+ model = new Model();
65
76
 
66
- getStub.should.have.been.calledWithExactly(':remote-model');
77
+ model.logger.outbound.should.eql(console.log);
78
+ model.logger.trimHtml.should.be.a('function');
79
+ const html = {};
80
+ model.logger.trimHtml(html).should.equal(html);
81
+ });
67
82
  });
68
83
 
69
84
  describe('fetch', () => {
@@ -84,6 +99,15 @@ describe('Remote Model', () => {
84
99
  method: 'GET'
85
100
  });
86
101
  });
102
+
103
+ it('should pass args onto requestConfig', () => {
104
+ model.fetch({ foo: 'bar' }, cb);
105
+
106
+ model.requestConfig.should.have.been.calledWithExactly({
107
+ method: 'GET'
108
+ }, { foo: 'bar' });
109
+ });
110
+
87
111
  it('should call request', () => {
88
112
  model.requestConfig.returns(mocks.config);
89
113
 
@@ -109,9 +133,9 @@ describe('Remote Model', () => {
109
133
  it('should call prepare', () => {
110
134
  model.save(cb);
111
135
 
112
- model.prepare.should.have.been.called;
113
-
136
+ model.prepare.should.have.been.calledWithExactly(sinon.match.func);
114
137
  });
138
+
115
139
  it('should call callback with an error', () => {
116
140
  let error = new Error('error');
117
141
  model.prepare.yields(error);
@@ -119,7 +143,6 @@ describe('Remote Model', () => {
119
143
  model.save(cb);
120
144
 
121
145
  cb.should.have.been.calledWith(error);
122
-
123
146
  });
124
147
 
125
148
  context('on prepare success', () => {
@@ -135,20 +158,27 @@ describe('Remote Model', () => {
135
158
  it('should use requestConfig', () => {
136
159
  model.save(cb);
137
160
 
138
- model.requestConfig.should.have.been.calledWith({
161
+ model.requestConfig.should.have.been.calledWithExactly({
139
162
  method: 'POST',
140
- headers: {
141
- 'Content-Type': 'application/json',
142
- 'Content-Length': Buffer.byteLength(JSON.stringify(preparedData))
143
- }
144
- });
163
+ json: preparedData
164
+ }, undefined);
145
165
  });
166
+
167
+ it('should pass args onto requestConfig', () => {
168
+ model.save({ foo: 'bar' }, cb);
169
+
170
+ model.requestConfig.should.have.been.calledWithExactly({
171
+ method: 'POST',
172
+ json: preparedData
173
+ }, { foo: 'bar' });
174
+ });
175
+
146
176
  it('should call request', () => {
147
177
  model.requestConfig.returns(mocks.config);
148
178
 
149
179
  model.save(cb);
150
180
 
151
- model.request.should.have.been.calledWith(mocks.config, JSON.stringify(preparedData), cb);
181
+ model.request.should.have.been.calledWith(mocks.config, cb);
152
182
  });
153
183
  });
154
184
 
@@ -172,6 +202,15 @@ describe('Remote Model', () => {
172
202
  method: 'DELETE'
173
203
  });
174
204
  });
205
+
206
+ it('should pass args onto requestConfig', () => {
207
+ model.delete({ foo: 'bar' }, cb);
208
+
209
+ model.requestConfig.should.have.been.calledWithExactly({
210
+ method: 'DELETE'
211
+ }, { foo: 'bar' });
212
+ });
213
+
175
214
  it('should call request', () => {
176
215
  model.requestConfig.returns(mocks.config);
177
216
 
@@ -182,50 +221,227 @@ describe('Remote Model', () => {
182
221
  });
183
222
 
184
223
  describe('requestConfig', () => {
185
- beforeEach(() => {
186
- sinon.stub(model, 'url');
187
- sinon.stub(model, 'auth');
224
+ describe('url', () => {
225
+ it('should use url from model options', () => {
226
+ model.options.url = 'https://example.com/options';
227
+ const config = model.requestConfig({
228
+ 'method': 'VERB'
229
+ });
188
230
 
189
- model.url.returns('http://example.net');
190
- });
231
+ config.url.should.equal('https://example.com/options');
232
+ });
191
233
 
192
- afterEach(() => {
193
- model.url.restore();
194
- model.auth.restore();
234
+ it('should use url from request config', () => {
235
+ model.options.url = 'https://example.com/options';
236
+ const config = model.requestConfig({
237
+ 'method': 'VERB',
238
+ 'url': 'https://example.com/config'
239
+ });
240
+
241
+ config.url.should.equal('https://example.com/config');
242
+ });
243
+
244
+ it('should use url returned by overridden url() method', () => {
245
+ model.options.url = 'https://example.com/options';
246
+ model.url = sinon.stub().returns('https://example.com/overridden');
247
+ const config = model.requestConfig({
248
+ 'method': 'VERB',
249
+ 'url': 'https://example.com/config'
250
+ });
251
+ model.url.should.have.been.calledWithExactly('https://example.com/config', undefined);
252
+ config.url.should.equal('https://example.com/overridden');
253
+ });
254
+
255
+ it('should pass args onto url method', () => {
256
+ model.url = sinon.stub().returns('https://example.com/overridden');
257
+ const config = model.requestConfig({
258
+ 'method': 'VERB',
259
+ 'url': 'https://example.com/config'
260
+ }, { foo: 'bar' });
261
+ model.url.should.have.been.calledWithExactly('https://example.com/config', { foo: 'bar' });
262
+ config.url.should.equal('https://example.com/overridden');
263
+ });
195
264
  });
196
265
 
266
+ describe('auth', () => {
267
+ it('should use auth from model options', () => {
268
+ model.options.auth = 'options:pass:word';
269
+ const config = model.requestConfig({
270
+ 'method': 'VERB'
271
+ });
197
272
 
198
- it('should use url', () => {
199
- model.requestConfig({
200
- 'method': 'VERB'
273
+ config.username.should.equal('options');
274
+ config.password.should.equal('pass:word');
275
+ config.should.not.have.property('auth');
276
+ });
277
+
278
+ it('should use auth from config', () => {
279
+ model.options.auth = 'options:pass:word';
280
+ const config = model.requestConfig({
281
+ 'method': 'VERB',
282
+ 'auth': 'config:pass:word'
283
+ });
284
+
285
+ config.username.should.equal('config');
286
+ config.password.should.equal('pass:word');
287
+ config.should.not.have.property('auth');
201
288
  });
202
289
 
203
- model.url.should.have.been.calledWithExactly();
290
+ it('should use auth from overidden auth() method', () => {
291
+ model.options.auth = 'options:pass:word';
292
+ model.auth = sinon.stub().returns({ user: 'overridden', pass: 'pass:word' });
293
+ const config = model.requestConfig({
294
+ 'method': 'VERB',
295
+ 'auth': 'config:pass:word'
296
+ });
297
+
298
+ model.auth.should.have.been.calledWithExactly('config:pass:word');
299
+ config.username.should.equal('overridden');
300
+ config.password.should.equal('pass:word');
301
+ config.should.not.have.property('auth');
302
+ });
204
303
  });
205
304
 
206
- it('should use auth', () => {
207
- model.requestConfig({
208
- 'method': 'VERB'
305
+ describe('timeout', () => {
306
+ it('should use a default timeout', () => {
307
+ const config = model.requestConfig({
308
+ 'method': 'VERB'
309
+ });
310
+
311
+ config.should.deep.include({
312
+ timeout: {
313
+ connect: 60000,
314
+ lookup: 60000,
315
+ response: 60000,
316
+ secureConnect: 60000,
317
+ send: 60000,
318
+ socket: 60000
319
+ }
320
+ });
321
+ });
322
+
323
+ it('should use timeout from model options', () => {
324
+ model.options.timeout = 1000;
325
+ const config = model.requestConfig({
326
+ 'method': 'VERB'
327
+ });
328
+
329
+ config.should.deep.include({
330
+ timeout: {
331
+ connect: 1000,
332
+ lookup: 1000,
333
+ response: 1000,
334
+ secureConnect: 1000,
335
+ send: 1000,
336
+ socket: 1000
337
+ }
338
+ });
339
+ });
340
+
341
+ it('should use timeout from config', () => {
342
+ model.options.timeout = 1000;
343
+ const config = model.requestConfig({
344
+ 'method': 'VERB',
345
+ 'timeout': 2000
346
+ });
347
+
348
+ config.should.deep.include({
349
+ timeout: {
350
+ connect: 2000,
351
+ lookup: 2000,
352
+ response: 2000,
353
+ secureConnect: 2000,
354
+ send: 2000,
355
+ socket: 2000
356
+ }
357
+ });
358
+ });
359
+
360
+ it('should use timeout from specified object', () => {
361
+ const config = model.requestConfig({
362
+ 'method': 'VERB',
363
+ 'timeout': { connect: 3000 }
364
+ });
365
+
366
+ config.should.deep.include({
367
+ timeout: {
368
+ connect: 3000,
369
+ }
370
+ });
209
371
  });
210
372
 
211
- model.auth.should.have.been.calledWithExactly();
373
+ it('should use timeout from overidden timeout() method', () => {
374
+ model.timeout = sinon.stub().returns({ connect: 4000 });
375
+ const config = model.requestConfig({
376
+ 'method': 'VERB',
377
+ 'timeout': 2000
378
+ });
379
+
380
+ model.timeout.should.have.been.calledWithExactly(2000);
381
+ config.should.deep.include({
382
+ timeout: {
383
+ connect: 4000,
384
+ }
385
+ });
386
+ });
212
387
  });
213
388
 
214
- it('should add auth to config if provided', () => {
215
- model.auth.returns({
216
- user: 'username',
217
- pass: 'password'
389
+ describe('proxy', () => {
390
+ it('should not set up http proxy if there is no url', () => {
391
+ const returnedConfig = model.requestConfig({
392
+ 'method': 'VERB',
393
+ 'proxy': 'http://proxy.example.com:8000'
394
+ });
395
+
396
+ returnedConfig.should.not.have.property('proxy');
397
+ returnedConfig.should.not.have.property('agent');
218
398
  });
219
399
 
220
- let returnedConfig = model.requestConfig({
221
- 'method': 'VERB'
400
+ it('should set up http proxy if specified', () => {
401
+ const returnedConfig = model.requestConfig({
402
+ 'method': 'VERB',
403
+ 'url': 'http://example.net',
404
+ 'proxy': 'http://proxy.example.com:8000'
405
+ });
406
+
407
+ sinon.assert.match(returnedConfig, {
408
+ agent: {
409
+ http: sinon.match.instanceOf(HttpProxyAgent)
410
+ }
411
+ });
222
412
  });
223
413
 
224
- returnedConfig.should.deep.include({
225
- auth: {
226
- user: 'username',
227
- pass: 'password'
228
- }
414
+ it('should set up https proxy if specified', () => {
415
+ const returnedConfig = model.requestConfig({
416
+ 'method': 'VERB',
417
+ 'url': 'https://example.net',
418
+ 'proxy': 'http://proxy.example.com:8000'
419
+ });
420
+
421
+ sinon.assert.match(returnedConfig, {
422
+ agent: {
423
+ https: sinon.match.instanceOf(HttpsProxyAgent)
424
+ }
425
+ });
426
+ });
427
+
428
+ it('should pass proxy options to the new proxy', () => {
429
+ const returnedConfig = model.requestConfig({
430
+ 'method': 'VERB',
431
+ 'url': 'http://example.net',
432
+ 'proxy': {
433
+ proxy: 'http://proxy.example.com:8000',
434
+ keepAlive: true
435
+ }
436
+ });
437
+
438
+ sinon.assert.match(returnedConfig, {
439
+ agent: {
440
+ http: {
441
+ keepAlive: true
442
+ }
443
+ }
444
+ });
229
445
  });
230
446
  });
231
447
 
@@ -326,7 +542,7 @@ describe('Remote Model', () => {
326
542
  }
327
543
  };
328
544
 
329
- let savedConfig = _.clone(config);
545
+ let savedConfig = Object.assign({}, config);
330
546
 
331
547
  let returnedConfig = model.requestConfig(config);
332
548
 
@@ -337,14 +553,15 @@ describe('Remote Model', () => {
337
553
  });
338
554
 
339
555
  describe('request', () => {
340
- let settings, body, requestSettings;
556
+ let settings, requestSettings, mocks;
341
557
 
342
558
  beforeEach(() => {
343
- let Model = proxyquire('../../lib/remote-model', {
344
- 'request': mocks.request
345
- });
346
-
559
+ mocks = {};
560
+ mocks.got = sinon.stub().returns(mocks);
561
+ mocks.then = sinon.stub().returns(mocks);
562
+ mocks.catch = sinon.stub().returns(mocks);
347
563
  model = new Model();
564
+ model.got = mocks.got;
348
565
 
349
566
  sinon.stub(model, 'logSync');
350
567
  sinon.stub(model, 'logSuccess');
@@ -355,7 +572,7 @@ describe('Remote Model', () => {
355
572
  method: 'VERB'
356
573
  };
357
574
 
358
- requestSettings = _.clone(settings);
575
+ requestSettings = Object.assign({}, settings);
359
576
  });
360
577
 
361
578
  afterEach(() => {
@@ -365,22 +582,11 @@ describe('Remote Model', () => {
365
582
  model.emit.restore();
366
583
  });
367
584
 
368
- it('should invoke request with settings including a body', () => {
369
- requestSettings.body = body;
370
-
371
- model.request(settings, body, cb);
372
-
373
- mocks.request.should.have.been.called;
374
- mocks.request.should.have.been.calledWith(requestSettings);
375
- });
376
-
377
585
  it('should invoke request with request settings', () => {
378
- requestSettings.body = undefined;
379
-
380
586
  model.request(settings, cb);
381
587
 
382
- mocks.request.should.have.been.called;
383
- mocks.request.should.have.been.calledWith(requestSettings);
588
+ mocks.got.should.have.been.called;
589
+ mocks.got.should.have.been.calledWith(requestSettings);
384
590
  });
385
591
 
386
592
  it('should log sync messages', () => {
@@ -412,7 +618,7 @@ describe('Remote Model', () => {
412
618
  });
413
619
 
414
620
  it('should work without a callback', () => {
415
- mocks.request.yields(new Error('Random Error'), {});
621
+ mocks.catch.yields(new Error('Random Error'));
416
622
 
417
623
  model.request(settings);
418
624
 
@@ -431,7 +637,9 @@ describe('Remote Model', () => {
431
637
  'statusCode': 418
432
638
  };
433
639
 
434
- mocks.request.yields(error, response);
640
+ error.response = response;
641
+
642
+ mocks.catch.yields(error);
435
643
  });
436
644
 
437
645
  it('should log error messages', () => {
@@ -514,7 +722,7 @@ describe('Remote Model', () => {
514
722
 
515
723
  context('on success', () => {
516
724
  beforeEach(() => {
517
- mocks.request.yields(null, {
725
+ mocks.then.yields({
518
726
  'body': JSON.stringify({'data': 'value'}),
519
727
  'statusCode': 200
520
728
  });
@@ -621,6 +829,7 @@ describe('Remote Model', () => {
621
829
 
622
830
  afterEach(() => {
623
831
  model.parse.restore();
832
+
624
833
  model.parseError.restore();
625
834
  });
626
835
 
@@ -694,6 +903,22 @@ describe('Remote Model', () => {
694
903
  it('returns data passed', () => {
695
904
  model.parse({ data: 1 }).should.eql({ data: 1 });
696
905
  });
906
+
907
+ it('sets the parsed data to the model', () => {
908
+ model.parse({ foo: 'bar' });
909
+ model.get('foo').should.equal('bar');
910
+ });
911
+
912
+ it('sets the parsed array data to the model as "data"', () => {
913
+ model.parse([1, 2, 3, 4]);
914
+ model.get('data').should.eql([1, 2, 3, 4]);
915
+ });
916
+
917
+ it('does not set if the data falsey', () => {
918
+ model.set = sinon.stub();
919
+ model.parse(null);
920
+ model.set.should.not.have.been.called;
921
+ });
697
922
  });
698
923
 
699
924
  describe('parseError', () => {
@@ -723,12 +948,11 @@ describe('Remote Model', () => {
723
948
  });
724
949
 
725
950
  it('should return parsed credentials if credentials is a string', () => {
726
- let credentials = model.auth('username:password');
951
+ let credentials = model.auth('username:pass:word');
727
952
 
728
953
  credentials.should.deep.equal({
729
- user: 'username',
730
- pass: 'password',
731
- sendImmediately: true
954
+ username: 'username',
955
+ password: 'pass:word'
732
956
  });
733
957
 
734
958
  });
@@ -747,21 +971,21 @@ describe('Remote Model', () => {
747
971
 
748
972
 
749
973
  describe('logging', () => {
750
- let logger;
974
+ let mocks;
751
975
 
752
976
  beforeEach(() => {
753
- logger = {
977
+ mocks = {
754
978
  outbound: sinon.stub(),
755
979
  trimHtml: sinon.stub()
756
980
  };
757
-
758
- let Model = proxyquire('../../lib/remote-model', {
759
- 'hmpo-logger': {
760
- get: () => logger,
761
- }
762
- });
981
+ sinon.stub(logger, 'get').returns(mocks);
763
982
 
764
983
  model = new Model();
984
+
985
+ });
986
+
987
+ afterEach(() => {
988
+ logger.get.restore();
765
989
  });
766
990
 
767
991
  describe('logSync', () => {
@@ -769,7 +993,7 @@ describe('Remote Model', () => {
769
993
  let args = {
770
994
  settings: {
771
995
  method: 'VERB',
772
- uri: 'http://example.org'
996
+ url: 'http://example.org'
773
997
  }
774
998
  };
775
999
 
@@ -780,7 +1004,7 @@ describe('Remote Model', () => {
780
1004
 
781
1005
  model.logSync(args);
782
1006
 
783
- logger.outbound.should.have.been.calledWithExactly(
1007
+ mocks.outbound.should.have.been.calledWithExactly(
784
1008
  'Model request sent :outVerb :outRequest',
785
1009
  argsAsMeta
786
1010
  );
@@ -792,7 +1016,7 @@ describe('Remote Model', () => {
792
1016
  let args = {
793
1017
  settings: {
794
1018
  method: 'VERB',
795
- uri: 'http://example.org'
1019
+ url: 'http://example.org'
796
1020
  },
797
1021
  statusCode: 418,
798
1022
  responseTime: 1000
@@ -807,7 +1031,7 @@ describe('Remote Model', () => {
807
1031
 
808
1032
  model.logError(args);
809
1033
 
810
- logger.outbound.should.have.been.calledWithExactly(
1034
+ mocks.outbound.should.have.been.calledWithExactly(
811
1035
  'Model request failed :outVerb :outRequest :outResponseCode :outError',
812
1036
  argsAsMeta
813
1037
  );
@@ -819,7 +1043,7 @@ describe('Remote Model', () => {
819
1043
  let args = {
820
1044
  settings: {
821
1045
  method: 'VERB',
822
- uri: 'http://example.org'
1046
+ url: 'http://example.org'
823
1047
  },
824
1048
  statusCode: 418,
825
1049
  responseTime: 1000
@@ -834,7 +1058,7 @@ describe('Remote Model', () => {
834
1058
 
835
1059
  model.logSuccess(args);
836
1060
 
837
- logger.outbound.should.have.been.calledWithExactly(
1061
+ mocks.outbound.should.have.been.calledWithExactly(
838
1062
  'Model request success :outVerb :outRequest :outResponseCode',
839
1063
  argsAsMeta
840
1064
  );
@@ -849,7 +1073,7 @@ describe('Remote Model', () => {
849
1073
  data = {
850
1074
  settings: {
851
1075
  method: 'VERB',
852
- uri: 'http://example.org'
1076
+ url: 'http://example.org'
853
1077
  },
854
1078
  statusCode: 418,
855
1079
  responseTime: 3000,
@@ -924,7 +1148,7 @@ describe('Remote Model', () => {
924
1148
  });
925
1149
 
926
1150
  it('should be present with error', () => {
927
- logger.trimHtml.returns('Html Body');
1151
+ mocks.trimHtml.returns('Html Body');
928
1152
 
929
1153
  let result = model.logMeta(data);
930
1154
 
package/.travis.yml DELETED
@@ -1,8 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - "10"
4
- - "12"
5
- - "14"
6
- notifications:
7
- email: false
8
- sudo: false