@webex/webex-core 3.12.0-webex-services-ready.1 → 3.12.0-webex-services-ready.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.
- 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 -37
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +118 -36
- 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 +92 -19
- package/src/lib/services-v2/services-v2.ts +90 -18
- 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 +402 -322
- package/test/unit/spec/services-v2/services-v2.ts +331 -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,290 @@ 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
|
+
});
|
|
223
175
|
|
|
224
|
-
|
|
225
|
-
|
|
176
|
+
it('listens on the "loaded" event (not "ready") to avoid deadlock', () => {
|
|
177
|
+
services.listenToOnce = sinon.stub();
|
|
178
|
+
services.initialize();
|
|
226
179
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
await waitForAsync();
|
|
180
|
+
// Fire change:config to trigger the mode-specific listener registration.
|
|
181
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
230
182
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
183
|
+
const [, event] = services.listenToOnce.getCall(1).args;
|
|
184
|
+
assert.equal(event, 'loaded', 'gated path must listen on loaded to avoid deadlock');
|
|
185
|
+
});
|
|
234
186
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
+
};
|
|
238
193
|
|
|
239
|
-
|
|
240
|
-
|
|
194
|
+
services.initialize();
|
|
195
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
196
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
197
|
+
await waitForAsync();
|
|
241
198
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(false));
|
|
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 = {};
|
|
199
|
+
assert.isTrue(services.ready);
|
|
200
|
+
});
|
|
251
201
|
|
|
252
|
-
|
|
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());
|
|
253
205
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
206
|
+
services.initialize();
|
|
207
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
208
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
209
|
+
await waitForAsync();
|
|
257
210
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
await waitForAsync();
|
|
211
|
+
assert.isTrue(services.ready);
|
|
212
|
+
});
|
|
261
213
|
|
|
262
|
-
|
|
263
|
-
|
|
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();
|
|
264
221
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
222
|
+
services.initialize();
|
|
223
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
224
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
225
|
+
await waitForAsync();
|
|
268
226
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
227
|
+
assert.isTrue(services.initFailed);
|
|
228
|
+
assert.isTrue(services.ready);
|
|
229
|
+
});
|
|
230
|
+
|
|
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
|
+
|
|
324
|
+
it('does not re-init postauth catalog when canAuthorize fires but catalog is already ready', async () => {
|
|
325
|
+
services.listenToOnce = sinon.stub();
|
|
326
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
327
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
328
|
+
|
|
329
|
+
services.initialize();
|
|
330
|
+
// initialize() creates a new catalog; grab the current one.
|
|
331
|
+
const currentCatalog = services._getCatalog();
|
|
332
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
333
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
334
|
+
await waitForAsync();
|
|
335
|
+
|
|
336
|
+
const freshLoginCall = services.listenToOnce
|
|
337
|
+
.getCalls()
|
|
338
|
+
.find((call: any) => call.args[1] === 'change:canAuthorize');
|
|
339
|
+
|
|
340
|
+
services.webex.canAuthorize = true;
|
|
341
|
+
currentCatalog.status.postauth.ready = true;
|
|
342
|
+
freshLoginCall!.args[2]();
|
|
343
|
+
|
|
344
|
+
assert.isFalse(
|
|
345
|
+
services.initServiceCatalogs.called,
|
|
346
|
+
'must not re-fetch postauth catalog if it is already ready'
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('finalizes ready when cache is warm (skips network fetch)', async () => {
|
|
351
|
+
services.listenToOnce = sinon.stub();
|
|
352
|
+
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(true));
|
|
353
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
354
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
355
|
+
|
|
356
|
+
services.initialize();
|
|
357
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
358
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
359
|
+
await waitForAsync();
|
|
360
|
+
await waitForAsync();
|
|
361
|
+
|
|
362
|
+
assert.isTrue(services.ready);
|
|
363
|
+
assert.isFalse(
|
|
364
|
+
services.initServiceCatalogs.called,
|
|
365
|
+
'must skip network fetch when cache is warm'
|
|
366
|
+
);
|
|
367
|
+
assert.isFalse(
|
|
368
|
+
services.collectPreauthCatalog.called,
|
|
369
|
+
'must skip preauth fetch when cache is warm'
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe('#_finalizeReady', () => {
|
|
375
|
+
beforeEach(() => {
|
|
376
|
+
// Outer beforeEach construction already ran initialize() (ungated,
|
|
377
|
+
// sets ready=true). Reset so _finalizeReady's flip is observable.
|
|
378
|
+
services.ready = false;
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('sets ready=true immediately when credentials are not refreshing', async () => {
|
|
382
|
+
services.webex.credentials = {isRefreshing: false};
|
|
383
|
+
assert.isFalse(services.ready);
|
|
384
|
+
|
|
385
|
+
await services._finalizeReady();
|
|
386
|
+
|
|
387
|
+
assert.isTrue(services.ready);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('sets ready=true when credentials are missing', async () => {
|
|
391
|
+
services.webex.credentials = undefined;
|
|
392
|
+
|
|
393
|
+
await services._finalizeReady();
|
|
394
|
+
|
|
395
|
+
assert.isTrue(services.ready);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('awaits change:isRefreshing before setting ready=true', async () => {
|
|
399
|
+
let onChangeIsRefreshing: (() => void) | undefined;
|
|
400
|
+
services.webex.credentials = {
|
|
401
|
+
isRefreshing: true,
|
|
402
|
+
once: sinon.stub().callsFake((event: string, cb: () => void) => {
|
|
403
|
+
if (event === 'change:isRefreshing') onChangeIsRefreshing = cb;
|
|
404
|
+
}),
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const settled = services._finalizeReady();
|
|
408
|
+
await waitForAsync();
|
|
409
|
+
|
|
410
|
+
assert.isFalse(services.ready, 'ready must not flip while refresh is in flight');
|
|
411
|
+
assert.isFunction(onChangeIsRefreshing);
|
|
412
|
+
|
|
413
|
+
onChangeIsRefreshing!();
|
|
414
|
+
await settled;
|
|
415
|
+
|
|
416
|
+
assert.isTrue(services.ready);
|
|
274
417
|
});
|
|
275
418
|
});
|
|
276
419
|
|
|
@@ -425,7 +568,7 @@ describe('webex-core', () => {
|
|
|
425
568
|
|
|
426
569
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
427
570
|
services.webex.credentials = {
|
|
428
|
-
getOrgId: sinon.stub().returns('')
|
|
571
|
+
getOrgId: sinon.stub().returns('')
|
|
429
572
|
};
|
|
430
573
|
catalog.status = {};
|
|
431
574
|
});
|
|
@@ -466,7 +609,7 @@ describe('webex-core', () => {
|
|
|
466
609
|
const serviceGroup = 'postauth';
|
|
467
610
|
const hostmap = {services: [{hostmap: 'hostmap'}]};
|
|
468
611
|
|
|
469
|
-
services._formatReceivedHostmap = sinon.stub().returns({services: [{some: 'hostmap'}]});
|
|
612
|
+
services._formatReceivedHostmap = sinon.stub().returns({services : [{some: 'hostmap'}]});
|
|
470
613
|
|
|
471
614
|
catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
|
|
472
615
|
|
|
@@ -482,7 +625,7 @@ describe('webex-core', () => {
|
|
|
482
625
|
const serviceGroup = 'postauth';
|
|
483
626
|
const hostmap = {};
|
|
484
627
|
|
|
485
|
-
services._formatReceivedHostmap = sinon.stub().returns({services: undefined});
|
|
628
|
+
services._formatReceivedHostmap = sinon.stub().returns({services : undefined});
|
|
486
629
|
|
|
487
630
|
catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
|
|
488
631
|
|
|
@@ -721,13 +864,13 @@ describe('webex-core', () => {
|
|
|
721
864
|
});
|
|
722
865
|
|
|
723
866
|
describe('#invalidateCache', () => {
|
|
724
|
-
beforeEach(() => {
|
|
867
|
+
beforeEach( () => {
|
|
725
868
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
726
869
|
services.webex.credentials = {
|
|
727
|
-
getOrgId: sinon.stub().returns('')
|
|
870
|
+
getOrgId: sinon.stub().returns('')
|
|
728
871
|
};
|
|
729
872
|
catalog.status = {};
|
|
730
|
-
})
|
|
873
|
+
})
|
|
731
874
|
it('should log the timestamp parameter', async () => {
|
|
732
875
|
const timestamp = '1234567890';
|
|
733
876
|
services.logger.info = sinon.stub();
|
|
@@ -735,11 +878,7 @@ describe('webex-core', () => {
|
|
|
735
878
|
|
|
736
879
|
await services.invalidateCache(timestamp);
|
|
737
880
|
|
|
738
|
-
assert.calledWith(
|
|
739
|
-
services.logger.info,
|
|
740
|
-
'services: invalidate cache, timestamp:',
|
|
741
|
-
timestamp
|
|
742
|
-
);
|
|
881
|
+
assert.calledWith(services.logger.info, 'services: invalidate cache, timestamp:', timestamp);
|
|
743
882
|
});
|
|
744
883
|
|
|
745
884
|
it('should call initServiceCatalogs when invalidate timestamp is newer than catalog timestamp', async () => {
|
|
@@ -866,30 +1005,30 @@ describe('webex-core', () => {
|
|
|
866
1005
|
// Arrange: seed internal _services with mobius (including duplicate baseUrl)
|
|
867
1006
|
services._services = [
|
|
868
1007
|
{
|
|
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
|
|
1008
|
+
"id": "urn:TEAM:us-east-2_a:mobius",
|
|
1009
|
+
"serviceName": 'mobius',
|
|
1010
|
+
"serviceUrls": [
|
|
1011
|
+
{"baseUrl": 'https://mobius-us-east-2.prod.infra.webex.com/api/v1', "priority": 5},
|
|
1012
|
+
{"baseUrl": 'https://mobius-eu-central-1.prod.infra.webex.com/api/v1', "priority": 10},
|
|
1013
|
+
{"baseUrl": 'https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1', "priority": 15}, // duplicate
|
|
875
1014
|
],
|
|
876
1015
|
},
|
|
877
1016
|
{
|
|
878
|
-
id:
|
|
879
|
-
serviceName:
|
|
880
|
-
serviceUrls: [
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1017
|
+
"id": "urn:TEAM:ap-southeast-2_m:mobius",
|
|
1018
|
+
"serviceName": "mobius",
|
|
1019
|
+
"serviceUrls": [
|
|
1020
|
+
{
|
|
1021
|
+
"baseUrl": "https://mobius-me-central-1.prod.infra.webex.com/api/v1",
|
|
1022
|
+
"priority": 5
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
"baseUrl": "https://mobius-eu-central-1.prod.infra.webex.com/api/v1",
|
|
1026
|
+
"priority": 10
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
"baseUrl": "https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1",
|
|
1030
|
+
"priority": 15
|
|
1031
|
+
},
|
|
893
1032
|
],
|
|
894
1033
|
},
|
|
895
1034
|
// Non-mobius service should be ignored by getMobiusClusters
|
|
@@ -907,64 +1046,42 @@ describe('webex-core', () => {
|
|
|
907
1046
|
assert.deepEqual(
|
|
908
1047
|
clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
|
|
909
1048
|
[
|
|
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
|
-
},
|
|
1049
|
+
{host: 'mobius-us-east-2.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 5},
|
|
1050
|
+
{host: 'mobius-eu-central-1.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 10},
|
|
1051
|
+
{host: 'mobius-ap-southeast-2.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 15},
|
|
1052
|
+
{host: 'mobius-me-central-1.prod.infra.webex.com', id: 'urn:TEAM:ap-southeast-2_m:mobius', ttl: 0, priority: 5},
|
|
934
1053
|
]
|
|
935
1054
|
);
|
|
936
1055
|
});
|
|
937
1056
|
});
|
|
938
|
-
|
|
1057
|
+
|
|
939
1058
|
describe('#isValidHost', () => {
|
|
940
1059
|
beforeEach(() => {
|
|
941
1060
|
// 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
|
-
];
|
|
1061
|
+
services._services = [{
|
|
1062
|
+
"id": "urn:IDENTITY:PC75:adminAudit",
|
|
1063
|
+
"serviceName": "adminAudit",
|
|
1064
|
+
"serviceUrls": [
|
|
1065
|
+
{
|
|
1066
|
+
"baseUrl": "https://audit-ci-r.wbx2.com/audit-ci/api/v2",
|
|
1067
|
+
"priority": 5
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
"baseUrl": "https://audit-ci-t.wbx2.com/audit-ci/api/v2",
|
|
1071
|
+
"priority": 10
|
|
1072
|
+
}
|
|
1073
|
+
]
|
|
1074
|
+
},
|
|
1075
|
+
{
|
|
1076
|
+
"id": "urn:IDENTITY:PC75:cdf",
|
|
1077
|
+
"serviceName": "cdf",
|
|
1078
|
+
"serviceUrls": [
|
|
1079
|
+
{
|
|
1080
|
+
"baseUrl": "https://wapdavis.webex.com/davis/api/v1",
|
|
1081
|
+
"priority": 5
|
|
1082
|
+
}
|
|
1083
|
+
]
|
|
1084
|
+
}];
|
|
968
1085
|
});
|
|
969
1086
|
afterAll(() => {
|
|
970
1087
|
// Clean up the mock services list
|