@webex/internal-plugin-dss 3.0.0-bnr.5 → 3.0.0-next.2

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