@webex/webex-core 3.12.0-webex-services-ready.1 → 3.12.0-webex-services-ready.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/batcher.js +1 -1
- package/dist/lib/credentials/credentials.js +1 -1
- package/dist/lib/credentials/token.js +1 -1
- package/dist/lib/services/services.js +121 -35
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +118 -34
- package/dist/lib/services-v2/services-v2.js.map +1 -1
- package/dist/plugins/logger.js +1 -1
- package/dist/webex-core.js +2 -2
- package/package.json +3 -3
- package/src/lib/services/services.js +87 -10
- package/src/lib/services-v2/services-v2.ts +85 -9
- package/test/integration/spec/services/services.js +39 -17
- package/test/integration/spec/services-v2/services-v2.js +39 -17
- package/test/unit/spec/services/services.js +411 -322
- package/test/unit/spec/services-v2/services-v2.ts +340 -214
|
@@ -34,53 +34,6 @@ describe('webex-core', () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
describe('#initialize', () => {
|
|
37
|
-
it('listens for "loaded" event instead of "ready" to avoid deadlock', () => {
|
|
38
|
-
services.listenToOnce = sinon.stub();
|
|
39
|
-
services.initialize();
|
|
40
|
-
|
|
41
|
-
// Second listenToOnce call should be for 'loaded' event
|
|
42
|
-
assert.equal(services.listenToOnce.getCall(1).args[1], 'loaded');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('services.ready starts as false', () => {
|
|
46
|
-
assert.isFalse(services.ready);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('sets ready to true and triggers services:initialized when initialization succeeds with credentials', async () => {
|
|
50
|
-
services.listenToOnce = sinon.stub();
|
|
51
|
-
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
52
|
-
services.trigger = sinon.stub();
|
|
53
|
-
services.webex.credentials = {
|
|
54
|
-
supertoken: {
|
|
55
|
-
access_token: 'token',
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
services.initialize();
|
|
60
|
-
|
|
61
|
-
// call the onLoaded callback
|
|
62
|
-
services.listenToOnce.getCall(1).args[2]();
|
|
63
|
-
await waitForAsync();
|
|
64
|
-
|
|
65
|
-
assert.isTrue(services.ready);
|
|
66
|
-
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('sets ready to true and triggers services:initialized when initialization succeeds without credentials', async () => {
|
|
70
|
-
services.listenToOnce = sinon.stub();
|
|
71
|
-
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
72
|
-
services.trigger = sinon.stub();
|
|
73
|
-
|
|
74
|
-
services.initialize();
|
|
75
|
-
|
|
76
|
-
// call the onLoaded callback
|
|
77
|
-
services.listenToOnce.getCall(1).args[2]();
|
|
78
|
-
await waitForAsync();
|
|
79
|
-
|
|
80
|
-
assert.isTrue(services.ready);
|
|
81
|
-
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
37
|
it('initFailed is false when initialization succeeds and credentials are available', async () => {
|
|
85
38
|
services.listenToOnce = sinon.stub();
|
|
86
39
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
@@ -92,7 +45,8 @@ describe('webex-core', () => {
|
|
|
92
45
|
|
|
93
46
|
services.initialize();
|
|
94
47
|
|
|
95
|
-
// call the
|
|
48
|
+
// call the onReady callback
|
|
49
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
96
50
|
services.listenToOnce.getCall(1).args[2]();
|
|
97
51
|
await waitForAsync();
|
|
98
52
|
|
|
@@ -105,7 +59,8 @@ describe('webex-core', () => {
|
|
|
105
59
|
|
|
106
60
|
services.initialize();
|
|
107
61
|
|
|
108
|
-
// call the
|
|
62
|
+
// call the onReady callback
|
|
63
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
109
64
|
services.listenToOnce.getCall(1).args[2]();
|
|
110
65
|
await waitForAsync();
|
|
111
66
|
|
|
@@ -116,7 +71,7 @@ describe('webex-core', () => {
|
|
|
116
71
|
{error: new Error('failed'), expectedMessage: 'failed'},
|
|
117
72
|
{error: undefined, expectedMessage: undefined},
|
|
118
73
|
])(
|
|
119
|
-
'sets initFailed to true when collectPreauthCatalog errors
|
|
74
|
+
'sets initFailed to true when collectPreauthCatalog errors',
|
|
120
75
|
async ({error, expectedMessage}) => {
|
|
121
76
|
services.collectPreauthCatalog = sinon.stub().callsFake(() => {
|
|
122
77
|
return Promise.reject(error);
|
|
@@ -124,18 +79,16 @@ describe('webex-core', () => {
|
|
|
124
79
|
|
|
125
80
|
services.listenToOnce = sinon.stub();
|
|
126
81
|
services.logger.error = sinon.stub();
|
|
127
|
-
services.trigger = sinon.stub();
|
|
128
82
|
|
|
129
83
|
services.initialize();
|
|
130
84
|
|
|
131
|
-
// call the
|
|
85
|
+
// call the onReady callback
|
|
86
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
132
87
|
services.listenToOnce.getCall(1).args[2]();
|
|
133
88
|
|
|
134
89
|
await waitForAsync();
|
|
135
90
|
|
|
136
91
|
assert.isTrue(services.initFailed);
|
|
137
|
-
assert.isTrue(services.ready);
|
|
138
|
-
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
139
92
|
sinon.assert.calledWith(
|
|
140
93
|
services.logger.error,
|
|
141
94
|
`services: failed to init initial services when no credentials available, ${expectedMessage}`
|
|
@@ -147,7 +100,7 @@ describe('webex-core', () => {
|
|
|
147
100
|
{error: new Error('failed'), expectedMessage: 'failed'},
|
|
148
101
|
{error: undefined, expectedMessage: undefined},
|
|
149
102
|
])(
|
|
150
|
-
'sets initFailed to true when initServiceCatalogs errors
|
|
103
|
+
'sets initFailed to true when initServiceCatalogs errors',
|
|
151
104
|
async ({error, expectedMessage}) => {
|
|
152
105
|
services.initServiceCatalogs = sinon.stub().callsFake(() => {
|
|
153
106
|
return Promise.reject(error);
|
|
@@ -160,18 +113,16 @@ describe('webex-core', () => {
|
|
|
160
113
|
|
|
161
114
|
services.listenToOnce = sinon.stub();
|
|
162
115
|
services.logger.error = sinon.stub();
|
|
163
|
-
services.trigger = sinon.stub();
|
|
164
116
|
|
|
165
117
|
services.initialize();
|
|
166
118
|
|
|
167
|
-
// call the
|
|
119
|
+
// call the onReady callback
|
|
120
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
168
121
|
services.listenToOnce.getCall(1).args[2]();
|
|
169
122
|
|
|
170
123
|
await waitForAsync();
|
|
171
124
|
|
|
172
125
|
assert.isTrue(services.initFailed);
|
|
173
|
-
assert.isTrue(services.ready);
|
|
174
|
-
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
175
126
|
sinon.assert.calledWith(
|
|
176
127
|
services.logger.error,
|
|
177
128
|
`services: failed to init initial services when credentials available, ${expectedMessage}`
|
|
@@ -179,98 +130,299 @@ describe('webex-core', () => {
|
|
|
179
130
|
}
|
|
180
131
|
);
|
|
181
132
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(false));
|
|
186
|
-
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
187
|
-
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
188
|
-
services.trigger = sinon.stub();
|
|
189
|
-
// Ensure no credentials so we go to the preauth path
|
|
190
|
-
services.webex.credentials = {};
|
|
133
|
+
it('sets services.ready to true when waitForCatalogInit is disabled (default)', () => {
|
|
134
|
+
services.listenToOnce = sinon.stub();
|
|
135
|
+
services.initialize();
|
|
191
136
|
|
|
192
|
-
|
|
137
|
+
// Fire the change:config handler which decides gated-vs-ungated and, in
|
|
138
|
+
// ungated mode, flips services.ready to true.
|
|
139
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
193
140
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
testCatalog.status.postauth.ready = false;
|
|
141
|
+
assert.isTrue(services.ready, 'services.ready should be true so it does not block webex.ready');
|
|
142
|
+
});
|
|
197
143
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
144
|
+
it('listens on the "ready" event when waitForCatalogInit is disabled (default)', () => {
|
|
145
|
+
services.listenToOnce = sinon.stub();
|
|
146
|
+
services.initialize();
|
|
201
147
|
|
|
202
|
-
|
|
203
|
-
|
|
148
|
+
// Fire change:config to trigger the mode-specific listener registration.
|
|
149
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
204
150
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
151
|
+
const [, event] = services.listenToOnce.getCall(1).args;
|
|
152
|
+
assert.equal(event, 'ready', 'default path listens on webex ready');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
208
155
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
156
|
+
describe('#initialize (waitForCatalogInit=true)', () => {
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
services.webex.config = {
|
|
159
|
+
...(services.webex.config || {}),
|
|
160
|
+
services: {waitForCatalogInit: true},
|
|
161
|
+
};
|
|
162
|
+
// Reset ready flag: the outer beforeEach constructs services which
|
|
163
|
+
// runs initialize() once with no config (ungated path, flips ready to
|
|
164
|
+
// true). We reset here so tests that call initialize() a second time
|
|
165
|
+
// with the flag enabled observe the gated behavior from a clean state.
|
|
166
|
+
services.ready = false;
|
|
167
|
+
});
|
|
212
168
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
217
|
-
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
218
|
-
services.trigger = sinon.stub();
|
|
219
|
-
// Ensure no credentials so we go to the preauth path
|
|
220
|
-
services.webex.credentials = {};
|
|
169
|
+
it('leaves services.ready=false after initialize (does not flip until finalize)', () => {
|
|
170
|
+
services.listenToOnce = sinon.stub();
|
|
171
|
+
services.initialize();
|
|
221
172
|
|
|
222
|
-
|
|
173
|
+
assert.isFalse(services.ready, 'services.ready should block webex.ready until finalize');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('listens on the "loaded" event (not "ready") to avoid deadlock', () => {
|
|
177
|
+
services.listenToOnce = sinon.stub();
|
|
178
|
+
services.initialize();
|
|
223
179
|
|
|
224
|
-
|
|
225
|
-
|
|
180
|
+
// Fire change:config to trigger the mode-specific listener registration.
|
|
181
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
226
182
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
183
|
+
const [, event] = services.listenToOnce.getCall(1).args;
|
|
184
|
+
assert.equal(event, 'loaded', 'gated path must listen on loaded to avoid deadlock');
|
|
185
|
+
});
|
|
230
186
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
187
|
+
it('flips services.ready=true after init succeeds with credentials', async () => {
|
|
188
|
+
services.listenToOnce = sinon.stub();
|
|
189
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
190
|
+
services.webex.credentials = {
|
|
191
|
+
supertoken: {access_token: 'token'},
|
|
192
|
+
};
|
|
234
193
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
194
|
+
services.initialize();
|
|
195
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
196
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
197
|
+
await waitForAsync();
|
|
238
198
|
|
|
239
|
-
|
|
240
|
-
|
|
199
|
+
assert.isTrue(services.ready);
|
|
200
|
+
});
|
|
241
201
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
246
|
-
services.initServiceCatalogs = sinon.stub().rejects(new Error('auth failed'));
|
|
247
|
-
services.logger.error = sinon.stub();
|
|
248
|
-
services.trigger = sinon.stub();
|
|
249
|
-
// Ensure no credentials so we go to the preauth path
|
|
250
|
-
services.webex.credentials = {};
|
|
202
|
+
it('flips services.ready=true after init succeeds without credentials (preauth path)', async () => {
|
|
203
|
+
services.listenToOnce = sinon.stub();
|
|
204
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
251
205
|
|
|
252
|
-
|
|
206
|
+
services.initialize();
|
|
207
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
208
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
209
|
+
await waitForAsync();
|
|
253
210
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
testCatalog.status.postauth.ready = false;
|
|
211
|
+
assert.isTrue(services.ready);
|
|
212
|
+
});
|
|
257
213
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
214
|
+
it('flips services.ready=true even when initServiceCatalogs errors, so webex.ready can fire', async () => {
|
|
215
|
+
services.listenToOnce = sinon.stub();
|
|
216
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.reject(new Error('boom')));
|
|
217
|
+
services.webex.credentials = {
|
|
218
|
+
supertoken: {access_token: 'token'},
|
|
219
|
+
};
|
|
220
|
+
services.logger.error = sinon.stub();
|
|
261
221
|
|
|
262
|
-
|
|
263
|
-
|
|
222
|
+
services.initialize();
|
|
223
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
224
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
225
|
+
await waitForAsync();
|
|
264
226
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
227
|
+
assert.isTrue(services.initFailed);
|
|
228
|
+
assert.isTrue(services.ready);
|
|
229
|
+
});
|
|
268
230
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
231
|
+
it('flips services.ready=true even when collectPreauthCatalog errors', async () => {
|
|
232
|
+
services.listenToOnce = sinon.stub();
|
|
233
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.reject(new Error('boom')));
|
|
234
|
+
services.logger.error = sinon.stub();
|
|
235
|
+
|
|
236
|
+
services.initialize();
|
|
237
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
238
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
239
|
+
await waitForAsync();
|
|
240
|
+
|
|
241
|
+
assert.isTrue(services.initFailed);
|
|
242
|
+
assert.isTrue(services.ready);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('flips services.ready=true when init times out', async () => {
|
|
246
|
+
const clock = sinon.useFakeTimers();
|
|
247
|
+
services.listenToOnce = sinon.stub();
|
|
248
|
+
services.initServiceCatalogs = sinon.stub().returns(new Promise(() => {}));
|
|
249
|
+
services.webex.credentials = {
|
|
250
|
+
supertoken: {access_token: 'token'},
|
|
251
|
+
};
|
|
252
|
+
services.logger.error = sinon.stub();
|
|
253
|
+
|
|
254
|
+
services.initialize();
|
|
255
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
256
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
257
|
+
|
|
258
|
+
await clock.tickAsync(15_001);
|
|
259
|
+
clock.restore();
|
|
260
|
+
|
|
261
|
+
assert.isTrue(services.initFailed);
|
|
262
|
+
assert.isTrue(services.ready);
|
|
263
|
+
sinon.assert.calledWithMatch(
|
|
264
|
+
services.logger.error,
|
|
265
|
+
/services: init timed out after 15000ms/
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('awaits an in-flight credentials refresh before flipping services.ready=true', async () => {
|
|
270
|
+
services.listenToOnce = sinon.stub();
|
|
271
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
272
|
+
|
|
273
|
+
let onChangeIsRefreshing: (() => void) | undefined;
|
|
274
|
+
services.webex.credentials = {
|
|
275
|
+
supertoken: {access_token: 'token'},
|
|
276
|
+
isRefreshing: true,
|
|
277
|
+
once: sinon.stub().callsFake((event: string, cb: () => void) => {
|
|
278
|
+
if (event === 'change:isRefreshing') onChangeIsRefreshing = cb;
|
|
279
|
+
}),
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
services.initialize();
|
|
283
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
284
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
285
|
+
await waitForAsync();
|
|
286
|
+
|
|
287
|
+
assert.isFalse(services.ready, 'must not flip ready while refresh is in flight');
|
|
288
|
+
assert.isFunction(onChangeIsRefreshing, 'must subscribe to change:isRefreshing');
|
|
289
|
+
sinon.assert.calledWith(services.webex.credentials.once, 'change:isRefreshing');
|
|
290
|
+
|
|
291
|
+
onChangeIsRefreshing!();
|
|
292
|
+
await waitForAsync();
|
|
293
|
+
|
|
294
|
+
assert.isTrue(services.ready);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('registers a change:canAuthorize listener when there is no supertoken (fresh-login path)', async () => {
|
|
298
|
+
services.listenToOnce = sinon.stub();
|
|
299
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
300
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
301
|
+
|
|
302
|
+
services.initialize();
|
|
303
|
+
// initialize() creates a new catalog and replaces the WeakMap entry.
|
|
304
|
+
const currentCatalog = services._getCatalog();
|
|
305
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
306
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
307
|
+
await waitForAsync();
|
|
308
|
+
|
|
309
|
+
const freshLoginCall = services.listenToOnce
|
|
310
|
+
.getCalls()
|
|
311
|
+
.find((call: any) => call.args[1] === 'change:canAuthorize');
|
|
312
|
+
assert.isDefined(freshLoginCall, 'expected change:canAuthorize listener to be registered');
|
|
313
|
+
|
|
314
|
+
services.webex.canAuthorize = true;
|
|
315
|
+
currentCatalog.status.postauth.ready = false;
|
|
316
|
+
freshLoginCall!.args[2]();
|
|
317
|
+
|
|
318
|
+
assert.isTrue(
|
|
319
|
+
services.initServiceCatalogs.called,
|
|
320
|
+
'expected postauth catalog init after canAuthorize flips'
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
// After the postauth catalog init resolves, catalog.isReady must flip
|
|
324
|
+
// to true so downstream consumers see the catalog as usable.
|
|
325
|
+
await waitForAsync();
|
|
326
|
+
|
|
327
|
+
assert.isTrue(
|
|
328
|
+
currentCatalog.isReady,
|
|
329
|
+
'expected catalog.isReady=true after postauth catalog init succeeds'
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('does not re-init postauth catalog when canAuthorize fires but catalog is already ready', async () => {
|
|
334
|
+
services.listenToOnce = sinon.stub();
|
|
335
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
336
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
337
|
+
|
|
338
|
+
services.initialize();
|
|
339
|
+
// initialize() creates a new catalog; grab the current one.
|
|
340
|
+
const currentCatalog = services._getCatalog();
|
|
341
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
342
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
343
|
+
await waitForAsync();
|
|
344
|
+
|
|
345
|
+
const freshLoginCall = services.listenToOnce
|
|
346
|
+
.getCalls()
|
|
347
|
+
.find((call: any) => call.args[1] === 'change:canAuthorize');
|
|
348
|
+
|
|
349
|
+
services.webex.canAuthorize = true;
|
|
350
|
+
currentCatalog.status.postauth.ready = true;
|
|
351
|
+
freshLoginCall!.args[2]();
|
|
352
|
+
|
|
353
|
+
assert.isFalse(
|
|
354
|
+
services.initServiceCatalogs.called,
|
|
355
|
+
'must not re-fetch postauth catalog if it is already ready'
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('finalizes ready when cache is warm (skips network fetch)', async () => {
|
|
360
|
+
services.listenToOnce = sinon.stub();
|
|
361
|
+
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(true));
|
|
362
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
363
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
364
|
+
|
|
365
|
+
services.initialize();
|
|
366
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
367
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
368
|
+
await waitForAsync();
|
|
369
|
+
await waitForAsync();
|
|
370
|
+
|
|
371
|
+
assert.isTrue(services.ready);
|
|
372
|
+
assert.isFalse(
|
|
373
|
+
services.initServiceCatalogs.called,
|
|
374
|
+
'must skip network fetch when cache is warm'
|
|
375
|
+
);
|
|
376
|
+
assert.isFalse(
|
|
377
|
+
services.collectPreauthCatalog.called,
|
|
378
|
+
'must skip preauth fetch when cache is warm'
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
describe('#_finalizeReady', () => {
|
|
384
|
+
beforeEach(() => {
|
|
385
|
+
// Outer beforeEach construction already ran initialize() (ungated,
|
|
386
|
+
// sets ready=true). Reset so _finalizeReady's flip is observable.
|
|
387
|
+
services.ready = false;
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('sets ready=true immediately when credentials are not refreshing', async () => {
|
|
391
|
+
services.webex.credentials = {isRefreshing: false};
|
|
392
|
+
assert.isFalse(services.ready);
|
|
393
|
+
|
|
394
|
+
await services._finalizeReady();
|
|
395
|
+
|
|
396
|
+
assert.isTrue(services.ready);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('sets ready=true when credentials are missing', async () => {
|
|
400
|
+
services.webex.credentials = undefined;
|
|
401
|
+
|
|
402
|
+
await services._finalizeReady();
|
|
403
|
+
|
|
404
|
+
assert.isTrue(services.ready);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('awaits change:isRefreshing before setting ready=true', async () => {
|
|
408
|
+
let onChangeIsRefreshing: (() => void) | undefined;
|
|
409
|
+
services.webex.credentials = {
|
|
410
|
+
isRefreshing: true,
|
|
411
|
+
once: sinon.stub().callsFake((event: string, cb: () => void) => {
|
|
412
|
+
if (event === 'change:isRefreshing') onChangeIsRefreshing = cb;
|
|
413
|
+
}),
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const settled = services._finalizeReady();
|
|
417
|
+
await waitForAsync();
|
|
418
|
+
|
|
419
|
+
assert.isFalse(services.ready, 'ready must not flip while refresh is in flight');
|
|
420
|
+
assert.isFunction(onChangeIsRefreshing);
|
|
421
|
+
|
|
422
|
+
onChangeIsRefreshing!();
|
|
423
|
+
await settled;
|
|
424
|
+
|
|
425
|
+
assert.isTrue(services.ready);
|
|
274
426
|
});
|
|
275
427
|
});
|
|
276
428
|
|
|
@@ -425,7 +577,7 @@ describe('webex-core', () => {
|
|
|
425
577
|
|
|
426
578
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
427
579
|
services.webex.credentials = {
|
|
428
|
-
getOrgId: sinon.stub().returns('')
|
|
580
|
+
getOrgId: sinon.stub().returns('')
|
|
429
581
|
};
|
|
430
582
|
catalog.status = {};
|
|
431
583
|
});
|
|
@@ -466,7 +618,7 @@ describe('webex-core', () => {
|
|
|
466
618
|
const serviceGroup = 'postauth';
|
|
467
619
|
const hostmap = {services: [{hostmap: 'hostmap'}]};
|
|
468
620
|
|
|
469
|
-
services._formatReceivedHostmap = sinon.stub().returns({services: [{some: 'hostmap'}]});
|
|
621
|
+
services._formatReceivedHostmap = sinon.stub().returns({services : [{some: 'hostmap'}]});
|
|
470
622
|
|
|
471
623
|
catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
|
|
472
624
|
|
|
@@ -482,7 +634,7 @@ describe('webex-core', () => {
|
|
|
482
634
|
const serviceGroup = 'postauth';
|
|
483
635
|
const hostmap = {};
|
|
484
636
|
|
|
485
|
-
services._formatReceivedHostmap = sinon.stub().returns({services: undefined});
|
|
637
|
+
services._formatReceivedHostmap = sinon.stub().returns({services : undefined});
|
|
486
638
|
|
|
487
639
|
catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
|
|
488
640
|
|
|
@@ -721,13 +873,13 @@ describe('webex-core', () => {
|
|
|
721
873
|
});
|
|
722
874
|
|
|
723
875
|
describe('#invalidateCache', () => {
|
|
724
|
-
beforeEach(() => {
|
|
876
|
+
beforeEach( () => {
|
|
725
877
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
726
878
|
services.webex.credentials = {
|
|
727
|
-
getOrgId: sinon.stub().returns('')
|
|
879
|
+
getOrgId: sinon.stub().returns('')
|
|
728
880
|
};
|
|
729
881
|
catalog.status = {};
|
|
730
|
-
})
|
|
882
|
+
})
|
|
731
883
|
it('should log the timestamp parameter', async () => {
|
|
732
884
|
const timestamp = '1234567890';
|
|
733
885
|
services.logger.info = sinon.stub();
|
|
@@ -735,11 +887,7 @@ describe('webex-core', () => {
|
|
|
735
887
|
|
|
736
888
|
await services.invalidateCache(timestamp);
|
|
737
889
|
|
|
738
|
-
assert.calledWith(
|
|
739
|
-
services.logger.info,
|
|
740
|
-
'services: invalidate cache, timestamp:',
|
|
741
|
-
timestamp
|
|
742
|
-
);
|
|
890
|
+
assert.calledWith(services.logger.info, 'services: invalidate cache, timestamp:', timestamp);
|
|
743
891
|
});
|
|
744
892
|
|
|
745
893
|
it('should call initServiceCatalogs when invalidate timestamp is newer than catalog timestamp', async () => {
|
|
@@ -866,30 +1014,30 @@ describe('webex-core', () => {
|
|
|
866
1014
|
// Arrange: seed internal _services with mobius (including duplicate baseUrl)
|
|
867
1015
|
services._services = [
|
|
868
1016
|
{
|
|
869
|
-
id:
|
|
870
|
-
serviceName: 'mobius',
|
|
871
|
-
serviceUrls: [
|
|
872
|
-
{baseUrl: 'https://mobius-us-east-2.prod.infra.webex.com/api/v1', priority: 5},
|
|
873
|
-
{baseUrl: 'https://mobius-eu-central-1.prod.infra.webex.com/api/v1', priority: 10},
|
|
874
|
-
{baseUrl: 'https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1', priority: 15}, // duplicate
|
|
1017
|
+
"id": "urn:TEAM:us-east-2_a:mobius",
|
|
1018
|
+
"serviceName": 'mobius',
|
|
1019
|
+
"serviceUrls": [
|
|
1020
|
+
{"baseUrl": 'https://mobius-us-east-2.prod.infra.webex.com/api/v1', "priority": 5},
|
|
1021
|
+
{"baseUrl": 'https://mobius-eu-central-1.prod.infra.webex.com/api/v1', "priority": 10},
|
|
1022
|
+
{"baseUrl": 'https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1', "priority": 15}, // duplicate
|
|
875
1023
|
],
|
|
876
1024
|
},
|
|
877
1025
|
{
|
|
878
|
-
id:
|
|
879
|
-
serviceName:
|
|
880
|
-
serviceUrls: [
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1026
|
+
"id": "urn:TEAM:ap-southeast-2_m:mobius",
|
|
1027
|
+
"serviceName": "mobius",
|
|
1028
|
+
"serviceUrls": [
|
|
1029
|
+
{
|
|
1030
|
+
"baseUrl": "https://mobius-me-central-1.prod.infra.webex.com/api/v1",
|
|
1031
|
+
"priority": 5
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
"baseUrl": "https://mobius-eu-central-1.prod.infra.webex.com/api/v1",
|
|
1035
|
+
"priority": 10
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
"baseUrl": "https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1",
|
|
1039
|
+
"priority": 15
|
|
1040
|
+
},
|
|
893
1041
|
],
|
|
894
1042
|
},
|
|
895
1043
|
// Non-mobius service should be ignored by getMobiusClusters
|
|
@@ -907,64 +1055,42 @@ describe('webex-core', () => {
|
|
|
907
1055
|
assert.deepEqual(
|
|
908
1056
|
clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
|
|
909
1057
|
[
|
|
910
|
-
{
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
priority: 5,
|
|
915
|
-
},
|
|
916
|
-
{
|
|
917
|
-
host: 'mobius-eu-central-1.prod.infra.webex.com',
|
|
918
|
-
id: 'urn:TEAM:us-east-2_a:mobius',
|
|
919
|
-
ttl: 0,
|
|
920
|
-
priority: 10,
|
|
921
|
-
},
|
|
922
|
-
{
|
|
923
|
-
host: 'mobius-ap-southeast-2.prod.infra.webex.com',
|
|
924
|
-
id: 'urn:TEAM:us-east-2_a:mobius',
|
|
925
|
-
ttl: 0,
|
|
926
|
-
priority: 15,
|
|
927
|
-
},
|
|
928
|
-
{
|
|
929
|
-
host: 'mobius-me-central-1.prod.infra.webex.com',
|
|
930
|
-
id: 'urn:TEAM:ap-southeast-2_m:mobius',
|
|
931
|
-
ttl: 0,
|
|
932
|
-
priority: 5,
|
|
933
|
-
},
|
|
1058
|
+
{host: 'mobius-us-east-2.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 5},
|
|
1059
|
+
{host: 'mobius-eu-central-1.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 10},
|
|
1060
|
+
{host: 'mobius-ap-southeast-2.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 15},
|
|
1061
|
+
{host: 'mobius-me-central-1.prod.infra.webex.com', id: 'urn:TEAM:ap-southeast-2_m:mobius', ttl: 0, priority: 5},
|
|
934
1062
|
]
|
|
935
1063
|
);
|
|
936
1064
|
});
|
|
937
1065
|
});
|
|
938
|
-
|
|
1066
|
+
|
|
939
1067
|
describe('#isValidHost', () => {
|
|
940
1068
|
beforeEach(() => {
|
|
941
1069
|
// Setting up a mock services list
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
},
|
|
967
|
-
];
|
|
1070
|
+
services._services = [{
|
|
1071
|
+
"id": "urn:IDENTITY:PC75:adminAudit",
|
|
1072
|
+
"serviceName": "adminAudit",
|
|
1073
|
+
"serviceUrls": [
|
|
1074
|
+
{
|
|
1075
|
+
"baseUrl": "https://audit-ci-r.wbx2.com/audit-ci/api/v2",
|
|
1076
|
+
"priority": 5
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
"baseUrl": "https://audit-ci-t.wbx2.com/audit-ci/api/v2",
|
|
1080
|
+
"priority": 10
|
|
1081
|
+
}
|
|
1082
|
+
]
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
"id": "urn:IDENTITY:PC75:cdf",
|
|
1086
|
+
"serviceName": "cdf",
|
|
1087
|
+
"serviceUrls": [
|
|
1088
|
+
{
|
|
1089
|
+
"baseUrl": "https://wapdavis.webex.com/davis/api/v1",
|
|
1090
|
+
"priority": 5
|
|
1091
|
+
}
|
|
1092
|
+
]
|
|
1093
|
+
}];
|
|
968
1094
|
});
|
|
969
1095
|
afterAll(() => {
|
|
970
1096
|
// Clean up the mock services list
|