@webex/internal-plugin-dss 3.0.0-beta.42 → 3.0.0-beta.421

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,28 +1,35 @@
1
1
  /*!
2
2
  * Copyright (c) 2015-2022 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
-
5
- import {assert} from '@webex/test-helper-chai';
4
+ /* eslint-disable no-underscore-dangle */
5
+ import chai from 'chai';
6
+ import chaiAsPromised from 'chai-as-promised';
7
+ import {assert, expect} from '@webex/test-helper-chai';
6
8
  import DSS from '@webex/internal-plugin-dss';
9
+ import {Batcher} from '@webex/webex-core';
7
10
  import MockWebex from '@webex/test-helper-mock-webex';
8
11
  import sinon from 'sinon';
9
- import {expect} from 'chai';
10
12
  import {set} from 'lodash';
11
13
  import uuid from 'uuid';
14
+ import config from '@webex/internal-plugin-dss/src/config';
12
15
 
16
+ chai.use(chaiAsPromised);
13
17
  describe('plugin-dss', () => {
14
18
  describe('DSS', () => {
19
+ const originalBatcherRequest = Batcher.prototype.request;
15
20
  let webex;
16
21
  let uuidStub;
17
22
  let mercuryCallbacks;
23
+ let clock;
18
24
 
19
25
  beforeEach(() => {
20
- webex = new MockWebex({
26
+ webex = MockWebex({
21
27
  canAuthorize: false,
22
28
  children: {
23
29
  dss: DSS,
24
30
  },
25
31
  });
32
+ webex.config.dss = config.dss;
26
33
 
27
34
  uuidStub = sinon.stub(uuid, 'v4').returns('randomid');
28
35
 
@@ -39,10 +46,14 @@ describe('plugin-dss', () => {
39
46
  }),
40
47
  off: sinon.spy(),
41
48
  };
49
+
50
+ clock = sinon.useFakeTimers();
42
51
  });
43
52
 
44
53
  afterEach(() => {
45
54
  uuidStub.restore();
55
+ clock.restore();
56
+ Batcher.prototype.request = originalBatcherRequest;
46
57
  });
47
58
 
48
59
  describe('#register()', () => {
@@ -52,10 +63,12 @@ describe('plugin-dss', () => {
52
63
  assert.callCount(webex.internal.mercury.on, 2);
53
64
 
54
65
  const firstCallArgs = webex.internal.mercury.on.getCall(0).args;
66
+
55
67
  expect(firstCallArgs[0]).to.equal('event:directory.lookup');
56
68
  expect(firstCallArgs[1]).to.be.a('function');
57
69
 
58
70
  const secondCallArgs = webex.internal.mercury.on.getCall(1).args;
71
+
59
72
  expect(secondCallArgs[0]).to.equal('event:directory.search');
60
73
  expect(secondCallArgs[1]).to.be.a('function');
61
74
 
@@ -81,9 +94,11 @@ describe('plugin-dss', () => {
81
94
  assert.callCount(webex.internal.mercury.off, 2);
82
95
 
83
96
  const firstCallArgs = webex.internal.mercury.off.getCall(0).args;
97
+
84
98
  expect(firstCallArgs[0]).to.equal('event:directory.lookup');
85
99
 
86
100
  const secondCallArgs = webex.internal.mercury.off.getCall(1).args;
101
+
87
102
  expect(secondCallArgs[0]).to.equal('event:directory.search');
88
103
 
89
104
  assert.equal(webex.internal.dss.registered, false);
@@ -91,24 +106,27 @@ describe('plugin-dss', () => {
91
106
 
92
107
  it('handles unregister when it is not registered', async () => {
93
108
  const result = await webex.internal.dss.unregister();
109
+
94
110
  await expect(result).equal(undefined);
95
111
  assert.equal(webex.internal.dss.registered, false);
96
112
  });
97
113
  });
98
114
 
99
- const createData = (requestId, sequence, finished, dataPath) => {
115
+ const createData = (requestId, sequence, finished, dataPath, results) => {
100
116
  const data = {
101
117
  requestId,
102
118
  sequence,
103
119
  };
120
+
104
121
  if (finished) {
105
122
  (data as any).finished = finished;
106
123
  }
107
- set(data, dataPath, [`data${sequence}`]);
124
+ set(data, dataPath, results);
125
+
108
126
  return {data};
109
127
  };
110
128
 
111
- const testRequest = async ({method, resource, params, bodyParams, dataPath, event}) => {
129
+ const testMakeRequest = async ({method, resource, params, bodyParams}) => {
112
130
  webex.request = sinon.stub();
113
131
 
114
132
  await webex.internal.dss.register();
@@ -130,23 +148,58 @@ describe('plugin-dss', () => {
130
148
  },
131
149
  ]);
132
150
 
133
- mercuryCallbacks[event](createData(requestId, 1, false, dataPath));
134
- mercuryCallbacks[event](createData(requestId, 2, true, dataPath));
135
- mercuryCallbacks[event](createData(requestId, 0, false, dataPath));
151
+ return {requestId, promise};
152
+ };
153
+
154
+ const testMakeBatchedRequests = async ({requests, calls}) => {
155
+ requests.forEach((request, index) => {
156
+ uuidStub.onCall(index).returns(request.id);
157
+ });
158
+ webex.request = sinon.stub();
159
+
160
+ await webex.internal.dss.register();
136
161
 
137
- const result = await promise;
138
- expect(result).to.deep.equal(['data0', 'data1', 'data2']);
162
+ const promises = calls.map((call) => webex.internal.dss[call.method](call.params));
163
+
164
+ await clock.tickAsync(49);
165
+ expect(webex.request.notCalled).to.be.true;
166
+ await clock.tickAsync(1);
167
+ expect(webex.request.called).to.be.true;
168
+
169
+ requests.forEach((request, index) => {
170
+ expect(webex.request.getCall(index).args).to.deep.equal([
171
+ {
172
+ service: 'directorySearch',
173
+ body: {
174
+ requestId: request.id,
175
+ ...request.bodyParams,
176
+ },
177
+ contentType: 'application/json',
178
+ method: 'POST',
179
+ resource: request.resource,
180
+ },
181
+ ]);
182
+ });
183
+
184
+ return {promises};
139
185
  };
140
186
 
141
187
  describe('#lookupDetail', () => {
142
188
  it('calls _request correctly', async () => {
143
189
  webex.internal.device.orgId = 'userOrgId';
144
- webex.internal.dss._request = sinon.stub().returns(Promise.resolve('some return value'));
190
+ webex.internal.dss._request = sinon.stub().returns(
191
+ Promise.resolve({
192
+ resultArray: ['some return value'],
193
+ foundArray: ['test id'],
194
+ })
195
+ );
145
196
 
146
197
  const result = await webex.internal.dss.lookupDetail({id: 'test id'});
198
+
147
199
  expect(webex.internal.dss._request.getCall(0).args).to.deep.equal([
148
200
  {
149
201
  dataPath: 'lookupResult.entities',
202
+ foundPath: 'lookupResult.entitiesFound',
150
203
  resource: '/lookup/orgid/userOrgId/identity/test id/detail',
151
204
  },
152
205
  ]);
@@ -154,31 +207,97 @@ describe('plugin-dss', () => {
154
207
  });
155
208
 
156
209
  it('works correctly', async () => {
157
- await testRequest({
210
+ const {requestId, promise} = await testMakeRequest({
158
211
  method: 'lookupDetail',
159
- dataPath: 'lookupResult.entities',
160
- event: 'event:directory.lookup',
161
212
  resource: '/lookup/orgid/userOrgId/identity/test id/detail',
162
- params: {
163
- id: 'test id',
164
- },
213
+ params: {id: 'test id'},
165
214
  bodyParams: {},
166
215
  });
216
+
217
+ mercuryCallbacks['event:directory.lookup'](
218
+ createData(requestId, 0, true, 'lookupResult', {
219
+ entities: ['data0'],
220
+ entitiesFound: ['test id'],
221
+ })
222
+ );
223
+ const result = await promise;
224
+
225
+ expect(result).to.deep.equal('data0');
226
+ });
227
+
228
+ it('fails correctly if lookup fails', async () => {
229
+ const {requestId, promise} = await testMakeRequest({
230
+ method: 'lookupDetail',
231
+ resource: '/lookup/orgid/userOrgId/identity/test id/detail',
232
+ params: {id: 'test id'},
233
+ bodyParams: {},
234
+ });
235
+
236
+ mercuryCallbacks['event:directory.lookup'](
237
+ createData(requestId, 0, true, 'lookupResult', {entitiesNotFound: ['test id']})
238
+ );
239
+ const result = await promise;
240
+
241
+ expect(result).to.be.null;
242
+ });
243
+ it('fails with default timeout when mercury does not respond', async () => {
244
+ const {promise} = await testMakeRequest({
245
+ method: 'lookupDetail',
246
+ resource: '/lookup/orgid/userOrgId/identity/test id/detail',
247
+ params: {id: 'test id'},
248
+ bodyParams: {},
249
+ });
250
+
251
+ promise.catch(() => {}); // to prevent the test from failing due to unhandled promise rejection
252
+
253
+ await clock.tickAsync(6000);
254
+
255
+ return assert.isRejected(
256
+ promise,
257
+ 'The DSS did not respond within 6000 ms.' +
258
+ '\n Request Id: randomid' +
259
+ '\n Resource: /lookup/orgid/userOrgId/identity/test id/detail' +
260
+ '\n Params: undefined'
261
+ );
262
+ });
263
+
264
+ it('does not fail with timeout when mercury response in time', async () => {
265
+ const {requestId, promise} = await testMakeRequest({
266
+ method: 'lookupDetail',
267
+ resource: '/lookup/orgid/userOrgId/identity/test id/detail',
268
+ params: {id: 'test id'},
269
+ bodyParams: {},
270
+ });
271
+
272
+ await clock.tickAsync(499);
273
+
274
+ mercuryCallbacks['event:directory.lookup'](
275
+ createData(requestId, 0, true, 'lookupResult', {entitiesNotFound: ['test id']})
276
+ );
277
+
278
+ return assert.isFulfilled(promise);
167
279
  });
168
280
  });
169
281
 
170
282
  describe('#lookup', () => {
171
283
  it('calls _request correctly', async () => {
172
284
  webex.internal.device.orgId = 'userOrgId';
173
- webex.internal.dss._request = sinon.stub().returns(Promise.resolve('some return value'));
285
+ webex.internal.dss._request = sinon.stub().returns(
286
+ Promise.resolve({
287
+ resultArray: ['some return value'],
288
+ foundArray: ['id1'],
289
+ })
290
+ );
291
+
292
+ const result = await webex.internal.dss.lookup({id: 'id1', shouldBatch: false});
174
293
 
175
- const result = await webex.internal.dss.lookup({ids: ['id1', 'id2']});
176
294
  expect(webex.internal.dss._request.getCall(0).args).to.deep.equal([
177
295
  {
178
296
  dataPath: 'lookupResult.entities',
297
+ foundPath: 'lookupResult.entitiesFound',
179
298
  resource: '/lookup/orgid/userOrgId/identities',
180
299
  params: {
181
- lookupValues: ['id1', 'id2'],
300
+ lookupValues: ['id1'],
182
301
  },
183
302
  },
184
303
  ]);
@@ -187,18 +306,26 @@ describe('plugin-dss', () => {
187
306
 
188
307
  it('calls _request correctly with entityProviderType', async () => {
189
308
  webex.internal.device.orgId = 'userOrgId';
190
- webex.internal.dss._request = sinon.stub().returns(Promise.resolve('some return value'));
309
+ webex.internal.dss._request = sinon.stub().returns(
310
+ Promise.resolve({
311
+ resultArray: ['some return value'],
312
+ foundArray: ['id1'],
313
+ })
314
+ );
191
315
 
192
316
  const result = await webex.internal.dss.lookup({
193
- ids: ['id1', 'id2'],
317
+ id: 'id1',
194
318
  entityProviderType: 'CI_USER',
319
+ shouldBatch: false,
195
320
  });
321
+
196
322
  expect(webex.internal.dss._request.getCall(0).args).to.deep.equal([
197
323
  {
198
324
  dataPath: 'lookupResult.entities',
325
+ foundPath: 'lookupResult.entitiesFound',
199
326
  resource: '/lookup/orgid/userOrgId/entityprovidertype/CI_USER',
200
327
  params: {
201
- lookupValues: ['id1', 'id2'],
328
+ lookupValues: ['id1'],
202
329
  },
203
330
  },
204
331
  ]);
@@ -206,35 +333,352 @@ describe('plugin-dss', () => {
206
333
  });
207
334
 
208
335
  it('works correctly', async () => {
209
- await testRequest({
336
+ const {requestId, promise} = await testMakeRequest({
210
337
  method: 'lookup',
211
- dataPath: 'lookupResult.entities',
212
- event: 'event:directory.lookup',
213
338
  resource: '/lookup/orgid/userOrgId/identities',
214
- params: {
215
- ids: ['id1', 'id2'],
339
+ params: {id: 'id1', shouldBatch: false},
340
+ bodyParams: {lookupValues: ['id1']},
341
+ });
342
+
343
+ mercuryCallbacks['event:directory.lookup'](
344
+ createData(requestId, 0, true, 'lookupResult', {
345
+ entities: ['data0'],
346
+ entitiesFound: ['id1'],
347
+ })
348
+ );
349
+ const result = await promise;
350
+
351
+ expect(result).to.deep.equal('data0');
352
+ });
353
+
354
+ it('fails correctly if lookup fails', async () => {
355
+ const {requestId, promise} = await testMakeRequest({
356
+ method: 'lookup',
357
+ resource: '/lookup/orgid/userOrgId/identities',
358
+ params: {id: 'id1', shouldBatch: false},
359
+ bodyParams: {lookupValues: ['id1']},
360
+ });
361
+
362
+ mercuryCallbacks['event:directory.lookup'](
363
+ createData(requestId, 0, true, 'lookupResult', {entitiesNotFound: ['id1']})
364
+ );
365
+ const result = await promise;
366
+
367
+ expect(result).to.be.null;
368
+ });
369
+
370
+ it('calls _batchedLookup correctly', async () => {
371
+ webex.internal.device.orgId = 'userOrgId';
372
+ webex.internal.dss._batchedLookup = sinon
373
+ .stub()
374
+ .returns(Promise.resolve('some return value'));
375
+
376
+ const result = await webex.internal.dss.lookup({id: 'id1'});
377
+
378
+ expect(webex.internal.dss._batchedLookup.getCall(0).args).to.deep.equal([
379
+ {
380
+ resource: '/lookup/orgid/userOrgId/identities',
381
+ lookupValue: 'id1',
216
382
  },
217
- bodyParams: {
218
- lookupValues: ['id1', 'id2'],
383
+ ]);
384
+ expect(result).to.equal('some return value');
385
+ });
386
+
387
+ it('calls _batchedLookup correctly with entityProviderType', async () => {
388
+ webex.internal.device.orgId = 'userOrgId';
389
+ webex.internal.dss._batchedLookup = sinon
390
+ .stub()
391
+ .returns(Promise.resolve('some return value'));
392
+
393
+ const result = await webex.internal.dss.lookup({
394
+ id: 'id1',
395
+ entityProviderType: 'CI_USER',
396
+ });
397
+
398
+ expect(webex.internal.dss._batchedLookup.getCall(0).args).to.deep.equal([
399
+ {
400
+ resource: '/lookup/orgid/userOrgId/entityprovidertype/CI_USER',
401
+ lookupValue: 'id1',
219
402
  },
403
+ ]);
404
+ expect(result).to.equal('some return value');
405
+ });
406
+
407
+ it('Single batched lookup is made after 50 ms and works', async () => {
408
+ const {promises} = await testMakeBatchedRequests({
409
+ requests: [
410
+ {
411
+ id: 'randomid1',
412
+ resource: '/lookup/orgid/userOrgId/identities',
413
+ bodyParams: {lookupValues: ['id1']},
414
+ },
415
+ ],
416
+ calls: [
417
+ {
418
+ method: 'lookup',
419
+ params: {id: 'id1', shouldBatch: true},
420
+ },
421
+ ],
422
+ });
423
+
424
+ mercuryCallbacks['event:directory.lookup'](
425
+ createData('randomid1', 0, true, 'lookupResult', {
426
+ entities: ['data0'],
427
+ entitiesFound: ['id1'],
428
+ })
429
+ );
430
+ const result = await promises[0];
431
+
432
+ expect(result).to.deep.equal('data0');
433
+ });
434
+
435
+ it('Single batched lookup fails correctly if lookup fails', async () => {
436
+ const {promises} = await testMakeBatchedRequests({
437
+ requests: [
438
+ {
439
+ id: 'randomid1',
440
+ resource: '/lookup/orgid/userOrgId/identities',
441
+ bodyParams: {lookupValues: ['id1']},
442
+ },
443
+ ],
444
+ calls: [
445
+ {
446
+ method: 'lookup',
447
+ params: {id: 'id1', shouldBatch: true},
448
+ },
449
+ ],
450
+ });
451
+
452
+ mercuryCallbacks['event:directory.lookup'](
453
+ createData('randomid1', 0, true, 'lookupResult', {entitiesNotFound: ['id1']})
454
+ );
455
+
456
+ const result = await promises[0];
457
+
458
+ expect(result).to.be.null;
459
+ });
460
+
461
+ it('Batch of 2 lookups is made after 50 ms and works', async () => {
462
+ const {promises} = await testMakeBatchedRequests({
463
+ requests: [
464
+ {
465
+ id: 'randomid1',
466
+ resource: '/lookup/orgid/userOrgId/identities',
467
+ bodyParams: {lookupValues: ['id1', 'id2']},
468
+ },
469
+ ],
470
+ calls: [
471
+ {
472
+ method: 'lookup',
473
+ params: {id: 'id1', shouldBatch: true},
474
+ },
475
+ {
476
+ method: 'lookup',
477
+ params: {id: 'id2', shouldBatch: true},
478
+ },
479
+ ],
480
+ });
481
+
482
+ mercuryCallbacks['event:directory.lookup'](
483
+ createData('randomid1', 0, true, 'lookupResult', {
484
+ entities: ['data1', 'data2'],
485
+ entitiesFound: ['id1', 'id2'],
486
+ })
487
+ );
488
+ const result1 = await promises[0];
489
+
490
+ expect(result1).to.equal('data1');
491
+ const result2 = await promises[1];
492
+
493
+ expect(result2).to.equal('data2');
494
+ });
495
+
496
+ it('Batch of 2 lookups is made after 50 ms and one fails correctly', async () => {
497
+ const {promises} = await testMakeBatchedRequests({
498
+ requests: [
499
+ {
500
+ id: 'randomid1',
501
+ resource: '/lookup/orgid/userOrgId/identities',
502
+ bodyParams: {lookupValues: ['id1', 'id2']},
503
+ },
504
+ ],
505
+ calls: [
506
+ {
507
+ method: 'lookup',
508
+ params: {id: 'id1', shouldBatch: true},
509
+ },
510
+ {
511
+ method: 'lookup',
512
+ params: {id: 'id2', shouldBatch: true},
513
+ },
514
+ ],
515
+ });
516
+
517
+ mercuryCallbacks['event:directory.lookup'](
518
+ createData('randomid1', 0, true, 'lookupResult', {
519
+ entities: ['data2'],
520
+ entitiesFound: ['id2'],
521
+ entitiesNotFound: ['id1'],
522
+ })
523
+ );
524
+ const result1 = await promises[0];
525
+
526
+ expect(result1).to.be.null;
527
+
528
+ const result2 = await promises[1];
529
+
530
+ expect(result2).to.equal('data2');
531
+ });
532
+
533
+ it('Two unrelated lookups are made after 50 ms and work', async () => {
534
+ const {promises} = await testMakeBatchedRequests({
535
+ requests: [
536
+ {
537
+ id: 'randomid1',
538
+ resource: '/lookup/orgid/userOrgId/entityprovidertype/CI_USER',
539
+ bodyParams: {lookupValues: ['id1']},
540
+ },
541
+ {
542
+ id: 'randomid2',
543
+ resource: '/lookup/orgid/userOrgId/identities',
544
+ bodyParams: {lookupValues: ['id2']},
545
+ },
546
+ ],
547
+ calls: [
548
+ {
549
+ method: 'lookup',
550
+ params: {id: 'id1', entityProviderType: 'CI_USER', shouldBatch: true},
551
+ },
552
+ {
553
+ method: 'lookup',
554
+ params: {id: 'id2', shouldBatch: true},
555
+ },
556
+ ],
220
557
  });
558
+
559
+ mercuryCallbacks['event:directory.lookup'](
560
+ createData('randomid1', 0, true, 'lookupResult', {
561
+ entities: ['data1'],
562
+ entitiesFound: ['id1'],
563
+ })
564
+ );
565
+ mercuryCallbacks['event:directory.lookup'](
566
+ createData('randomid2', 0, true, 'lookupResult', {
567
+ entities: ['data2'],
568
+ entitiesFound: ['id2'],
569
+ })
570
+ );
571
+ const result1 = await promises[0];
572
+
573
+ expect(result1).to.equal('data1');
574
+ const result2 = await promises[1];
575
+
576
+ expect(result2).to.equal('data2');
577
+ });
578
+
579
+ it('Two unrelated lookups are made after 50 ms and one fails correctly', async () => {
580
+ const {promises} = await testMakeBatchedRequests({
581
+ requests: [
582
+ {
583
+ id: 'randomid1',
584
+ resource: '/lookup/orgid/userOrgId/entityprovidertype/CI_USER',
585
+ bodyParams: {lookupValues: ['id1']},
586
+ },
587
+ {
588
+ id: 'randomid2',
589
+ resource: '/lookup/orgid/userOrgId/identities',
590
+ bodyParams: {lookupValues: ['id2']},
591
+ },
592
+ ],
593
+ calls: [
594
+ {
595
+ method: 'lookup',
596
+ params: {id: 'id1', entityProviderType: 'CI_USER', shouldBatch: true},
597
+ },
598
+ {
599
+ method: 'lookup',
600
+ params: {id: 'id2', shouldBatch: true},
601
+ },
602
+ ],
603
+ });
604
+
605
+ mercuryCallbacks['event:directory.lookup'](
606
+ createData('randomid1', 0, true, 'lookupResult', {entitiesNotFound: ['id1']})
607
+ );
608
+ mercuryCallbacks['event:directory.lookup'](
609
+ createData('randomid2', 0, true, 'lookupResult', {
610
+ entities: ['data2'],
611
+ entitiesFound: ['id2'],
612
+ })
613
+ );
614
+ const result1 = await promises[0];
615
+
616
+ expect(result1).to.be.null;
617
+ const result2 = await promises[1];
618
+
619
+ expect(result2).to.equal('data2');
620
+ });
621
+
622
+ it('fails with default timeout when mercury does not respond', async () => {
623
+ const {promise} = await testMakeRequest({
624
+ method: 'lookup',
625
+ resource: '/lookup/orgid/userOrgId/identities',
626
+ params: {id: 'id1', shouldBatch: false},
627
+ bodyParams: {lookupValues: ['id1']},
628
+ });
629
+
630
+ promise.catch(() => {}); // to prevent the test from failing due to unhandled promise rejection
631
+
632
+ await clock.tickAsync(6000);
633
+
634
+ return assert.isRejected(
635
+ promise,
636
+ 'The DSS did not respond within 6000 ms.' +
637
+ '\n Request Id: randomid' +
638
+ '\n Resource: /lookup/orgid/userOrgId/identities' +
639
+ '\n Params: {"lookupValues":["id1"]}'
640
+ );
641
+ });
642
+
643
+ it('does not fail with timeout when mercury response in time', async () => {
644
+ const {promise, requestId} = await testMakeRequest({
645
+ method: 'lookup',
646
+ resource: '/lookup/orgid/userOrgId/identities',
647
+ params: {id: 'id1', shouldBatch: false},
648
+ bodyParams: {lookupValues: ['id1']},
649
+ });
650
+
651
+ await clock.tickAsync(499);
652
+
653
+ mercuryCallbacks['event:directory.lookup'](
654
+ createData(requestId, 0, true, 'lookupResult', {entitiesNotFound: ['test id']})
655
+ );
656
+
657
+ return assert.isFulfilled(promise);
221
658
  });
222
659
  });
223
660
 
224
661
  describe('#lookupByEmail', () => {
225
662
  it('calls _request correctly', async () => {
226
663
  webex.internal.device.orgId = 'userOrgId';
227
- webex.internal.dss._request = sinon.stub().returns(Promise.resolve('some return value'));
664
+ webex.internal.dss._request = sinon.stub().returns(
665
+ Promise.resolve({
666
+ resultArray: ['some return value'],
667
+ foundArray: ['email1'],
668
+ })
669
+ );
228
670
 
229
671
  const result = await webex.internal.dss.lookupByEmail({
230
- emails: ['email1', 'email2'],
672
+ email: 'email1',
231
673
  });
674
+
232
675
  expect(webex.internal.dss._request.getCall(0).args).to.deep.equal([
233
676
  {
234
677
  dataPath: 'lookupResult.entities',
678
+ foundPath: 'lookupResult.entitiesFound',
235
679
  resource: '/lookup/orgid/userOrgId/emails',
236
680
  params: {
237
- lookupValues: ['email1', 'email2'],
681
+ lookupValues: ['email1'],
238
682
  },
239
683
  },
240
684
  ]);
@@ -242,31 +686,92 @@ describe('plugin-dss', () => {
242
686
  });
243
687
 
244
688
  it('works correctly', async () => {
245
- await testRequest({
689
+ const {requestId, promise} = await testMakeRequest({
246
690
  method: 'lookupByEmail',
247
- dataPath: 'lookupResult.entities',
248
- event: 'event:directory.lookup',
249
691
  resource: '/lookup/orgid/userOrgId/emails',
250
- params: {
251
- emails: ['email1', 'email2'],
252
- },
253
- bodyParams: {
254
- lookupValues: ['email1', 'email2'],
255
- },
692
+ params: {email: 'email1'},
693
+ bodyParams: {lookupValues: ['email1']},
256
694
  });
695
+
696
+ mercuryCallbacks['event:directory.lookup'](
697
+ createData(requestId, 0, true, 'lookupResult', {
698
+ entities: ['data0'],
699
+ entitiesFound: ['email1'],
700
+ })
701
+ );
702
+ const result = await promise;
703
+
704
+ expect(result).to.deep.equal('data0');
705
+ });
706
+
707
+ it('fails correctly if lookup fails', async () => {
708
+ const {requestId, promise} = await testMakeRequest({
709
+ method: 'lookupByEmail',
710
+ resource: '/lookup/orgid/userOrgId/emails',
711
+ params: {email: 'email1'},
712
+ bodyParams: {lookupValues: ['email1']},
713
+ });
714
+
715
+ mercuryCallbacks['event:directory.lookup'](
716
+ createData(requestId, 0, true, 'lookupResult', {}) // entitiesNotFound isn't returned for email
717
+ );
718
+ const result = await promise;
719
+
720
+ expect(result).to.be.null;
721
+ });
722
+
723
+ it('fails with default timeout when mercury does not respond', async () => {
724
+ const {promise} = await testMakeRequest({
725
+ method: 'lookupByEmail',
726
+ resource: '/lookup/orgid/userOrgId/emails',
727
+ params: {email: 'email1'},
728
+ bodyParams: {lookupValues: ['email1']},
729
+ });
730
+
731
+ promise.catch(() => {}); // to prevent the test from failing due to unhandled promise rejection
732
+
733
+ await clock.tickAsync(6000);
734
+
735
+ return assert.isRejected(
736
+ promise,
737
+ 'The DSS did not respond within 6000 ms.' +
738
+ '\n Request Id: randomid' +
739
+ '\n Resource: /lookup/orgid/userOrgId/emails' +
740
+ '\n Params: {"lookupValues":["email1"]}'
741
+ );
742
+ });
743
+
744
+ it('does not fail with timeout when mercury response in time', async () => {
745
+ const {requestId, promise} = await testMakeRequest({
746
+ method: 'lookupByEmail',
747
+ resource: '/lookup/orgid/userOrgId/emails',
748
+ params: {email: 'email1'},
749
+ bodyParams: {lookupValues: ['email1']},
750
+ });
751
+
752
+ await clock.tickAsync(5999);
753
+
754
+ mercuryCallbacks['event:directory.lookup'](
755
+ createData(requestId, 0, true, 'lookupResult', {}) // entitiesNotFound isn't returned for email
756
+ );
757
+
758
+ return assert.isFulfilled(promise);
257
759
  });
258
760
  });
259
761
 
260
762
  describe('#search', () => {
261
763
  it('calls _request correctly', async () => {
262
764
  webex.internal.device.orgId = 'userOrgId';
263
- webex.internal.dss._request = sinon.stub().returns(Promise.resolve('some return value'));
765
+ webex.internal.dss._request = sinon
766
+ .stub()
767
+ .returns(Promise.resolve({resultArray: 'some return value'}));
264
768
 
265
769
  const result = await webex.internal.dss.search({
266
770
  requestedTypes: ['PERSON', 'ROBOT'],
267
771
  resultSize: 100,
268
772
  queryString: 'query',
269
773
  });
774
+
270
775
  expect(webex.internal.dss._request.getCall(0).args).to.deep.equal([
271
776
  {
272
777
  dataPath: 'directoryEntities',
@@ -282,10 +787,67 @@ describe('plugin-dss', () => {
282
787
  });
283
788
 
284
789
  it('works correctly', async () => {
285
- await testRequest({
790
+ const {requestId, promise} = await testMakeRequest({
791
+ method: 'search',
792
+ resource: '/search/orgid/userOrgId/entities',
793
+ params: {
794
+ requestedTypes: ['PERSON', 'ROBOT'],
795
+ resultSize: 100,
796
+ queryString: 'query',
797
+ },
798
+ bodyParams: {
799
+ requestedTypes: ['PERSON', 'ROBOT'],
800
+ resultSize: 100,
801
+ queryString: 'query',
802
+ },
803
+ });
804
+
805
+ mercuryCallbacks['event:directory.search'](
806
+ createData(requestId, 1, false, 'directoryEntities', ['data1'])
807
+ );
808
+ mercuryCallbacks['event:directory.search'](
809
+ createData(requestId, 2, true, 'directoryEntities', ['data2'])
810
+ );
811
+ mercuryCallbacks['event:directory.search'](
812
+ createData(requestId, 0, false, 'directoryEntities', ['data0'])
813
+ );
814
+ const result = await promise;
815
+
816
+ expect(result).to.deep.equal(['data0', 'data1', 'data2']);
817
+ });
818
+
819
+ it('fails with default timeout when mercury does not respond', async () => {
820
+ const {promise} = await testMakeRequest({
821
+ method: 'search',
822
+ resource: '/search/orgid/userOrgId/entities',
823
+ params: {
824
+ requestedTypes: ['PERSON', 'ROBOT'],
825
+ resultSize: 100,
826
+ queryString: 'query',
827
+ },
828
+ bodyParams: {
829
+ requestedTypes: ['PERSON', 'ROBOT'],
830
+ resultSize: 100,
831
+ queryString: 'query',
832
+ },
833
+ });
834
+
835
+ promise.catch(() => {}); // to prevent the test from failing due to unhandled promise rejection
836
+
837
+ await clock.tickAsync(6000);
838
+
839
+ return assert.isRejected(
840
+ promise,
841
+ 'The DSS did not respond within 6000 ms.' +
842
+ '\n Request Id: randomid' +
843
+ '\n Resource: /search/orgid/userOrgId/entities' +
844
+ '\n Params: {"queryString":"query","resultSize":100,"requestedTypes":["PERSON","ROBOT"]}'
845
+ );
846
+ });
847
+
848
+ it('does not fail with timeout when mercury response in time', async () => {
849
+ const {requestId, promise} = await testMakeRequest({
286
850
  method: 'search',
287
- event: 'event:directory.search',
288
- dataPath: 'directoryEntities',
289
851
  resource: '/search/orgid/userOrgId/entities',
290
852
  params: {
291
853
  requestedTypes: ['PERSON', 'ROBOT'],
@@ -298,13 +860,63 @@ describe('plugin-dss', () => {
298
860
  queryString: 'query',
299
861
  },
300
862
  });
863
+
864
+ await clock.tickAsync(5999);
865
+
866
+ mercuryCallbacks['event:directory.search'](
867
+ createData(requestId, 1, false, 'directoryEntities', ['data1'])
868
+ );
869
+ mercuryCallbacks['event:directory.search'](
870
+ createData(requestId, 2, true, 'directoryEntities', ['data2'])
871
+ );
872
+ mercuryCallbacks['event:directory.search'](
873
+ createData(requestId, 0, false, 'directoryEntities', ['data0'])
874
+ );
875
+
876
+ return assert.isFulfilled(promise);
877
+ });
878
+
879
+ it('fails with timeout when request only partially resolved', async () => {
880
+ const {requestId, promise} = await testMakeRequest({
881
+ method: 'search',
882
+ resource: '/search/orgid/userOrgId/entities',
883
+ params: {
884
+ requestedTypes: ['PERSON', 'ROBOT'],
885
+ resultSize: 100,
886
+ queryString: 'query',
887
+ },
888
+ bodyParams: {
889
+ requestedTypes: ['PERSON', 'ROBOT'],
890
+ resultSize: 100,
891
+ queryString: 'query',
892
+ },
893
+ });
894
+
895
+ mercuryCallbacks['event:directory.search'](
896
+ createData(requestId, 2, true, 'directoryEntities', ['data2'])
897
+ );
898
+ mercuryCallbacks['event:directory.search'](
899
+ createData(requestId, 0, false, 'directoryEntities', ['data0'])
900
+ );
901
+
902
+ promise.catch(() => {}); // to prevent the test from failing due to unhandled promise rejection
903
+
904
+ await clock.tickAsync(6000);
905
+
906
+ return assert.isRejected(
907
+ promise,
908
+ 'The DSS did not respond within 6000 ms.' +
909
+ '\n Request Id: randomid' +
910
+ '\n Resource: /search/orgid/userOrgId/entities' +
911
+ '\n Params: {"queryString":"query","resultSize":100,"requestedTypes":["PERSON","ROBOT"]}'
912
+ );
301
913
  });
302
914
  });
303
915
 
304
916
  describe('#_request', () => {
305
917
  it('handles a request correctly', async () => {
306
918
  webex.request = sinon.stub();
307
- uuid.v4.returns('randomid');
919
+ uuid.v4();
308
920
  const promise = webex.internal.dss._request({
309
921
  resource: '/search/orgid/userOrgId/entities',
310
922
  params: {some: 'param'},
@@ -326,34 +938,363 @@ describe('plugin-dss', () => {
326
938
 
327
939
  webex.internal.dss.trigger(webex.internal.dss._getResultEventName('randomid'), {
328
940
  sequence: 1,
329
- a: {
330
- b: {
331
- c: ['data1'],
332
- },
333
- },
941
+ a: {b: {c: ['data1']}},
334
942
  });
335
-
336
943
  webex.internal.dss.trigger(webex.internal.dss._getResultEventName('randomid'), {
337
944
  sequence: 2,
338
945
  finished: true,
339
- a: {
340
- b: {
341
- c: ['data2'],
946
+ a: {b: {c: ['data2']}},
947
+ });
948
+ webex.internal.dss.trigger(webex.internal.dss._getResultEventName('randomid'), {
949
+ sequence: 0,
950
+ a: {b: {c: ['data0']}},
951
+ });
952
+
953
+ const result = await promise;
954
+
955
+ expect(result).to.deep.equal({
956
+ resultArray: ['data0', 'data1', 'data2'],
957
+ });
958
+ });
959
+
960
+ it('handles a request with foundPath correctly', async () => {
961
+ webex.request = sinon.stub();
962
+ uuid.v4.returns('randomid');
963
+ const promise = webex.internal.dss._request({
964
+ resource: '/search/orgid/userOrgId/entities',
965
+ params: {some: 'param'},
966
+ dataPath: 'a.b.c',
967
+ foundPath: 'someFoundPath',
968
+ });
969
+
970
+ expect(webex.request.getCall(0).args).to.deep.equal([
971
+ {
972
+ service: 'directorySearch',
973
+ body: {
974
+ requestId: 'randomid',
975
+ some: 'param',
342
976
  },
977
+ contentType: 'application/json',
978
+ method: 'POST',
979
+ resource: '/search/orgid/userOrgId/entities',
343
980
  },
344
- });
981
+ ]);
345
982
 
983
+ webex.internal.dss.trigger(webex.internal.dss._getResultEventName('randomid'), {
984
+ sequence: 1,
985
+ a: {b: {c: ['data1']}},
986
+ someFoundPath: ['id1'],
987
+ });
988
+ webex.internal.dss.trigger(webex.internal.dss._getResultEventName('randomid'), {
989
+ sequence: 2,
990
+ finished: true,
991
+ a: {b: {c: ['data2']}},
992
+ someFoundPath: ['id2'],
993
+ });
346
994
  webex.internal.dss.trigger(webex.internal.dss._getResultEventName('randomid'), {
347
995
  sequence: 0,
348
- a: {
349
- b: {
350
- c: ['data0'],
996
+ a: {b: {c: ['data0']}},
997
+ someFoundPath: ['id0'],
998
+ });
999
+
1000
+ const result = await promise;
1001
+
1002
+ expect(result).to.deep.equal({
1003
+ resultArray: ['data0', 'data1', 'data2'],
1004
+ foundArray: ['id0', 'id1', 'id2'],
1005
+ });
1006
+ });
1007
+
1008
+ it('handles a request with foundPath and notFoundPath correctly', async () => {
1009
+ webex.request = sinon.stub();
1010
+ uuid.v4.returns('randomid');
1011
+ const promise = webex.internal.dss._request({
1012
+ resource: '/search/orgid/userOrgId/entities',
1013
+ params: {some: 'param'},
1014
+ dataPath: 'a.b.c',
1015
+ foundPath: 'someFoundPath',
1016
+ notFoundPath: 'someNotFoundPath',
1017
+ });
1018
+
1019
+ expect(webex.request.getCall(0).args).to.deep.equal([
1020
+ {
1021
+ service: 'directorySearch',
1022
+ body: {
1023
+ requestId: 'randomid',
1024
+ some: 'param',
351
1025
  },
1026
+ contentType: 'application/json',
1027
+ method: 'POST',
1028
+ resource: '/search/orgid/userOrgId/entities',
352
1029
  },
1030
+ ]);
1031
+
1032
+ webex.internal.dss.trigger(webex.internal.dss._getResultEventName('randomid'), {
1033
+ sequence: 1,
1034
+ a: {b: {c: ['data1']}},
1035
+ someFoundPath: ['id1'],
1036
+ });
1037
+ webex.internal.dss.trigger(webex.internal.dss._getResultEventName('randomid'), {
1038
+ sequence: 2,
1039
+ finished: true,
1040
+ a: {b: {c: ['data2']}},
1041
+ someFoundPath: ['id2'],
1042
+ someNotFoundPath: ['id3'],
1043
+ });
1044
+ webex.internal.dss.trigger(webex.internal.dss._getResultEventName('randomid'), {
1045
+ sequence: 0,
1046
+ a: {b: {c: ['data0']}},
1047
+ someFoundPath: ['id0'],
353
1048
  });
354
1049
 
355
1050
  const result = await promise;
356
- expect(result).to.deep.equal(['data0', 'data1', 'data2']);
1051
+
1052
+ expect(result).to.deep.equal({
1053
+ resultArray: ['data0', 'data1', 'data2'],
1054
+ foundArray: ['id0', 'id1', 'id2'],
1055
+ notFoundArray: ['id3'],
1056
+ });
1057
+ });
1058
+ });
1059
+
1060
+ describe('#_batchedLookup', () => {
1061
+ const checkStandardProperties = (batcher) => {
1062
+ expect(batcher.dataPath).to.equal('lookupResult.entities');
1063
+ expect(batcher.entitiesFoundPath).to.equal('lookupResult.entitiesFound');
1064
+ expect(batcher.entitiesNotFoundPath).to.equal('lookupResult.entitiesNotFound');
1065
+ expect(batcher.requestKey).to.equal('lookupValues');
1066
+ expect(batcher.config).to.deep.equal({
1067
+ batcherWait: 50,
1068
+ batcherMaxCalls: 50,
1069
+ batcherMaxWait: 150,
1070
+ requestTimeout: 6000,
1071
+ });
1072
+ };
1073
+
1074
+ it('calls batcher.request on new batcher for first lookup', async () => {
1075
+ const resource = '/lookup/orgid/userOrgId/identities';
1076
+ const response = 'response1';
1077
+
1078
+ Batcher.prototype.request = sinon.stub().returns(Promise.resolve(response));
1079
+
1080
+ expect(webex.internal.dss.batchers).to.deep.equal({});
1081
+
1082
+ const result = await webex.internal.dss._batchedLookup({
1083
+ resource,
1084
+ lookupValue: 'id1',
1085
+ });
1086
+
1087
+ const batcher = webex.internal.dss.batchers[resource];
1088
+
1089
+ expect(batcher).to.exist;
1090
+ expect(batcher.resource).to.equal(resource);
1091
+ checkStandardProperties(batcher);
1092
+
1093
+ expect(Batcher.prototype.request.getCall(0).args).to.deep.equal(['id1']);
1094
+ expect(result).to.equal(response);
1095
+ });
1096
+
1097
+ it('calls batcher.request on new batcher for lookup with new resource', async () => {
1098
+ const resource1 = '/lookup/orgid/userOrgId/identities';
1099
+ const resource2 = '/lookup/orgid/userOrgId/entityprovidertype/CI_USER';
1100
+ const response1 = 'response1';
1101
+ const response2 = 'response2';
1102
+
1103
+ Batcher.prototype.request = sinon
1104
+ .stub()
1105
+ .onFirstCall()
1106
+ .returns(Promise.resolve(response1))
1107
+ .onSecondCall()
1108
+ .returns(Promise.resolve(response2));
1109
+
1110
+ expect(webex.internal.dss.batchers).to.deep.equal({});
1111
+
1112
+ await webex.internal.dss._batchedLookup({
1113
+ resource: resource1,
1114
+ lookupValue: 'id1',
1115
+ });
1116
+
1117
+ const result = await webex.internal.dss._batchedLookup({
1118
+ resource: resource2,
1119
+ lookupValue: 'id2',
1120
+ });
1121
+
1122
+ expect(webex.internal.dss.batchers[resource1]).to.exist;
1123
+ const batcher = webex.internal.dss.batchers[resource2];
1124
+
1125
+ expect(batcher).to.exist;
1126
+ expect(batcher.resource).to.equal(resource2);
1127
+ checkStandardProperties(batcher);
1128
+
1129
+ expect(Batcher.prototype.request.getCall(1).args).to.deep.equal(['id2']);
1130
+ expect(result).to.equal(response2);
1131
+ });
1132
+
1133
+ it('calls batcher.request on existing batcher for lookup with existing reource', async () => {
1134
+ const resource1 = '/lookup/orgid/userOrgId/identities';
1135
+ const response1 = 'response1';
1136
+ const response2 = 'response2';
1137
+
1138
+ Batcher.prototype.request = sinon
1139
+ .stub()
1140
+ .onFirstCall()
1141
+ .returns(Promise.resolve(response1))
1142
+ .onSecondCall()
1143
+ .returns(Promise.resolve(response2));
1144
+
1145
+ expect(webex.internal.dss.batchers).to.deep.equal({});
1146
+
1147
+ await webex.internal.dss._batchedLookup({
1148
+ resource: resource1,
1149
+ lookupValue: 'id1',
1150
+ });
1151
+ expect(webex.internal.dss.batchers[resource1]).to.exist;
1152
+ const initialBatcher = webex.internal.dss.batchers[resource1];
1153
+
1154
+ const result = await webex.internal.dss._batchedLookup({
1155
+ resource: resource1,
1156
+ lookupValue: 'id2',
1157
+ });
1158
+
1159
+ const batcher = webex.internal.dss.batchers[resource1];
1160
+
1161
+ expect(batcher).to.equal(initialBatcher);
1162
+ expect(batcher.resource).to.equal(resource1);
1163
+ checkStandardProperties(batcher);
1164
+
1165
+ expect(Batcher.prototype.request.getCall(1).args).to.deep.equal(['id2']);
1166
+ expect(result).to.equal(response2);
1167
+ });
1168
+
1169
+ it('fails fails when mercury does not respond, later batches can still pass ok', async () => {
1170
+ // Batch 1
1171
+ const {
1172
+ promises: [p1, p2, p3],
1173
+ } = await testMakeBatchedRequests({
1174
+ requests: [
1175
+ {
1176
+ id: 'req-id-1',
1177
+ resource: '/lookup/orgid/userOrgId/identities',
1178
+ bodyParams: {lookupValues: ['id1', 'id2', 'id3']},
1179
+ },
1180
+ ],
1181
+ calls: [
1182
+ {
1183
+ method: 'lookup',
1184
+ params: {id: 'id1', shouldBatch: true},
1185
+ },
1186
+ {
1187
+ method: 'lookup',
1188
+ params: {id: 'id2', shouldBatch: true},
1189
+ },
1190
+ {
1191
+ method: 'lookup',
1192
+ params: {id: 'id3', shouldBatch: true},
1193
+ },
1194
+ ],
1195
+ });
1196
+
1197
+ // Batch 2
1198
+ const {
1199
+ promises: [p4],
1200
+ } = await testMakeBatchedRequests({
1201
+ requests: [
1202
+ {
1203
+ id: 'randomid',
1204
+ resource: '/lookup/orgid/userOrgId/identities',
1205
+ bodyParams: {lookupValues: ['id4']},
1206
+ },
1207
+ ],
1208
+ calls: [
1209
+ {
1210
+ method: 'lookup',
1211
+ params: {id: 'id4', shouldBatch: true},
1212
+ },
1213
+ ],
1214
+ });
1215
+
1216
+ // Batch 1 - only 1 mercury response out of 2 received
1217
+ mercuryCallbacks['event:directory.lookup'](
1218
+ createData('req-id-1', 0, false, 'lookupResult', {
1219
+ entitiesFound: ['id1', 'id3'],
1220
+ entities: ['data1', 'data3'],
1221
+ })
1222
+ );
1223
+
1224
+ // Batch 2 - response
1225
+ mercuryCallbacks['event:directory.lookup'](
1226
+ createData('randomid', 0, true, 'lookupResult', {entitiesNotFound: ['id4']})
1227
+ );
1228
+
1229
+ // Timeout
1230
+ await clock.tickAsync(6000);
1231
+
1232
+ return Promise.all([
1233
+ assert.isRejected(
1234
+ p1,
1235
+ 'The DSS did not respond within 6000 ms.' +
1236
+ '\n Request Id: req-id-1' +
1237
+ '\n Resource: /lookup/orgid/userOrgId/identities' +
1238
+ '\n Params: {"lookupValues":["id1","id2","id3"]}'
1239
+ ),
1240
+ assert.isRejected(
1241
+ p2,
1242
+ 'The DSS did not respond within 6000 ms.' +
1243
+ '\n Request Id: req-id-1' +
1244
+ '\n Resource: /lookup/orgid/userOrgId/identities' +
1245
+ '\n Params: {"lookupValues":["id1","id2","id3"]}'
1246
+ ),
1247
+ assert.isRejected(
1248
+ p3,
1249
+ 'The DSS did not respond within 6000 ms.' +
1250
+ '\n Request Id: req-id-1' +
1251
+ '\n Resource: /lookup/orgid/userOrgId/identities' +
1252
+ '\n Params: {"lookupValues":["id1","id2","id3"]}'
1253
+ ),
1254
+ assert.isFulfilled(p4),
1255
+ ]);
1256
+ });
1257
+ });
1258
+
1259
+ describe('#searchPlaces', () => {
1260
+ it('calls _request correctly', async () => {
1261
+ webex.internal.device.orgId = 'userOrgId';
1262
+ webex.internal.dss._request = sinon.stub().returns(Promise.resolve('some return value'));
1263
+
1264
+ const result = await webex.internal.dss.searchPlaces({
1265
+ resultSize: 100,
1266
+ queryString: 'query',
1267
+ isOnlySchedulableRooms: true,
1268
+ });
1269
+ expect(webex.internal.dss._request.getCall(0).args).to.deep.equal([
1270
+ {
1271
+ dataPath: 'directoryEntities',
1272
+ resource: '/search/orgid/userOrgId/places',
1273
+ params: {
1274
+ queryString: 'query',
1275
+ resultSize: 100,
1276
+ isOnlySchedulableRooms: true,
1277
+ },
1278
+ },
1279
+ ]);
1280
+ expect(result).to.equal('some return value');
1281
+ });
1282
+
1283
+ it('works correctly', async () => {
1284
+ await testMakeRequest({
1285
+ method: 'searchPlaces',
1286
+ resource: '/search/orgid/userOrgId/places',
1287
+ params: {
1288
+ isOnlySchedulableRooms: true,
1289
+ resultSize: 100,
1290
+ queryString: 'query',
1291
+ },
1292
+ bodyParams: {
1293
+ isOnlySchedulableRooms: true,
1294
+ resultSize: 100,
1295
+ queryString: 'query',
1296
+ },
1297
+ });
357
1298
  });
358
1299
  });
359
1300
  });